Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a8fb674 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent f339f259
Loading
Loading
Loading
Loading
+95 −6
Original line number Diff line number Diff line
@@ -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<List<Document>> 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<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);
    }
}
+46 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}