DirectoryFragment.java revision 9fb567b59112f99e64e0bff6f343188331bad28d
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.text.format.Formatter; 30import android.util.SparseBooleanArray; 31import android.view.ActionMode; 32import android.view.LayoutInflater; 33import android.view.Menu; 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.documentsui.model.Root; 50import com.android.internal.util.Predicate; 51import com.google.android.collect.Lists; 52 53import java.text.DateFormat; 54import java.util.ArrayList; 55import java.util.Comparator; 56import java.util.List; 57import java.util.concurrent.atomic.AtomicInteger; 58 59/** 60 * Display the documents inside a single directory. 61 */ 62public class DirectoryFragment extends Fragment { 63 64 private View mEmptyView; 65 private ListView mListView; 66 private GridView mGridView; 67 68 private AbsListView mCurrentView; 69 70 private Predicate<Document> mFilter; 71 72 public static final int TYPE_NORMAL = 1; 73 public static final int TYPE_SEARCH = 2; 74 public static final int TYPE_RECENT_OPEN = 3; 75 76 private int mType = TYPE_NORMAL; 77 78 private DocumentsAdapter mAdapter; 79 private LoaderCallbacks<List<Document>> mCallbacks; 80 81 private static final String EXTRA_TYPE = "type"; 82 private static final String EXTRA_URI = "uri"; 83 84 private static AtomicInteger sLoaderId = new AtomicInteger(4000); 85 86 private final int mLoaderId = sLoaderId.incrementAndGet(); 87 88 public static void showNormal(FragmentManager fm, Uri uri) { 89 show(fm, TYPE_NORMAL, uri); 90 } 91 92 public static void showSearch(FragmentManager fm, Uri uri, String query) { 93 final Uri searchUri = DocumentsContract.buildSearchUri(uri, query); 94 show(fm, TYPE_SEARCH, searchUri); 95 } 96 97 public static void showRecentsOpen(FragmentManager fm) { 98 show(fm, TYPE_RECENT_OPEN, null); 99 } 100 101 private static void show(FragmentManager fm, int type, Uri uri) { 102 final Bundle args = new Bundle(); 103 args.putInt(EXTRA_TYPE, type); 104 args.putParcelable(EXTRA_URI, uri); 105 106 final DirectoryFragment fragment = new DirectoryFragment(); 107 fragment.setArguments(args); 108 109 final FragmentTransaction ft = fm.beginTransaction(); 110 ft.replace(R.id.container_directory, fragment); 111 ft.commitAllowingStateLoss(); 112 } 113 114 public static DirectoryFragment get(FragmentManager fm) { 115 // TODO: deal with multiple directories shown at once 116 return (DirectoryFragment) fm.findFragmentById(R.id.container_directory); 117 } 118 119 @Override 120 public View onCreateView( 121 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 122 final Context context = inflater.getContext(); 123 124 final View view = inflater.inflate(R.layout.fragment_directory, container, false); 125 126 mEmptyView = view.findViewById(android.R.id.empty); 127 128 mListView = (ListView) view.findViewById(R.id.list); 129 mListView.setOnItemClickListener(mItemListener); 130 mListView.setMultiChoiceModeListener(mMultiListener); 131 132 mGridView = (GridView) view.findViewById(R.id.grid); 133 mGridView.setOnItemClickListener(mItemListener); 134 mGridView.setMultiChoiceModeListener(mMultiListener); 135 136 mAdapter = new DocumentsAdapter(); 137 138 final Uri uri = getArguments().getParcelable(EXTRA_URI); 139 mType = getArguments().getInt(EXTRA_TYPE); 140 141 mCallbacks = new LoaderCallbacks<List<Document>>() { 142 @Override 143 public Loader<List<Document>> onCreateLoader(int id, Bundle args) { 144 final DisplayState state = getDisplayState(DirectoryFragment.this); 145 mFilter = new MimePredicate(state.acceptMimes); 146 147 final Uri contentsUri; 148 if (mType == TYPE_NORMAL) { 149 contentsUri = DocumentsContract.buildContentsUri(uri); 150 } else if (mType == TYPE_RECENT_OPEN) { 151 contentsUri = RecentsProvider.buildRecentOpen(); 152 } else { 153 contentsUri = uri; 154 } 155 156 final Comparator<Document> sortOrder; 157 if (state.sortOrder == DisplayState.SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) { 158 sortOrder = new Document.DateComparator(); 159 } else if (state.sortOrder == DisplayState.SORT_ORDER_NAME) { 160 sortOrder = new Document.NameComparator(); 161 } else if (state.sortOrder == DisplayState.SORT_ORDER_SIZE) { 162 sortOrder = new Document.SizeComparator(); 163 } else { 164 throw new IllegalArgumentException("Unknown sort order " + state.sortOrder); 165 } 166 167 return new DirectoryLoader(context, contentsUri, mType, null, sortOrder); 168 } 169 170 @Override 171 public void onLoadFinished(Loader<List<Document>> loader, List<Document> data) { 172 mAdapter.swapDocuments(data); 173 } 174 175 @Override 176 public void onLoaderReset(Loader<List<Document>> loader) { 177 mAdapter.swapDocuments(null); 178 } 179 }; 180 181 updateDisplayState(); 182 183 return view; 184 } 185 186 @Override 187 public void onStart() { 188 super.onStart(); 189 190 final Context context = getActivity(); 191 getDisplayState(this).showSize = SettingsActivity.getDisplayFileSize(context); 192 193 getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks); 194 } 195 196 @Override 197 public void onStop() { 198 super.onStop(); 199 getLoaderManager().destroyLoader(mLoaderId); 200 } 201 202 public void updateDisplayState() { 203 final DisplayState state = getDisplayState(this); 204 205 // TODO: avoid kicking loader when nothing changed 206 getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks); 207 mListView.smoothScrollToPosition(0); 208 mGridView.smoothScrollToPosition(0); 209 210 mListView.setVisibility(state.mode == DisplayState.MODE_LIST ? View.VISIBLE : View.GONE); 211 mGridView.setVisibility(state.mode == DisplayState.MODE_GRID ? View.VISIBLE : View.GONE); 212 213 final int choiceMode; 214 if (state.allowMultiple) { 215 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL; 216 } else { 217 choiceMode = ListView.CHOICE_MODE_NONE; 218 } 219 220 if (state.mode == DisplayState.MODE_GRID) { 221 mListView.setAdapter(null); 222 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); 223 mGridView.setAdapter(mAdapter); 224 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width)); 225 mGridView.setNumColumns(GridView.AUTO_FIT); 226 mGridView.setChoiceMode(choiceMode); 227 mCurrentView = mGridView; 228 } else if (state.mode == DisplayState.MODE_LIST) { 229 mGridView.setAdapter(null); 230 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); 231 mListView.setAdapter(mAdapter); 232 mListView.setChoiceMode(choiceMode); 233 mCurrentView = mListView; 234 } else { 235 throw new IllegalStateException(); 236 } 237 } 238 239 private OnItemClickListener mItemListener = new OnItemClickListener() { 240 @Override 241 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 242 final Document doc = mAdapter.getItem(position); 243 ((DocumentsActivity) getActivity()).onDocumentPicked(doc); 244 } 245 }; 246 247 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() { 248 @Override 249 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 250 mode.getMenuInflater().inflate(R.menu.mode_directory, menu); 251 return true; 252 } 253 254 @Override 255 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 256 return true; 257 } 258 259 @Override 260 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 261 if (item.getItemId() == R.id.menu_open) { 262 final Uri uri = getArguments().getParcelable(EXTRA_URI); 263 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); 264 final ArrayList<Document> docs = Lists.newArrayList(); 265 266 final int size = checked.size(); 267 for (int i = 0; i < size; i++) { 268 if (checked.valueAt(i)) { 269 final Document doc = mAdapter.getItem(checked.keyAt(i)); 270 docs.add(doc); 271 } 272 } 273 274 ((DocumentsActivity) getActivity()).onDocumentsPicked(docs); 275 return true; 276 } else { 277 return false; 278 } 279 } 280 281 @Override 282 public void onDestroyActionMode(ActionMode mode) { 283 // ignored 284 } 285 286 @Override 287 public void onItemCheckedStateChanged( 288 ActionMode mode, int position, long id, boolean checked) { 289 if (checked) { 290 // Directories cannot be checked 291 final Document doc = mAdapter.getItem(position); 292 if (doc.isDirectory()) { 293 mCurrentView.setItemChecked(position, false); 294 } 295 } 296 297 mode.setTitle(getResources() 298 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount())); 299 } 300 }; 301 302 private static DisplayState getDisplayState(Fragment fragment) { 303 return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); 304 } 305 306 private class DocumentsAdapter extends BaseAdapter { 307 private List<Document> mDocuments; 308 309 public DocumentsAdapter() { 310 } 311 312 public void swapDocuments(List<Document> documents) { 313 mDocuments = documents; 314 315 if (documents != null && documents.isEmpty()) { 316 mEmptyView.setVisibility(View.VISIBLE); 317 } else { 318 mEmptyView.setVisibility(View.GONE); 319 } 320 321 notifyDataSetChanged(); 322 } 323 324 @Override 325 public View getView(int position, View convertView, ViewGroup parent) { 326 final Context context = parent.getContext(); 327 final DisplayState state = getDisplayState(DirectoryFragment.this); 328 329 if (convertView == null) { 330 final LayoutInflater inflater = LayoutInflater.from(context); 331 if (state.mode == DisplayState.MODE_LIST) { 332 convertView = inflater.inflate(R.layout.item_doc_list, parent, false); 333 } else if (state.mode == DisplayState.MODE_GRID) { 334 convertView = inflater.inflate(R.layout.item_doc_grid, parent, false); 335 } else { 336 throw new IllegalStateException(); 337 } 338 } 339 340 final Document doc = getItem(position); 341 342 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); 343 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 344 final View summaryGrid = convertView.findViewById(R.id.summary_grid); 345 final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1); 346 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); 347 final TextView date = (TextView) convertView.findViewById(R.id.date); 348 final TextView size = (TextView) convertView.findViewById(R.id.size); 349 350 if (doc.isThumbnailSupported()) { 351 // TODO: load thumbnails async 352 icon.setImageURI(doc.uri); 353 } else { 354 icon.setImageDrawable(RootsCache.resolveDocumentIcon( 355 context, doc.uri.getAuthority(), doc.mimeType)); 356 } 357 358 title.setText(doc.displayName); 359 360 if (mType == TYPE_NORMAL || mType == TYPE_SEARCH) { 361 icon1.setVisibility(View.GONE); 362 if (doc.summary != null) { 363 summary.setText(doc.summary); 364 summary.setVisibility(View.VISIBLE); 365 } else { 366 summary.setVisibility(View.INVISIBLE); 367 } 368 } else if (mType == TYPE_RECENT_OPEN) { 369 final Root root = RootsCache.findRoot(context, doc); 370 icon1.setVisibility(View.VISIBLE); 371 icon1.setImageDrawable(root.icon); 372 summary.setText(root.getDirectoryString()); 373 summary.setVisibility(View.VISIBLE); 374 } 375 376 if (summaryGrid != null) { 377 summaryGrid.setVisibility( 378 (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE); 379 } 380 381 // TODO: omit year from format 382 date.setText(DateUtils.formatSameDayTime( 383 doc.lastModified, System.currentTimeMillis(), DateFormat.SHORT, 384 DateFormat.SHORT)); 385 386 if (state.showSize) { 387 size.setVisibility(View.VISIBLE); 388 if (doc.isDirectory()) { 389 size.setText(null); 390 } else { 391 size.setText(Formatter.formatFileSize(context, doc.size)); 392 } 393 } else { 394 size.setVisibility(View.GONE); 395 } 396 397 return convertView; 398 } 399 400 @Override 401 public int getCount() { 402 return mDocuments != null ? mDocuments.size() : 0; 403 } 404 405 @Override 406 public Document getItem(int position) { 407 return mDocuments.get(position); 408 } 409 410 @Override 411 public long getItemId(int position) { 412 return getItem(position).uri.hashCode(); 413 } 414 415 @Override 416 public boolean areAllItemsEnabled() { 417 return false; 418 } 419 420 @Override 421 public boolean isEnabled(int position) { 422 final Document doc = getItem(position); 423 return mFilter.apply(doc); 424 } 425 } 426} 427