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

Commit 1394f620 authored by Austin Kolander's avatar Austin Kolander Committed by android-build-merger
Browse files

Add support to display thumbnail or mime icon in the document inspector.

am: 4143e376

Change-Id: Ieeabde0fdb54a9f1f0a80545ecf96a2efee46a09
parents 31078843 4143e376
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -17,11 +17,22 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/inspector_mime"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitCenter"
        android:background="@android:color/black" />

    <ImageView
        android:id="@+id/inspector_thumbnail"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0.0"
        android:scaleType="centerCrop"
        android:background="@android:color/black" />

    <TextView
        android:id="@+id/inspector_file_title"
        android:layout_width="wrap_content"
+134 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 static com.android.documentsui.base.Shared.VERBOSE;

import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.provider.DocumentsContract;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import com.android.documentsui.ProviderExecutor.Preemptable;
import java.util.function.BiConsumer;

/**
 *  Loads a Thumbnails asynchronously then animates from the mime icon to the thumbnail
 */
public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implements Preemptable {

    private static final String TAG = ThumbnailLoader.class.getCanonicalName();

    /**
     * Two animations applied to image views. The first is used to switch mime icon and thumbnail.
     * The second is used when we need to update thumbnail.
     */
    public static final BiConsumer<View, View> ANIM_FADE_IN = (mime, thumb) -> {
        float alpha = mime.getAlpha();
        mime.animate().alpha(0f).start();
        thumb.setAlpha(0f);
        thumb.animate().alpha(alpha).start();
    };
    public static final BiConsumer<View, View> ANIM_NO_OP = (mime, thumb) -> {};

    private final ImageView mIconThumb;
    private final Point mThumbSize;
    // A callback to apply animation to image views after the thumbnail is loaded.
    private final BiConsumer<View, View> mImageAnimator;
    private final Uri mUri;
    private final ImageView mIconMime;
    private final long mLastModified;
    private final boolean mAddToCache;
    private final CancellationSignal mSignal;

    /**
     * @param uri - to a thumbnail.
     * @param iconMime - ImageView for displaying a mime type.
     * @param iconThumb - ImageView to display the thumbnail.
     * @param thumbSize - size of the thumbnail.
     * @param lastModified - used for updating thumbnail caches.
     * @param animator - used to animate from the mime icon to the thumbnail.
     * @param addToCache - flag that determines if the loader saves the thumbnail to the cache.
     */
    public ThumbnailLoader(Uri uri, ImageView iconMime, ImageView iconThumb,
        Point thumbSize, long lastModified, BiConsumer<View, View> animator, boolean addToCache) {
        mUri = uri;
        mIconMime = iconMime;
        mIconThumb = iconThumb;
        mThumbSize = thumbSize;
        mImageAnimator = animator;
        mLastModified = lastModified;
        mAddToCache = addToCache;
        mSignal = new CancellationSignal();
        mIconThumb.setTag(this);
        if (VERBOSE) Log.v(TAG, "Starting icon loader task for " + mUri);
    }

    @Override
    public void preempt() {
        if (VERBOSE) Log.v(TAG, "Icon loader task for " + mUri + " was cancelled.");
        cancel(false);
        mSignal.cancel();
    }

    @Override
    protected Bitmap doInBackground(Uri... params) {
        if (isCancelled()) {
            return null;
        }

        final Context context = mIconThumb.getContext();
        final ContentResolver resolver = context.getContentResolver();

        ContentProviderClient client = null;
        Bitmap result = null;
        try {
            client = DocumentsApplication.acquireUnstableProviderOrThrow(
                resolver, mUri.getAuthority());
            result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal);
            if (result != null && mAddToCache) {
                final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context);
                cache.putThumbnail(mUri, mThumbSize, result, mLastModified);
            }
        } catch (Exception e) {
            if (!(e instanceof OperationCanceledException)) {
                Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e);
            }
        } finally {
            ContentProviderClient.releaseQuietly(client);
        }
        return result;
    }

    @Override
    protected void onPostExecute(Bitmap result) {
        if (VERBOSE) Log.v(TAG, "Loader task for " + mUri + " completed");

            if (mIconThumb.getTag() == this && result != null) {
                mIconThumb.setTag(null);
                mIconThumb.setImageBitmap(result);
                mImageAnimator.accept(mIconMime, mIconThumb);
            }
    }
}
+6 −97
Original line number Diff line number Diff line
@@ -20,16 +20,11 @@ import static com.android.documentsui.base.Shared.VERBOSE;
import static com.android.documentsui.base.State.MODE_GRID;
import static com.android.documentsui.base.State.MODE_LIST;

