From 9d0843df7e3984293dc4ab6ee2f9502e898b63aa Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 7 May 2013 12:41:33 -0700 Subject: [PATCH] Resized thumbnails; async; extend MatrixCursor. When requesting thumbnails, check if their dimensions are larger than requested, and downscale to avoid memory pressure. Load them async and with LruCache. Extend MatrixCursor so that RowBuilder can offer() columns without requiring they know the projection map. This makes it easier to respond to query() calls, where the remote side controls the projection map. Use it to handle custom projections in external storage backend. Update date/time formatting to match spec. Bug: 10333418, 10331689 Change-Id: I7e947a8e8068af8a39b55e6766b3241de4f3fc16 --- api/current.txt | 1 + core/java/android/database/MatrixCursor.java | 47 +++++++--- core/java/android/provider/DocumentsContract.java | 22 ++++- core/java/android/provider/MediaStore.java | 6 +- .../src/android/database/MatrixCursorTest.java | 50 ++++++++++ .../com/android/documentsui/DirectoryFragment.java | 101 +++++++++++++++++++-- .../com/android/documentsui/ThumbnailCache.java | 46 ++++++++++ .../ExternalStorageProvider/AndroidManifest.xml | 2 +- .../externalstorage/ExternalStorageProvider.java | 100 +++++++++++--------- 9 files changed, 306 insertions(+), 69 deletions(-) create mode 100644 packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java diff --git a/api/current.txt b/api/current.txt index 5a3c7db7e1eb..2a81469385ba 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8051,6 +8051,7 @@ package android.database { public class MatrixCursor.RowBuilder { method public android.database.MatrixCursor.RowBuilder add(java.lang.Object); + method public android.database.MatrixCursor.RowBuilder offer(java.lang.String, java.lang.Object); } public class MergeCursor extends android.database.AbstractCursor { diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java index 6e68b6bbbfcb..2a0d9b9f2842 100644 --- a/core/java/android/database/MatrixCursor.java +++ b/core/java/android/database/MatrixCursor.java @@ -83,11 +83,10 @@ public class MatrixCursor extends AbstractCursor { * row */ public RowBuilder newRow() { - rowCount++; - int endIndex = rowCount * columnCount; + final int row = rowCount++; + final int endIndex = rowCount * columnCount; ensureCapacity(endIndex); - int start = endIndex - columnCount; - return new RowBuilder(start, endIndex); + return new RowBuilder(row); } /** @@ -180,18 +179,29 @@ public class MatrixCursor extends AbstractCursor { } /** - * Builds a row, starting from the left-most column and adding one column - * value at a time. Follows the same ordering as the column names specified - * at cursor construction time. + * Builds a row of values using either of these approaches: + * + * Undefined values are left as {@code null}. */ public class RowBuilder { + private final int row; + private final int endIndex; private int index; - private final int endIndex; - RowBuilder(int index, int endIndex) { - this.index = index; - this.endIndex = endIndex; + RowBuilder(int row) { + this.row = row; + this.index = row * columnCount; + this.endIndex = index + columnCount; } /** @@ -210,6 +220,21 @@ public class MatrixCursor extends AbstractCursor { data[index++] = columnValue; return this; } + + /** + * Offer value for possible inclusion if this cursor defines the given + * column. Columns not defined by the cursor are silently ignored. + * + * @return this builder to support chaining + */ + public RowBuilder offer(String columnName, Object value) { + for (int i = 0; i < columnNames.length; i++) { + if (columnName.equals(columnNames[i])) { + data[(row * columnCount) + i] = value; + } + } + return this; + } } // AbstractCursor implementation. diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index e1810ca05f39..91d349a61294 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -36,6 +36,7 @@ import com.google.android.collect.Lists; import libcore.io.IoUtils; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -461,16 +462,27 @@ public final class DocumentsContract { final Bundle opts = new Bundle(); opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size); - InputStream is = null; + AssetFileDescriptor afd = null; try { - is = new AssetFileDescriptor.AutoCloseInputStream( - resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts)); - return BitmapFactory.decodeStream(is); + afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts); + + final FileDescriptor fd = afd.getFileDescriptor(); + final BitmapFactory.Options bitmapOpts = new BitmapFactory.Options(); + + bitmapOpts.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); + + final int widthSample = bitmapOpts.outWidth / size.x; + final int heightSample = bitmapOpts.outHeight / size.y; + + bitmapOpts.inJustDecodeBounds = false; + bitmapOpts.inSampleSize = Math.min(widthSample, heightSample); + return BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); } catch (IOException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; } finally { - IoUtils.closeQuietly(is); + IoUtils.closeQuietly(afd); } } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index cb6300f691f4..ad6839b63f3f 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -19,8 +19,8 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; @@ -532,7 +532,8 @@ public final class MediaStore { private static final Object sThumbBufLock = new Object(); private static byte[] sThumbBuf; - private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { + private static Bitmap getMiniThumbFromFile( + Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { Bitmap bitmap = null; Uri thumbUri = null; try { @@ -577,6 +578,7 @@ public final class MediaStore { if (c != null) c.close(); } } + /** * This method ensure thumbnails associated with origId are generated and decode the byte * stream from database (MICRO_KIND) or file (MINI_KIND). diff --git a/core/tests/coretests/src/android/database/MatrixCursorTest.java b/core/tests/coretests/src/android/database/MatrixCursorTest.java index cdab638205e6..fc48c17db839 100644 --- a/core/tests/coretests/src/android/database/MatrixCursorTest.java +++ b/core/tests/coretests/src/android/database/MatrixCursorTest.java @@ -128,6 +128,56 @@ public class MatrixCursorTest extends TestCase { } catch (IllegalArgumentException e) { /* expected */ } } + public void testRowBuilderOffer() { + MatrixCursor cursor = newMatrixCursor(); + + cursor.newRow() + .offer("float", 4.2f) + .offer("string", "foobar") + .offer("blob", new byte[] {(byte) 0xaa, (byte) 0x55}) + .offer("lolwat", "kittens"); + + cursor.newRow(); + + cursor.newRow() + .offer("string", "zero") + .offer("string", "one") + .offer("string", "two") + .offer("lolwat", "kittens"); + + assertTrue(cursor.moveToFirst()); + assertEquals("foobar", cursor.getString(0)); + assertEquals(null, cursor.getString(1)); + assertEquals(0, cursor.getShort(1)); + assertEquals(0, cursor.getInt(2)); + assertEquals(0, cursor.getLong(3)); + assertEquals(4.2f, cursor.getFloat(4)); + assertEquals(0.0d, cursor.getDouble(5)); + MoreAsserts.assertEquals(new byte[] {(byte) 0xaa, (byte) 0x55}, cursor.getBlob(6)); + + assertTrue(cursor.moveToNext()); + assertEquals(null, cursor.getString(0)); + assertEquals(0, cursor.getShort(1)); + assertEquals(0, cursor.getInt(2)); + assertEquals(0, cursor.getLong(3)); + assertEquals(0.0f, cursor.getFloat(4)); + assertEquals(0.0d, cursor.getDouble(5)); + assertEquals(null, cursor.getBlob(6)); + + assertTrue(cursor.moveToNext()); + assertEquals("two", cursor.getString(0)); + assertEquals(0, cursor.getShort(1)); + assertEquals(0, cursor.getInt(2)); + assertEquals(0, cursor.getLong(3)); + assertEquals(0.0f, cursor.getFloat(4)); + assertEquals(0.0d, cursor.getDouble(5)); + assertEquals(null, cursor.getBlob(6)); + + assertTrue(cursor.isLast()); + assertFalse(cursor.moveToNext()); + assertTrue(cursor.isAfterLast()); + } + static class NonIterableArrayList extends ArrayList { NonIterableArrayList() {} diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index ac5629edd627..fbdb3a706020 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -16,17 +16,24 @@ package com.android.documentsui; +import static com.android.documentsui.DocumentsActivity.TAG; + import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Loader; +import android.graphics.Bitmap; +import android.graphics.Point; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.provider.DocumentsContract; import android.text.format.DateUtils; import android.text.format.Formatter; +import android.text.format.Time; +import android.util.Log; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.LayoutInflater; @@ -75,6 +82,8 @@ public class DirectoryFragment extends Fragment { private int mType = TYPE_NORMAL; + private Point mThumbSize; + private DocumentsAdapter mAdapter; private LoaderCallbacks> mCallbacks; @@ -217,7 +226,9 @@ public class DirectoryFragment extends Fragment { choiceMode = ListView.CHOICE_MODE_NONE; } + final int thumbSize; if (state.mode == DisplayState.MODE_GRID) { + thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width); mListView.setAdapter(null); mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); mGridView.setAdapter(mAdapter); @@ -226,6 +237,7 @@ public class DirectoryFragment extends Fragment { mGridView.setChoiceMode(choiceMode); mCurrentView = mGridView; } else if (state.mode == DisplayState.MODE_LIST) { + thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); mGridView.setAdapter(null); mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); mListView.setAdapter(mAdapter); @@ -234,6 +246,8 @@ public class DirectoryFragment extends Fragment { } else { throw new IllegalStateException(); } + + mThumbSize = new Point(thumbSize, thumbSize); } private OnItemClickListener mItemListener = new OnItemClickListener() { @@ -349,9 +363,21 @@ public class DirectoryFragment extends Fragment { final TextView date = (TextView) convertView.findViewById(R.id.date); final TextView size = (TextView) convertView.findViewById(R.id.size); + final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag(); + if (oldTask != null) { + oldTask.cancel(false); + } + if (doc.isThumbnailSupported()) { - // TODO: load thumbnails async - icon.setImageURI(doc.uri); + final Bitmap cachedResult = ThumbnailCache.get(context).get(doc.uri); + if (cachedResult != null) { + icon.setImageBitmap(cachedResult); + } else { + final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize); + icon.setImageBitmap(null); + icon.setTag(task); + task.execute(doc.uri); + } } else { icon.setImageDrawable(RootsCache.resolveDocumentIcon( context, doc.uri.getAuthority(), doc.mimeType)); @@ -380,10 +406,11 @@ public class DirectoryFragment extends Fragment { (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE); } - // TODO: omit year from format - date.setText(DateUtils.formatSameDayTime( - doc.lastModified, System.currentTimeMillis(), DateFormat.SHORT, - DateFormat.SHORT)); + if (doc.lastModified == -1) { + date.setText(null); + } else { + date.setText(formatTime(context, doc.lastModified)); + } if (state.showSize) { size.setVisibility(View.VISIBLE); @@ -414,4 +441,66 @@ public class DirectoryFragment extends Fragment { return getItem(position).uri.hashCode(); } } + + private static class ThumbnailAsyncTask extends AsyncTask { + private final ImageView mTarget; + private final Point mSize; + + public ThumbnailAsyncTask(ImageView target, Point size) { + mTarget = target; + mSize = size; + } + + @Override + protected void onPreExecute() { + mTarget.setTag(this); + } + + @Override + protected Bitmap doInBackground(Uri... params) { + final Context context = mTarget.getContext(); + final Uri uri = params[0]; + + Bitmap result = null; + try { + result = DocumentsContract.getThumbnail( + context.getContentResolver(), uri, mSize); + if (result != null) { + ThumbnailCache.get(context).put(uri, result); + } + } catch (Exception e) { + Log.w(TAG, "Failed to load thumbnail: " + e); + } + return result; + } + + @Override + protected void onPostExecute(Bitmap result) { + if (mTarget.getTag() == this) { + mTarget.setImageBitmap(result); + mTarget.setTag(null); + } + } + } + + private static String formatTime(Context context, long when) { + // TODO: DateUtils should make this easier + Time then = new Time(); + then.set(when); + Time now = new Time(); + now.setToNow(); + + int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT + | DateUtils.FORMAT_ABBREV_ALL; + + if (then.year != now.year) { + flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE; + } else if (then.yearDay != now.yearDay) { + flags |= DateUtils.FORMAT_SHOW_DATE; + } else { + flags |= DateUtils.FORMAT_SHOW_TIME; + } + + return DateUtils.formatDateTime(context, when, flags); + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java b/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java new file mode 100644 index 000000000000..bc7abeba2349 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.LruCache; + +public class ThumbnailCache extends LruCache { + private static ThumbnailCache sCache; + + public static ThumbnailCache get(Context context) { + if (sCache == null) { + final ActivityManager am = (ActivityManager) context.getSystemService( + Context.ACTIVITY_SERVICE); + final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024; + sCache = new ThumbnailCache(memoryClassBytes / 4); + } + return sCache; + } + + public ThumbnailCache(int maxSizeBytes) { + super(maxSizeBytes); + } + + @Override + protected int sizeOf(Uri key, Bitmap value) { + return value.getByteCount(); + } +} diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index afdb6bb0be24..52721669cf54 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -7,7 +7,7 @@ diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 659139d5fb83..b4bf5631e9d5 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -22,10 +22,10 @@ import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.BaseColumns; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; import android.provider.DocumentsContract.Documents; @@ -45,7 +45,7 @@ import java.util.LinkedList; public class ExternalStorageProvider extends ContentProvider { private static final String TAG = "ExternalStorage"; - private static final String AUTHORITY = "com.android.externalstorage"; + private static final String AUTHORITY = "com.android.externalstorage.documents"; // TODO: support multiple storage devices @@ -57,6 +57,14 @@ public class ExternalStorageProvider extends ContentProvider { private static final int URI_DOCS_ID_CONTENTS = 4; private static final int URI_DOCS_ID_SEARCH = 5; + static { + sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS); + sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID); + sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID); + sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS); + sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH); + } + private HashMap mRoots = Maps.newHashMap(); private static class Root { @@ -68,13 +76,15 @@ public class ExternalStorageProvider extends ContentProvider { public File path; } - static { - sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS); - sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH); - } + private static final String[] ALL_ROOTS_COLUMNS = new String[] { + RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE, + RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES + }; + + private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] { + DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, + DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS + }; @Override public boolean onCreate() { @@ -93,64 +103,59 @@ public class ExternalStorageProvider extends ContentProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - - // TODO: support custom projections - final String[] rootsProjection = new String[] { - BaseColumns._ID, RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, - RootColumns.TITLE, RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES }; - final String[] docsProjection = new String[] { - BaseColumns._ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, - DocumentColumns.DOC_ID, DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, - DocumentColumns.FLAGS }; - switch (sMatcher.match(uri)) { case URI_ROOTS: { - final MatrixCursor cursor = new MatrixCursor(rootsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_ROOTS_COLUMNS); for (Root root : mRoots.values()) { - includeRoot(cursor, root); + includeRoot(result, root); } - return cursor; + return result; } case URI_ROOTS_ID: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final MatrixCursor cursor = new MatrixCursor(rootsProjection); - includeRoot(cursor, root); - return cursor; + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_ROOTS_COLUMNS); + includeRoot(result, root); + return result; } case URI_DOCS_ID: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); - final MatrixCursor cursor = new MatrixCursor(docsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_DOCUMENTS_COLUMNS); final File file = docIdToFile(root, docId); - includeFile(cursor, root, file); - return cursor; + includeFile(result, root, file); + return result; } case URI_DOCS_ID_CONTENTS: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); - final MatrixCursor cursor = new MatrixCursor(docsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_DOCUMENTS_COLUMNS); final File parent = docIdToFile(root, docId); for (File file : parent.listFiles()) { - includeFile(cursor, root, file); + includeFile(result, root, file); } - return cursor; + return result; } case URI_DOCS_ID_SEARCH: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); final String query = DocumentsContract.getSearchQuery(uri).toLowerCase(); - final MatrixCursor cursor = new MatrixCursor(docsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_DOCUMENTS_COLUMNS); final File parent = docIdToFile(root, docId); final LinkedList pending = new LinkedList(); pending.add(parent); - while (!pending.isEmpty() && cursor.getCount() < 20) { + while (!pending.isEmpty() && result.getCount() < 20) { final File file = pending.removeFirst(); if (file.isDirectory()) { for (File child : file.listFiles()) { @@ -158,12 +163,12 @@ public class ExternalStorageProvider extends ContentProvider { } } else { if (file.getName().toLowerCase().contains(query)) { - includeFile(cursor, root, file); + includeFile(result, root, file); } } } - return cursor; + return result; } default: { throw new UnsupportedOperationException("Unsupported Uri " + uri); @@ -196,13 +201,17 @@ public class ExternalStorageProvider extends ContentProvider { } } - private void includeRoot(MatrixCursor cursor, Root root) { - cursor.addRow(new Object[] { - root.name.hashCode(), root.name, root.rootType, root.icon, root.title, root.summary, - root.path.getFreeSpace() }); + private void includeRoot(MatrixCursor result, Root root) { + final RowBuilder row = result.newRow(); + row.offer(RootColumns.ROOT_ID, root.name); + row.offer(RootColumns.ROOT_TYPE, root.rootType); + row.offer(RootColumns.ICON, root.icon); + row.offer(RootColumns.TITLE, root.title); + row.offer(RootColumns.SUMMARY, root.summary); + row.offer(RootColumns.AVAILABLE_BYTES, root.path.getFreeSpace()); } - private void includeFile(MatrixCursor cursor, Root root, File file) { + private void includeFile(MatrixCursor result, Root root, File file) { int flags = 0; if (file.isDirectory()) { @@ -223,8 +232,6 @@ public class ExternalStorageProvider extends ContentProvider { } final String docId = fileToDocId(root, file); - final long id = docId.hashCode(); - final String displayName; if (Documents.DOC_ID_ROOT.equals(docId)) { displayName = root.title; @@ -232,8 +239,13 @@ public class ExternalStorageProvider extends ContentProvider { displayName = file.getName(); } - cursor.addRow(new Object[] { - id, displayName, file.length(), docId, mimeType, file.lastModified(), flags }); + final RowBuilder row = result.newRow(); + row.offer(DocumentColumns.DOC_ID, docId); + row.offer(DocumentColumns.DISPLAY_NAME, displayName); + row.offer(DocumentColumns.SIZE, file.length()); + row.offer(DocumentColumns.MIME_TYPE, mimeType); + row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified()); + row.offer(DocumentColumns.FLAGS, flags); } @Override -- 2.11.0