am 58514937: Merge "Resized thumbnails; async; extend MatrixCursor." into klp-dev
* commit '58514937628dfcf3b2949e4cbc45d5526ecb8019': Resized thumbnails; async; extend MatrixCursor.
This commit is contained in:
@@ -8052,6 +8052,7 @@ package android.database {
|
|||||||
|
|
||||||
public class MatrixCursor.RowBuilder {
|
public class MatrixCursor.RowBuilder {
|
||||||
method public android.database.MatrixCursor.RowBuilder add(java.lang.Object);
|
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 {
|
public class MergeCursor extends android.database.AbstractCursor {
|
||||||
|
|||||||
@@ -83,11 +83,10 @@ public class MatrixCursor extends AbstractCursor {
|
|||||||
* row
|
* row
|
||||||
*/
|
*/
|
||||||
public RowBuilder newRow() {
|
public RowBuilder newRow() {
|
||||||
rowCount++;
|
final int row = rowCount++;
|
||||||
int endIndex = rowCount * columnCount;
|
final int endIndex = rowCount * columnCount;
|
||||||
ensureCapacity(endIndex);
|
ensureCapacity(endIndex);
|
||||||
int start = endIndex - columnCount;
|
return new RowBuilder(row);
|
||||||
return new RowBuilder(start, endIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,18 +179,29 @@ public class MatrixCursor extends AbstractCursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a row, starting from the left-most column and adding one column
|
* Builds a row of values using either of these approaches:
|
||||||
* value at a time. Follows the same ordering as the column names specified
|
* <ul>
|
||||||
* at cursor construction time.
|
* <li>Values can be added with explicit column ordering using
|
||||||
|
* {@link #add(Object)}, which starts from the left-most column and adds one
|
||||||
|
* column value at a time. This follows the same ordering as the column
|
||||||
|
* names specified at cursor construction time.
|
||||||
|
* <li>Column and value pairs can be offered for possible inclusion using
|
||||||
|
* {@link #offer(String, Object)}. If the cursor includes the given column,
|
||||||
|
* the value will be set for that column, otherwise the value is ignored.
|
||||||
|
* This approach is useful when matching data to a custom projection.
|
||||||
|
* </ul>
|
||||||
|
* Undefined values are left as {@code null}.
|
||||||
*/
|
*/
|
||||||
public class RowBuilder {
|
public class RowBuilder {
|
||||||
|
private final int row;
|
||||||
private int index;
|
|
||||||
private final int endIndex;
|
private final int endIndex;
|
||||||
|
|
||||||
RowBuilder(int index, int endIndex) {
|
private int index;
|
||||||
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;
|
data[index++] = columnValue;
|
||||||
return this;
|
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.
|
// AbstractCursor implementation.
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import com.google.android.collect.Lists;
|
|||||||
|
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -461,16 +462,27 @@ public final class DocumentsContract {
|
|||||||
final Bundle opts = new Bundle();
|
final Bundle opts = new Bundle();
|
||||||
opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size);
|
opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size);
|
||||||
|
|
||||||
InputStream is = null;
|
AssetFileDescriptor afd = null;
|
||||||
try {
|
try {
|
||||||
is = new AssetFileDescriptor.AutoCloseInputStream(
|
afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts);
|
||||||
resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts));
|
|
||||||
return BitmapFactory.decodeStream(is);
|
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) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
|
Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(is);
|
IoUtils.closeQuietly(afd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ package android.provider;
|
|||||||
import android.annotation.SdkConstant;
|
import android.annotation.SdkConstant;
|
||||||
import android.annotation.SdkConstant.SdkConstantType;
|
import android.annotation.SdkConstant.SdkConstantType;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.DatabaseUtils;
|
import android.database.DatabaseUtils;
|
||||||
@@ -532,7 +532,8 @@ public final class MediaStore {
|
|||||||
private static final Object sThumbBufLock = new Object();
|
private static final Object sThumbBufLock = new Object();
|
||||||
private static byte[] sThumbBuf;
|
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;
|
Bitmap bitmap = null;
|
||||||
Uri thumbUri = null;
|
Uri thumbUri = null;
|
||||||
try {
|
try {
|
||||||
@@ -577,6 +578,7 @@ public final class MediaStore {
|
|||||||
if (c != null) c.close();
|
if (c != null) c.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method ensure thumbnails associated with origId are generated and decode the byte
|
* This method ensure thumbnails associated with origId are generated and decode the byte
|
||||||
* stream from database (MICRO_KIND) or file (MINI_KIND).
|
* stream from database (MICRO_KIND) or file (MINI_KIND).
|
||||||
|
|||||||
@@ -128,6 +128,56 @@ public class MatrixCursorTest extends TestCase {
|
|||||||
} catch (IllegalArgumentException e) { /* expected */ }
|
} 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<T> extends ArrayList<T> {
|
static class NonIterableArrayList<T> extends ArrayList<T> {
|
||||||
|
|
||||||
NonIterableArrayList() {}
|
NonIterableArrayList() {}
|
||||||
|
|||||||
@@ -16,17 +16,24 @@
|
|||||||
|
|
||||||
package com.android.documentsui;
|
package com.android.documentsui;
|
||||||
|
|
||||||
|
import static com.android.documentsui.DocumentsActivity.TAG;
|
||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.FragmentManager;
|
import android.app.FragmentManager;
|
||||||
import android.app.FragmentTransaction;
|
import android.app.FragmentTransaction;
|
||||||
import android.app.LoaderManager.LoaderCallbacks;
|
import android.app.LoaderManager.LoaderCallbacks;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Loader;
|
import android.content.Loader;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.text.format.Formatter;
|
import android.text.format.Formatter;
|
||||||
|
import android.text.format.Time;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -75,6 +82,8 @@ public class DirectoryFragment extends Fragment {
|
|||||||
|
|
||||||
private int mType = TYPE_NORMAL;
|
private int mType = TYPE_NORMAL;
|
||||||
|
|
||||||
|
private Point mThumbSize;
|
||||||
|
|
||||||
private DocumentsAdapter mAdapter;
|
private DocumentsAdapter mAdapter;
|
||||||
private LoaderCallbacks<List<Document>> mCallbacks;
|
private LoaderCallbacks<List<Document>> mCallbacks;
|
||||||
|
|
||||||
@@ -217,7 +226,9 @@ public class DirectoryFragment extends Fragment {
|
|||||||
choiceMode = ListView.CHOICE_MODE_NONE;
|
choiceMode = ListView.CHOICE_MODE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int thumbSize;
|
||||||
if (state.mode == DisplayState.MODE_GRID) {
|
if (state.mode == DisplayState.MODE_GRID) {
|
||||||
|
thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width);
|
||||||
mListView.setAdapter(null);
|
mListView.setAdapter(null);
|
||||||
mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
|
mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
|
||||||
mGridView.setAdapter(mAdapter);
|
mGridView.setAdapter(mAdapter);
|
||||||
@@ -226,6 +237,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
mGridView.setChoiceMode(choiceMode);
|
mGridView.setChoiceMode(choiceMode);
|
||||||
mCurrentView = mGridView;
|
mCurrentView = mGridView;
|
||||||
} else if (state.mode == DisplayState.MODE_LIST) {
|
} else if (state.mode == DisplayState.MODE_LIST) {
|
||||||
|
thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
|
||||||
mGridView.setAdapter(null);
|
mGridView.setAdapter(null);
|
||||||
mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
|
mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
|
||||||
mListView.setAdapter(mAdapter);
|
mListView.setAdapter(mAdapter);
|
||||||
@@ -234,6 +246,8 @@ public class DirectoryFragment extends Fragment {
|
|||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mThumbSize = new Point(thumbSize, thumbSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnItemClickListener mItemListener = new OnItemClickListener() {
|
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 date = (TextView) convertView.findViewById(R.id.date);
|
||||||
final TextView size = (TextView) convertView.findViewById(R.id.size);
|
final TextView size = (TextView) convertView.findViewById(R.id.size);
|
||||||
|
|
||||||
|
final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag();
|
||||||
|
if (oldTask != null) {
|
||||||
|
oldTask.cancel(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (doc.isThumbnailSupported()) {
|
if (doc.isThumbnailSupported()) {
|
||||||
// TODO: load thumbnails async
|
final Bitmap cachedResult = ThumbnailCache.get(context).get(doc.uri);
|
||||||
icon.setImageURI(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 {
|
} else {
|
||||||
icon.setImageDrawable(RootsCache.resolveDocumentIcon(
|
icon.setImageDrawable(RootsCache.resolveDocumentIcon(
|
||||||
context, doc.uri.getAuthority(), doc.mimeType));
|
context, doc.uri.getAuthority(), doc.mimeType));
|
||||||
@@ -380,10 +406,11 @@ public class DirectoryFragment extends Fragment {
|
|||||||
(summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE);
|
(summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: omit year from format
|
if (doc.lastModified == -1) {
|
||||||
date.setText(DateUtils.formatSameDayTime(
|
date.setText(null);
|
||||||
doc.lastModified, System.currentTimeMillis(), DateFormat.SHORT,
|
} else {
|
||||||
DateFormat.SHORT));
|
date.setText(formatTime(context, doc.lastModified));
|
||||||
|
}
|
||||||
|
|
||||||
if (state.showSize) {
|
if (state.showSize) {
|
||||||
size.setVisibility(View.VISIBLE);
|
size.setVisibility(View.VISIBLE);
|
||||||
@@ -414,4 +441,66 @@ public class DirectoryFragment extends Fragment {
|
|||||||
return getItem(position).uri.hashCode();
|
return getItem(position).uri.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<Uri, Bitmap> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<application android:label="@string/app_label">
|
<application android:label="@string/app_label">
|
||||||
<provider
|
<provider
|
||||||
android:name=".ExternalStorageProvider"
|
android:name=".ExternalStorageProvider"
|
||||||
android:authorities="com.android.externalstorage"
|
android:authorities="com.android.externalstorage.documents"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import android.content.ContentValues;
|
|||||||
import android.content.UriMatcher;
|
import android.content.UriMatcher;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
|
import android.database.MatrixCursor.RowBuilder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.provider.BaseColumns;
|
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.DocumentsContract.DocumentColumns;
|
import android.provider.DocumentsContract.DocumentColumns;
|
||||||
import android.provider.DocumentsContract.Documents;
|
import android.provider.DocumentsContract.Documents;
|
||||||
@@ -45,7 +45,7 @@ import java.util.LinkedList;
|
|||||||
public class ExternalStorageProvider extends ContentProvider {
|
public class ExternalStorageProvider extends ContentProvider {
|
||||||
private static final String TAG = "ExternalStorage";
|
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
|
// 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_CONTENTS = 4;
|
||||||
private static final int URI_DOCS_ID_SEARCH = 5;
|
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<String, Root> mRoots = Maps.newHashMap();
|
private HashMap<String, Root> mRoots = Maps.newHashMap();
|
||||||
|
|
||||||
private static class Root {
|
private static class Root {
|
||||||
@@ -68,13 +76,15 @@ public class ExternalStorageProvider extends ContentProvider {
|
|||||||
public File path;
|
public File path;
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
private static final String[] ALL_ROOTS_COLUMNS = new String[] {
|
||||||
sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS);
|
RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE,
|
||||||
sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID);
|
RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES
|
||||||
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_DOCUMENTS_COLUMNS = new String[] {
|
||||||
}
|
DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
|
||||||
|
DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
@@ -93,64 +103,59 @@ public class ExternalStorageProvider extends ContentProvider {
|
|||||||
@Override
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||||
String sortOrder) {
|
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)) {
|
switch (sMatcher.match(uri)) {
|
||||||
case URI_ROOTS: {
|
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()) {
|
for (Root root : mRoots.values()) {
|
||||||
includeRoot(cursor, root);
|
includeRoot(result, root);
|
||||||
}
|
}
|
||||||
return cursor;
|
return result;
|
||||||
}
|
}
|
||||||
case URI_ROOTS_ID: {
|
case URI_ROOTS_ID: {
|
||||||
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
||||||
|
|
||||||
final MatrixCursor cursor = new MatrixCursor(rootsProjection);
|
final MatrixCursor result = new MatrixCursor(
|
||||||
includeRoot(cursor, root);
|
projection != null ? projection : ALL_ROOTS_COLUMNS);
|
||||||
return cursor;
|
includeRoot(result, root);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
case URI_DOCS_ID: {
|
case URI_DOCS_ID: {
|
||||||
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
||||||
final String docId = DocumentsContract.getDocId(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);
|
final File file = docIdToFile(root, docId);
|
||||||
includeFile(cursor, root, file);
|
includeFile(result, root, file);
|
||||||
return cursor;
|
return result;
|
||||||
}
|
}
|
||||||
case URI_DOCS_ID_CONTENTS: {
|
case URI_DOCS_ID_CONTENTS: {
|
||||||
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
||||||
final String docId = DocumentsContract.getDocId(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);
|
final File parent = docIdToFile(root, docId);
|
||||||
|
|
||||||
for (File file : parent.listFiles()) {
|
for (File file : parent.listFiles()) {
|
||||||
includeFile(cursor, root, file);
|
includeFile(result, root, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cursor;
|
return result;
|
||||||
}
|
}
|
||||||
case URI_DOCS_ID_SEARCH: {
|
case URI_DOCS_ID_SEARCH: {
|
||||||
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
final Root root = mRoots.get(DocumentsContract.getRootId(uri));
|
||||||
final String docId = DocumentsContract.getDocId(uri);
|
final String docId = DocumentsContract.getDocId(uri);
|
||||||
final String query = DocumentsContract.getSearchQuery(uri).toLowerCase();
|
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 File parent = docIdToFile(root, docId);
|
||||||
|
|
||||||
final LinkedList<File> pending = new LinkedList<File>();
|
final LinkedList<File> pending = new LinkedList<File>();
|
||||||
pending.add(parent);
|
pending.add(parent);
|
||||||
while (!pending.isEmpty() && cursor.getCount() < 20) {
|
while (!pending.isEmpty() && result.getCount() < 20) {
|
||||||
final File file = pending.removeFirst();
|
final File file = pending.removeFirst();
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
for (File child : file.listFiles()) {
|
for (File child : file.listFiles()) {
|
||||||
@@ -158,12 +163,12 @@ public class ExternalStorageProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (file.getName().toLowerCase().contains(query)) {
|
if (file.getName().toLowerCase().contains(query)) {
|
||||||
includeFile(cursor, root, file);
|
includeFile(result, root, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cursor;
|
return result;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||||
@@ -196,13 +201,17 @@ public class ExternalStorageProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void includeRoot(MatrixCursor cursor, Root root) {
|
private void includeRoot(MatrixCursor result, Root root) {
|
||||||
cursor.addRow(new Object[] {
|
final RowBuilder row = result.newRow();
|
||||||
root.name.hashCode(), root.name, root.rootType, root.icon, root.title, root.summary,
|
row.offer(RootColumns.ROOT_ID, root.name);
|
||||||
root.path.getFreeSpace() });
|
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;
|
int flags = 0;
|
||||||
|
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
@@ -223,8 +232,6 @@ public class ExternalStorageProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String docId = fileToDocId(root, file);
|
final String docId = fileToDocId(root, file);
|
||||||
final long id = docId.hashCode();
|
|
||||||
|
|
||||||
final String displayName;
|
final String displayName;
|
||||||
if (Documents.DOC_ID_ROOT.equals(docId)) {
|
if (Documents.DOC_ID_ROOT.equals(docId)) {
|
||||||
displayName = root.title;
|
displayName = root.title;
|
||||||
@@ -232,8 +239,13 @@ public class ExternalStorageProvider extends ContentProvider {
|
|||||||
displayName = file.getName();
|
displayName = file.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.addRow(new Object[] {
|
final RowBuilder row = result.newRow();
|
||||||
id, displayName, file.length(), docId, mimeType, file.lastModified(), flags });
|
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
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user