import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
@@ -40,10 +35,10 @@ import android.widget.ImageView;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.IconUtils;
import com.android.documentsui.ProviderExecutor;
import com.android.documentsui.ProviderExecutor.Preemptable;
import com.android.documentsui.R;
import com.android.documentsui.ThumbnailCache;
import com.android.documentsui.ThumbnailCache.Result;
import com.android.documentsui.ThumbnailLoader;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.MimeTypes;
import com.android.documentsui.base.State;
@@ -58,16 +53,6 @@ import java.util.function.BiConsumer;
public class IconHelper {
    private static final String TAG = "IconHelper";

    // Two animations applied to image views. The first is used to switch mime icon and thumbnail.
    // The second is used when we need to update thumbnail.
    private static final BiConsumer<View, View> ANIM_FADE_IN = (mime, thumb) -> {
        float alpha = mime.getAlpha();
        mime.animate().alpha(0f).start();
        thumb.setAlpha(0f);
        thumb.animate().alpha(alpha).start();
    };
    private static final BiConsumer<View, View> ANIM_NO_OP = (mime, thumb) -> {};

    private final Context mContext;
    private final ThumbnailCache mThumbnailCache;

@@ -129,89 +114,13 @@ public class IconHelper {
     * @param icon
     */
    public void stopLoading(ImageView icon) {
        final LoaderTask oldTask = (LoaderTask) icon.getTag();
        final ThumbnailLoader oldTask = (ThumbnailLoader) icon.getTag();
        if (oldTask != null) {
            oldTask.preempt();
            icon.setTag(null);
        }
    }

    /** Internal task for loading thumbnails asynchronously. */
    private static class LoaderTask
            extends AsyncTask<Uri, Void, Bitmap>
            implements Preemptable {
        private final Uri mUri;
        private final ImageView mIconMime;
        private final ImageView mIconThumb;
        private final Point mThumbSize;
        private final long mLastModified;

        // A callback to apply animation to image views after the thumbnail is loaded.
        private final BiConsumer<View, View> mImageAnimator;

        private final CancellationSignal mSignal;

        public LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb,
                Point thumbSize, long lastModified, BiConsumer<View, View> animator) {
            mUri = uri;
            mIconMime = iconMime;
            mIconThumb = iconThumb;
            mThumbSize = thumbSize;
            mImageAnimator = animator;
            mLastModified = lastModified;
            mSignal = new CancellationSignal();
            if (VERBOSE) Log.v(TAG, "Starting icon loader task for " + mUri);
        }

        @Override
        public void preempt() {
            if (VERBOSE) Log.v(TAG, "Icon loader task for " + mUri + " was cancelled.");
            cancel(false);
            mSignal.cancel();
        }

        @Override
        protected Bitmap doInBackground(Uri... params) {
            if (isCancelled()) {
                return null;
            }

            final Context context = mIconThumb.getContext();
            final ContentResolver resolver = context.getContentResolver();

            ContentProviderClient client = null;
            Bitmap result = null;
            try {
                client = DocumentsApplication.acquireUnstableProviderOrThrow(
                        resolver, mUri.getAuthority());
                result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal);
                if (result != null) {
                    final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context);
                    cache.putThumbnail(mUri, mThumbSize, result, mLastModified);
                }
            } catch (Exception e) {
                if (!(e instanceof OperationCanceledException)) {
                    Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e);
                }
            } finally {
                ContentProviderClient.releaseQuietly(client);
            }
            return result;
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            if (VERBOSE) Log.v(TAG, "Loader task for " + mUri + " completed");

            if (mIconThumb.getTag() == this && result != null) {
                mIconThumb.setTag(null);
                mIconThumb.setImageBitmap(result);

                mImageAnimator.accept(mIconMime, mIconThumb);
            }
        }
    }

    /**
     * Load thumbnails for a directory list item.
     *
@@ -287,11 +196,11 @@ public class IconHelper {
                            uri.toString(), result.getStatus(), stale));
            if (!result.isExactHit() || stale) {
                final BiConsumer<View, View> animator =
                        (cachedThumbnail == null ? ANIM_FADE_IN : ANIM_NO_OP);
                final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize,
                        docLastModified, animator);
                        (cachedThumbnail == null ? ThumbnailLoader.ANIM_FADE_IN :
                                ThumbnailLoader.ANIM_NO_OP);

                iconThumb.setTag(task);
                final ThumbnailLoader task = new ThumbnailLoader(uri, iconMime, iconThumb,
                    mCurrentSize, docLastModified, animator, true);

                ProviderExecutor.forAuthority(docAuthority).execute(task);
            }
+42 −1
Original line number Diff line number Diff line
@@ -15,13 +15,20 @@
 */
