DirectoryFragment.java revision d182bb641f228b2d28527a6aa86075f6358ab838
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 static com.android.documentsui.DocumentsActivity.TAG; 20import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; 21import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; 22import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; 23import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN; 24import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN; 25import static com.android.documentsui.model.DocumentInfo.getCursorInt; 26import static com.android.documentsui.model.DocumentInfo.getCursorLong; 27import static com.android.documentsui.model.DocumentInfo.getCursorString; 28 29import android.app.Fragment; 30import android.app.FragmentManager; 31import android.app.FragmentTransaction; 32import android.app.LoaderManager.LoaderCallbacks; 33import android.content.ContentResolver; 34import android.content.Context; 35import android.content.Intent; 36import android.content.Loader; 37import android.database.Cursor; 38import android.graphics.Bitmap; 39import android.graphics.Point; 40import android.net.Uri; 41import android.os.AsyncTask; 42import android.os.Bundle; 43import android.provider.DocumentsContract; 44import android.provider.DocumentsContract.Document; 45import android.text.format.DateUtils; 46import android.text.format.Formatter; 47import android.text.format.Time; 48import android.util.Log; 49import android.util.SparseBooleanArray; 50import android.view.ActionMode; 51import android.view.LayoutInflater; 52import android.view.Menu; 53import android.view.MenuItem; 54import android.view.View; 55import android.view.ViewGroup; 56import android.widget.AbsListView; 57import android.widget.AbsListView.MultiChoiceModeListener; 58import android.widget.AdapterView; 59import android.widget.AdapterView.OnItemClickListener; 60import android.widget.BaseAdapter; 61import android.widget.GridView; 62import android.widget.ImageView; 63import android.widget.ListView; 64import android.widget.TextView; 65import android.widget.Toast; 66 67import com.android.documentsui.DocumentsActivity.State; 68import com.android.documentsui.model.DocumentInfo; 69import com.android.documentsui.model.RootInfo; 70import com.android.internal.util.Predicate; 71import com.google.android.collect.Lists; 72 73import java.util.ArrayList; 74import java.util.List; 75import java.util.concurrent.atomic.AtomicInteger; 76 77/** 78 * Display the documents inside a single directory. 79 */ 80public class DirectoryFragment extends Fragment { 81 82 private View mEmptyView; 83 private ListView mListView; 84 private GridView mGridView; 85 86 private AbsListView mCurrentView; 87 88 private Predicate<DocumentInfo> mFilter; 89 90 public static final int TYPE_NORMAL = 1; 91 public static final int TYPE_SEARCH = 2; 92 public static final int TYPE_RECENT_OPEN = 3; 93 94 private int mType = TYPE_NORMAL; 95 96 private int mLastMode = MODE_UNKNOWN; 97 private int mLastSortOrder = SORT_ORDER_UNKNOWN; 98 99 private Point mThumbSize; 100 101 private DocumentsAdapter mAdapter; 102 private LoaderCallbacks<DirectoryResult> mCallbacks; 103 104 private static final String EXTRA_TYPE = "type"; 105 private static final String EXTRA_ROOT = "root"; 106 private static final String EXTRA_DOC = "doc"; 107 private static final String EXTRA_QUERY = "query"; 108 109 private static AtomicInteger sLoaderId = new AtomicInteger(4000); 110 111 private final int mLoaderId = sLoaderId.incrementAndGet(); 112 113 public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc) { 114 show(fm, TYPE_NORMAL, root, doc, null); 115 } 116 117 public static void showSearch( 118 FragmentManager fm, RootInfo root, DocumentInfo doc, String query) { 119 show(fm, TYPE_SEARCH, root, doc, query); 120 } 121 122 public static void showRecentsOpen(FragmentManager fm) { 123 show(fm, TYPE_RECENT_OPEN, null, null, null); 124 } 125 126 private static void show( 127 FragmentManager fm, int type, RootInfo root, DocumentInfo doc, String query) { 128 final Bundle args = new Bundle(); 129 args.putInt(EXTRA_TYPE, type); 130 args.putParcelable(EXTRA_ROOT, root); 131 args.putParcelable(EXTRA_DOC, doc); 132 args.putString(EXTRA_QUERY, query); 133 134 final DirectoryFragment fragment = new DirectoryFragment(); 135 fragment.setArguments(args); 136 137 final FragmentTransaction ft = fm.beginTransaction(); 138 ft.replace(R.id.container_directory, fragment); 139 ft.commitAllowingStateLoss(); 140 } 141 142 public static DirectoryFragment get(FragmentManager fm) { 143 // TODO: deal with multiple directories shown at once 144 return (DirectoryFragment) fm.findFragmentById(R.id.container_directory); 145 } 146 147 @Override 148 public View onCreateView( 149 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 150 final Context context = inflater.getContext(); 151 final View view = inflater.inflate(R.layout.fragment_directory, container, false); 152 153 mEmptyView = view.findViewById(android.R.id.empty); 154 155 mListView = (ListView) view.findViewById(R.id.list); 156 mListView.setOnItemClickListener(mItemListener); 157 mListView.setMultiChoiceModeListener(mMultiListener); 158 159 mGridView = (GridView) view.findViewById(R.id.grid); 160 mGridView.setOnItemClickListener(mItemListener); 161 mGridView.setMultiChoiceModeListener(mMultiListener); 162 163 return view; 164 } 165 166 @Override 167 public void onActivityCreated(Bundle savedInstanceState) { 168 super.onActivityCreated(savedInstanceState); 169 170 final Context context = getActivity(); 171 final State state = getDisplayState(DirectoryFragment.this); 172 173 mAdapter = new DocumentsAdapter(); 174 mType = getArguments().getInt(EXTRA_TYPE); 175 176 mCallbacks = new LoaderCallbacks<DirectoryResult>() { 177 @Override 178 public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) { 179 final RootInfo root = getArguments().getParcelable(EXTRA_ROOT); 180 final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC); 181 final String query = getArguments().getString(EXTRA_QUERY); 182 183 Uri contentsUri; 184 switch (mType) { 185 case TYPE_NORMAL: 186 contentsUri = DocumentsContract.buildChildDocumentsUri( 187 doc.authority, doc.documentId); 188 return new DirectoryLoader(context, root, doc, contentsUri); 189 case TYPE_SEARCH: 190 contentsUri = DocumentsContract.buildSearchDocumentsUri( 191 doc.authority, doc.documentId, query); 192 return new DirectoryLoader(context, root, doc, contentsUri); 193 case TYPE_RECENT_OPEN: 194 final RootsCache roots = DocumentsApplication.getRootsCache(context); 195 final List<RootInfo> matchingRoots = roots.getMatchingRoots(state); 196 return new RecentLoader(context, matchingRoots, state.acceptMimes); 197 default: 198 throw new IllegalStateException("Unknown type " + mType); 199 } 200 } 201 202 @Override 203 public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) { 204 if (!isAdded()) return; 205 206 mAdapter.swapCursor(result.cursor); 207 208 // Push latest state up to UI 209 // TODO: if mode change was racing with us, don't overwrite it 210 state.mode = result.mode; 211 state.sortOrder = result.sortOrder; 212 ((DocumentsActivity) context).onStateChanged(); 213 214 updateDisplayState(); 215 216 if (mLastSortOrder != result.sortOrder) { 217 mLastSortOrder = result.sortOrder; 218 mListView.smoothScrollToPosition(0); 219 mGridView.smoothScrollToPosition(0); 220 } 221 } 222 223 @Override 224 public void onLoaderReset(Loader<DirectoryResult> loader) { 225 mAdapter.swapCursor(null); 226 } 227 }; 228 229 // Kick off loader at least once 230 getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); 231 232 updateDisplayState(); 233 } 234 235 @Override 236 public void onStart() { 237 super.onStart(); 238 updateDisplayState(); 239 } 240 241 public void onUserSortOrderChanged() { 242 // User change always triggers reload 243 getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); 244 } 245 246 public void onUserModeChanged() { 247 // Mode change is just display; no need to reload 248 updateDisplayState(); 249 } 250 251 private void updateDisplayState() { 252 final State state = getDisplayState(this); 253 254 mFilter = new MimePredicate(state.acceptMimes); 255 256 if (mLastMode == state.mode) return; 257 mLastMode = state.mode; 258 259 mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE); 260 mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE); 261 262 final int choiceMode; 263 if (state.allowMultiple) { 264 choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL; 265 } else { 266 choiceMode = ListView.CHOICE_MODE_NONE; 267 } 268 269 final int thumbSize; 270 if (state.mode == MODE_GRID) { 271 thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width); 272 mListView.setAdapter(null); 273 mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); 274 mGridView.setAdapter(mAdapter); 275 mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width)); 276 mGridView.setNumColumns(GridView.AUTO_FIT); 277 mGridView.setChoiceMode(choiceMode); 278 mCurrentView = mGridView; 279 } else if (state.mode == MODE_LIST) { 280 thumbSize = getResources().getDimensionPixelSize(R.dimen.icon_size); 281 mGridView.setAdapter(null); 282 mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); 283 mListView.setAdapter(mAdapter); 284 mListView.setChoiceMode(choiceMode); 285 mCurrentView = mListView; 286 } else { 287 throw new IllegalStateException("Unknown state " + state.mode); 288 } 289 290 mThumbSize = new Point(thumbSize, thumbSize); 291 } 292 293 private OnItemClickListener mItemListener = new OnItemClickListener() { 294 @Override 295 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 296 final Cursor cursor = mAdapter.getItem(position); 297 final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); 298 if (mFilter.apply(doc)) { 299 ((DocumentsActivity) getActivity()).onDocumentPicked(doc); 300 } 301 } 302 }; 303 304 private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() { 305 @Override 306 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 307 mode.getMenuInflater().inflate(R.menu.mode_directory, menu); 308 return true; 309 } 310 311 @Override 312 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 313 final State state = getDisplayState(DirectoryFragment.this); 314 315 final MenuItem open = menu.findItem(R.id.menu_open); 316 final MenuItem share = menu.findItem(R.id.menu_share); 317 final MenuItem delete = menu.findItem(R.id.menu_delete); 318 319 final boolean manageMode = state.action == ACTION_MANAGE; 320 open.setVisible(!manageMode); 321 share.setVisible(manageMode); 322 delete.setVisible(manageMode); 323 324 return true; 325 } 326 327 @Override 328 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 329 final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); 330 final ArrayList<DocumentInfo> docs = Lists.newArrayList(); 331 final int size = checked.size(); 332 for (int i = 0; i < size; i++) { 333 if (checked.valueAt(i)) { 334 final Cursor cursor = mAdapter.getItem(checked.keyAt(i)); 335 final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); 336 docs.add(doc); 337 } 338 } 339 340 final int id = item.getItemId(); 341 if (id == R.id.menu_open) { 342 DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); 343 mode.finish(); 344 return true; 345 346 } else if (id == R.id.menu_share) { 347 onShareDocuments(docs); 348 mode.finish(); 349 return true; 350 351 } else if (id == R.id.menu_delete) { 352 onDeleteDocuments(docs); 353 mode.finish(); 354 return true; 355 356 } else { 357 return false; 358 } 359 } 360 361 @Override 362 public void onDestroyActionMode(ActionMode mode) { 363 // ignored 364 } 365 366 @Override 367 public void onItemCheckedStateChanged( 368 ActionMode mode, int position, long id, boolean checked) { 369 if (checked) { 370 // Directories cannot be checked 371 final Cursor cursor = mAdapter.getItem(position); 372 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 373 if (Document.MIME_TYPE_DIR.equals(docMimeType)) { 374 mCurrentView.setItemChecked(position, false); 375 } 376 } 377 378 mode.setTitle(getResources() 379 .getString(R.string.mode_selected_count, mCurrentView.getCheckedItemCount())); 380 } 381 }; 382 383 private void onShareDocuments(List<DocumentInfo> docs) { 384 Intent intent; 385 if (docs.size() == 1) { 386 final DocumentInfo doc = docs.get(0); 387 388 intent = new Intent(Intent.ACTION_SEND); 389 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 390 intent.addCategory(Intent.CATEGORY_DEFAULT); 391 intent.setType(doc.mimeType); 392 intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri); 393 394 } else if (docs.size() > 1) { 395 intent = new Intent(Intent.ACTION_SEND_MULTIPLE); 396 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 397 intent.addCategory(Intent.CATEGORY_DEFAULT); 398 399 final ArrayList<String> mimeTypes = Lists.newArrayList(); 400 final ArrayList<Uri> uris = Lists.newArrayList(); 401 for (DocumentInfo doc : docs) { 402 mimeTypes.add(doc.mimeType); 403 uris.add(doc.derivedUri); 404 } 405 406 intent.setType(findCommonMimeType(mimeTypes)); 407 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); 408 409 } else { 410 return; 411 } 412 413 intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via)); 414 startActivity(intent); 415 } 416 417 private void onDeleteDocuments(List<DocumentInfo> docs) { 418 final Context context = getActivity(); 419 final ContentResolver resolver = context.getContentResolver(); 420 421 boolean hadTrouble = false; 422 for (DocumentInfo doc : docs) { 423 if (!doc.isDeleteSupported()) { 424 Log.w(TAG, "Skipping " + doc); 425 hadTrouble = true; 426 continue; 427 } 428 429 if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) { 430 Log.w(TAG, "Failed to delete " + doc); 431 hadTrouble = true; 432 } 433 } 434 435 if (hadTrouble) { 436 Toast.makeText(context, R.string.toast_failed_delete, Toast.LENGTH_SHORT).show(); 437 } 438 } 439 440 private static State getDisplayState(Fragment fragment) { 441 return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); 442 } 443 444 private interface Footer { 445 public View getView(View convertView, ViewGroup parent); 446 } 447 448 private static class LoadingFooter implements Footer { 449 @Override 450 public View getView(View convertView, ViewGroup parent) { 451 final Context context = parent.getContext(); 452 if (convertView == null) { 453 final LayoutInflater inflater = LayoutInflater.from(context); 454 convertView = inflater.inflate(R.layout.item_loading, parent, false); 455 } 456 return convertView; 457 } 458 } 459 460 private class MessageFooter implements Footer { 461 private final int mIcon; 462 private final String mMessage; 463 464 public MessageFooter(int icon, String message) { 465 mIcon = icon; 466 mMessage = message; 467 } 468 469 @Override 470 public View getView(View convertView, ViewGroup parent) { 471 final Context context = parent.getContext(); 472 final State state = getDisplayState(DirectoryFragment.this); 473 474 if (convertView == null) { 475 final LayoutInflater inflater = LayoutInflater.from(context); 476 if (state.mode == MODE_LIST) { 477 convertView = inflater.inflate(R.layout.item_message_list, parent, false); 478 } else if (state.mode == MODE_GRID) { 479 convertView = inflater.inflate(R.layout.item_message_grid, parent, false); 480 } else { 481 throw new IllegalStateException(); 482 } 483 } 484 485 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); 486 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 487 icon.setImageResource(mIcon); 488 title.setText(mMessage); 489 return convertView; 490 } 491 } 492 493 private class DocumentsAdapter extends BaseAdapter { 494 private Cursor mCursor; 495 private int mCursorCount; 496 497 private List<Footer> mFooters = Lists.newArrayList(); 498 499 public void swapCursor(Cursor cursor) { 500 mCursor = cursor; 501 mCursorCount = cursor != null ? cursor.getCount() : 0; 502 503 mFooters.clear(); 504 505 final Bundle extras = cursor != null ? cursor.getExtras() : null; 506 if (extras != null) { 507 final String info = extras.getString(DocumentsContract.EXTRA_INFO); 508 if (info != null) { 509 mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, info)); 510 } 511 final String error = extras.getString(DocumentsContract.EXTRA_ERROR); 512 if (error != null) { 513 mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, error)); 514 } 515 if (extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)) { 516 mFooters.add(new LoadingFooter()); 517 } 518 } 519 520 if (isEmpty()) { 521 mEmptyView.setVisibility(View.VISIBLE); 522 } else { 523 mEmptyView.setVisibility(View.GONE); 524 } 525 526 notifyDataSetChanged(); 527 } 528 529 @Override 530 public View getView(int position, View convertView, ViewGroup parent) { 531 if (position < mCursorCount) { 532 return getDocumentView(position, convertView, parent); 533 } else { 534 position -= mCursorCount; 535 return mFooters.get(position).getView(convertView, parent); 536 } 537 } 538 539 private View getDocumentView(int position, View convertView, ViewGroup parent) { 540 final Context context = parent.getContext(); 541 final State state = getDisplayState(DirectoryFragment.this); 542 543 final RootsCache roots = DocumentsApplication.getRootsCache(context); 544 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( 545 context, mThumbSize); 546 547 if (convertView == null) { 548 final LayoutInflater inflater = LayoutInflater.from(context); 549 if (state.mode == MODE_LIST) { 550 convertView = inflater.inflate(R.layout.item_doc_list, parent, false); 551 } else if (state.mode == MODE_GRID) { 552 convertView = inflater.inflate(R.layout.item_doc_grid, parent, false); 553 } else { 554 throw new IllegalStateException(); 555 } 556 } 557 558 final Cursor cursor = getItem(position); 559 560 final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); 561 final String docRootId = getCursorString(cursor, RootCursorWrapper.COLUMN_ROOT_ID); 562 final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); 563 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 564 final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); 565 final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); 566 final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON); 567 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); 568 final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY); 569 final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE); 570 571 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); 572 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 573 final View line2 = convertView.findViewById(R.id.line2); 574 final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1); 575 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); 576 final TextView date = (TextView) convertView.findViewById(R.id.date); 577 final TextView size = (TextView) convertView.findViewById(R.id.size); 578 579 final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag(); 580 if (oldTask != null) { 581 oldTask.cancel(false); 582 } 583 584 if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) { 585 final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); 586 final Bitmap cachedResult = thumbs.get(uri); 587 if (cachedResult != null) { 588 icon.setImageBitmap(cachedResult); 589 } else { 590 final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize); 591 icon.setImageBitmap(null); 592 icon.setTag(task); 593 task.execute(uri); 594 } 595 } else if (docIcon != 0) { 596 icon.setImageDrawable(IconUtils.loadPackageIcon(context, docAuthority, docIcon)); 597 } else { 598 icon.setImageDrawable(IconUtils.loadMimeIcon(context, docMimeType)); 599 } 600 601 title.setText(docDisplayName); 602 603 boolean hasLine2 = false; 604 605 if (mType == TYPE_RECENT_OPEN) { 606 final RootInfo root = roots.getRoot(docAuthority, docRootId); 607 icon1.setVisibility(View.VISIBLE); 608 icon1.setImageDrawable(root.loadIcon(context)); 609 summary.setText(root.getDirectoryString()); 610 summary.setVisibility(View.VISIBLE); 611 summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END); 612 hasLine2 = true; 613 } else { 614 icon1.setVisibility(View.GONE); 615 if (docSummary != null) { 616 summary.setText(docSummary); 617 summary.setVisibility(View.VISIBLE); 618 hasLine2 = true; 619 } else { 620 summary.setVisibility(View.INVISIBLE); 621 } 622 } 623 624 if (docLastModified == -1) { 625 date.setText(null); 626 } else { 627 date.setText(formatTime(context, docLastModified)); 628 hasLine2 = true; 629 } 630 631 if (state.showSize) { 632 size.setVisibility(View.VISIBLE); 633 if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) { 634 size.setText(null); 635 } else { 636 size.setText(Formatter.formatFileSize(context, docSize)); 637 hasLine2 = true; 638 } 639 } else { 640 size.setVisibility(View.GONE); 641 } 642 643 line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE); 644 645 return convertView; 646 } 647 648 @Override 649 public int getCount() { 650 return mCursorCount + mFooters.size(); 651 } 652 653 @Override 654 public Cursor getItem(int position) { 655 if (position < mCursorCount) { 656 mCursor.moveToPosition(position); 657 return mCursor; 658 } else { 659 return null; 660 } 661 } 662 663 @Override 664 public long getItemId(int position) { 665 return position; 666 } 667 668 @Override 669 public int getItemViewType(int position) { 670 if (position < mCursorCount) { 671 return 0; 672 } else { 673 return IGNORE_ITEM_VIEW_TYPE; 674 } 675 } 676 677 @Override 678 public boolean areAllItemsEnabled() { 679 return false; 680 } 681 682 @Override 683 public boolean isEnabled(int position) { 684 return position < mCursorCount; 685 } 686 } 687 688 private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> { 689 private final ImageView mTarget; 690 private final Point mThumbSize; 691 692 public ThumbnailAsyncTask(ImageView target, Point thumbSize) { 693 mTarget = target; 694 mThumbSize = thumbSize; 695 } 696 697 @Override 698 protected void onPreExecute() { 699 mTarget.setTag(this); 700 } 701 702 @Override 703 protected Bitmap doInBackground(Uri... params) { 704 final Context context = mTarget.getContext(); 705 final Uri uri = params[0]; 706 707 Bitmap result = null; 708 try { 709 result = DocumentsContract.getDocumentThumbnail( 710 context.getContentResolver(), uri, mThumbSize, null); 711 if (result != null) { 712 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( 713 context, mThumbSize); 714 thumbs.put(uri, result); 715 } 716 } catch (Exception e) { 717 Log.w(TAG, "Failed to load thumbnail: " + e); 718 } 719 return result; 720 } 721 722 @Override 723 protected void onPostExecute(Bitmap result) { 724 if (mTarget.getTag() == this) { 725 mTarget.setImageBitmap(result); 726 mTarget.setTag(null); 727 } 728 } 729 } 730 731 private static String formatTime(Context context, long when) { 732 // TODO: DateUtils should make this easier 733 Time then = new Time(); 734 then.set(when); 735 Time now = new Time(); 736 now.setToNow(); 737 738 int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT 739 | DateUtils.FORMAT_ABBREV_ALL; 740 741 if (then.year != now.year) { 742 flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE; 743 } else if (then.yearDay != now.yearDay) { 744 flags |= DateUtils.FORMAT_SHOW_DATE; 745 } else { 746 flags |= DateUtils.FORMAT_SHOW_TIME; 747 } 748 749 return DateUtils.formatDateTime(context, when, flags); 750 } 751 752 private String findCommonMimeType(List<String> mimeTypes) { 753 String[] commonType = mimeTypes.get(0).split("/"); 754 if (commonType.length != 2) { 755 return "*/*"; 756 } 757 758 for (int i = 1; i < mimeTypes.size(); i++) { 759 String[] type = mimeTypes.get(i).split("/"); 760 if (type.length != 2) continue; 761 762 if (!commonType[1].equals(type[1])) { 763 commonType[1] = "*"; 764 } 765 766 if (!commonType[0].equals(type[0])) { 767 commonType[0] = "*"; 768 commonType[1] = "*"; 769 break; 770 } 771 } 772 773 return commonType[0] + "/" + commonType[1]; 774 } 775} 776