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

Commit 4143e376 authored by Austin Kolander's avatar Austin Kolander
Browse files

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

Displays the thumbnail or mime icon in the header.

Bug: 62862758
Test: --
Change-Id: I8cc5807a002cba19f544fc404181e8c212384381
parent 62a4aed3
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