package com.android.documentsui.inspector;

import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.android.documentsui.ProviderExecutor;
import com.android.documentsui.ThumbnailLoader;
import com.android.documentsui.base.Display;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.R;
import java.util.function.Consumer;
@@ -31,8 +38,14 @@ import java.util.function.Consumer;
 */
public final class HeaderView extends RelativeLayout implements Consumer<DocumentInfo> {

    private static final String TAG = HeaderView.class.getCanonicalName();

    private final Context mContext;
    private final View mHeader;
    private ImageView mMime;
    private ImageView mThumbnail;
    private final TextView mTitle;
    private Point mImageDimensions;

    public HeaderView(Context context) {
        this(context, null);
@@ -46,8 +59,15 @@ public final class HeaderView extends RelativeLayout implements Consumer<Documen
        super(context, attrs, defStyleAttr);
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        mContext = context;
        mHeader = inflater.inflate(R.layout.inspector_header, null);
        mMime = (ImageView) mHeader.findViewById(R.id.inspector_mime);
        mThumbnail = (ImageView) mHeader.findViewById(R.id.inspector_thumbnail);
        mTitle = (TextView) mHeader.findViewById(R.id.inspector_file_title);

        int width = (int) Display.screenWidth((Activity)context);
        int height = mContext.getResources().getDimensionPixelSize(R.dimen.inspector_header_height);
        mImageDimensions = new Point(width, height);
    }

    @Override
@@ -55,6 +75,10 @@ public final class HeaderView extends RelativeLayout implements Consumer<Documen
        if (!hasHeader()) {
            addView(mHeader);
        }

        if(!hasHeaderImage()) {
            loadHeaderImage(info);
        }
        mTitle.setText(info.displayName);
    }

@@ -66,4 +90,21 @@ public final class HeaderView extends RelativeLayout implements Consumer<Documen
        }
        return false;
    }

    private void loadHeaderImage(DocumentInfo info) {

        // load the mime icon.
        Drawable d = mContext.getContentResolver().getTypeDrawable(info.mimeType);
        mMime.setImageDrawable(d);

        // load the thumbnail async.
        final ThumbnailLoader task = new ThumbnailLoader(info.derivedUri, mMime, mThumbnail,
            mImageDimensions, info.lastModified, ThumbnailLoader.ANIM_FADE_IN, false);
        task.executeOnExecutor(ProviderExecutor.forAuthority(info.derivedUri.getAuthority()),
            info.derivedUri);
    }

    private boolean hasHeaderImage() {
        return mThumbnail.getAlpha() == 1.0f;
    }
}
 No newline at end of file