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