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

Commit b5c226b1 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Sharesheet - image preview support"

parents 8bb64458 0ea391bc
Loading
Loading
Loading
Loading
+172 −6
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.internal.app;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.IntDef;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.prediction.AppPredictionContext;
@@ -27,6 +30,7 @@ import android.app.prediction.AppTargetId;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -46,6 +50,7 @@ import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
@@ -93,11 +98,13 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ImageUtils;

import com.google.android.collect.Lists;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -174,6 +181,15 @@ public class ChooserActivity extends ResolverActivity {
    private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
    private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;

    @Retention(SOURCE)
    @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
    private @interface ContentPreviewType {
    }

    private static final int CONTENT_PREVIEW_IMAGE = 0;
    private static final int CONTENT_PREVIEW_FILE = 1;
    private static final int CONTENT_PREVIEW_TEXT = 2;

    private final Handler mChooserHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
@@ -448,18 +464,35 @@ public class ChooserActivity extends ResolverActivity {
            return;
        }

        ViewGroup contentPreviewLayout = findViewById(R.id.content_preview);
        String action = targetIntent.getAction();
        if (!(Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action))) {
            contentPreviewLayout.setVisibility(View.GONE);
            return;
        }

        showDefaultContentPreview(contentPreviewLayout, targetIntent);
        int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
        displayContentPreview(previewType, targetIntent);
    }

    private void displayContentPreview(@ContentPreviewType int previewType, Intent targetIntent) {
        switch (previewType) {
            case CONTENT_PREVIEW_TEXT:
                displayTextContentPreview(targetIntent);
                break;
            case CONTENT_PREVIEW_IMAGE:
                displayImageContentPreview(targetIntent);
                break;
            case CONTENT_PREVIEW_FILE:
                displayFileContentPreview(targetIntent);
                break;
            default:
                Log.e(TAG, "Unexpected content preview type: " + previewType);
        }
    }

    private void showDefaultContentPreview(final ViewGroup parentLayout,
            final Intent targetIntent) {
    private void displayTextContentPreview(Intent targetIntent) {
        ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_text_area);
        contentPreviewLayout.setVisibility(View.VISIBLE);

        CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
        if (sharingText == null) {
            findViewById(R.id.content_preview_text_layout).setVisibility(View.GONE);
@@ -498,6 +531,105 @@ public class ChooserActivity extends ResolverActivity {
        }
    }

    private void displayImageContentPreview(Intent targetIntent) {
        ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_image_area);
        contentPreviewLayout.setVisibility(View.VISIBLE);

        String action = targetIntent.getAction();
        if (Intent.ACTION_SEND.equals(action)) {
            Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
            loadUriIntoView(R.id.content_preview_image_1_large, uri);
        } else {
            ContentResolver resolver = getContentResolver();

            List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
            List<Uri> imageUris = new ArrayList<>();
            for (Uri uri : uris) {
                if (isImageType(resolver.getType(uri))) {
                    imageUris.add(uri);
                }
            }

            if (imageUris.size() == 0) {
                Log.i(TAG, "Attempted to display image preview area with zero"
                        + " available images detected in EXTRA_STREAM list");
                return;
            }

            loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0));

            if (imageUris.size() == 2) {
                loadUriIntoView(R.id.content_preview_image_2_large, imageUris.get(1));
            } else if (imageUris.size() > 2) {
                loadUriIntoView(R.id.content_preview_image_2_small, imageUris.get(1));
                RoundedRectImageView imageView = loadUriIntoView(
                        R.id.content_preview_image_3_small, imageUris.get(2));

                if (imageUris.size() > 3) {
                    imageView.setExtraImageCount(imageUris.size() - 3);
                }
            }
        }
    }

    private void displayFileContentPreview(Intent targetIntent) {
        // support coming
    }

    private RoundedRectImageView loadUriIntoView(int imageResourceId, Uri uri) {
        RoundedRectImageView imageView = findViewById(imageResourceId);
        imageView.setVisibility(View.VISIBLE);
        Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
        imageView.setImageBitmap(bmp);

        return imageView;
    }

    @VisibleForTesting
    protected boolean isImageType(String mimeType) {
        return mimeType != null && mimeType.startsWith("image/");
    }

    @ContentPreviewType
    private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
        if (uri == null) {
            return CONTENT_PREVIEW_TEXT;
        }

        String mimeType = resolver.getType(uri);
        return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
    }

    /**
     * In {@link android.content.Intent#getType}, the app may specify a very general
     * mime-type that broadly covers all data being shared, such as {@literal *}/*
     * when sending an image and text. We therefore should inspect each item for the
     * the preferred type, in order of IMAGE, FILE, TEXT.
     */
    @ContentPreviewType
    private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
        String action = targetIntent.getAction();
        if (Intent.ACTION_SEND.equals(action)) {
            Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
            return findPreferredContentPreview(uri, resolver);
        } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
            List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
            if (uris == null || uris.isEmpty()) {
                return CONTENT_PREVIEW_TEXT;
            }

            for (Uri uri : uris) {
                if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_IMAGE) {
                    return CONTENT_PREVIEW_IMAGE;
                }
            }

            return CONTENT_PREVIEW_FILE;
        }

        return CONTENT_PREVIEW_TEXT;
    }

    static SharedPreferences getPinnedSharedPrefs(Context context) {
        // The code below is because in the android:ui process, no one can hear you scream.
        // The package info in the context isn't initialized in the way it is for normal apps,
@@ -1114,7 +1246,8 @@ public class ChooserActivity extends ResolverActivity {
        }

        try {
            return getContentResolver().loadThumbnail(uri, size, null);
            return ImageUtils.decodeSampledBitmapFromStream(getContentResolver(),
                uri, size.getWidth(), size.getHeight());
        } catch (IOException | NullPointerException ex) {
            Log.w(TAG, "Error loading preview thumbnail for uri: " + uri.toString(), ex);
        }
@@ -2045,6 +2178,9 @@ public class ChooserActivity extends ResolverActivity {
    public static class RoundedRectImageView extends ImageView {
        private int mRadius = 0;
        private Path mPath = new Path();
        private Paint mOverlayPaint = new Paint(0);
        private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        private String mExtraImageCount = null;

        public RoundedRectImageView(Context context) {
            super(context);
@@ -2062,6 +2198,14 @@ public class ChooserActivity extends ResolverActivity {
                int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);

            mOverlayPaint.setColor(0x99000000);
            mOverlayPaint.setStyle(Paint.Style.FILL);

            mTextPaint.setColor(Color.WHITE);
            mTextPaint.setTextSize(context.getResources()
                    .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
            mTextPaint.setTextAlign(Paint.Align.CENTER);
        }

        private void updatePath(int width, int height) {
@@ -2083,12 +2227,24 @@ public class ChooserActivity extends ResolverActivity {
            updatePath(getWidth(), getHeight());
        }

        /**
          * Display an overlay with extra image count on 3rd image
          */
        public void setExtraImageCount(int count) {
            if (count > 0) {
                this.mExtraImageCount = "+" + count;
            } else {
                this.mExtraImageCount = null;
            }
        }

        @Override
        protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
            super.onSizeChanged(width, height, oldWidth, oldHeight);
            updatePath(width, height);
        }


        @Override
        protected void onDraw(Canvas canvas) {
            if (mRadius != 0) {
@@ -2096,6 +2252,16 @@ public class ChooserActivity extends ResolverActivity {
            }

            super.onDraw(canvas);

            if (mExtraImageCount != null) {
                canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mOverlayPaint);

                int xPos = canvas.getWidth() / 2;
                int yPos = (int) ((canvas.getHeight() / 2.0f)
                        - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));

                canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
            }
        }
    }
}
+58 −1
Original line number Diff line number Diff line
@@ -16,14 +16,20 @@

package com.android.internal.util;

import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;

import java.io.IOException;
import java.io.InputStream;

/**
 * Utility class for image analysis and processing.
@@ -156,4 +162,55 @@ public class ImageUtils {

        return result;
    }

    /**
     * @see https://developer.android.com/topic/performance/graphics/load-bitmap
     */
    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

    /**
     * Load a bitmap, and attempt to downscale to the required size, to save
     * on memory.
     *
     * @see https://developer.android.com/topic/performance/graphics/load-bitmap
     */
    public static Bitmap decodeSampledBitmapFromStream(ContentResolver resolver,
            Uri uri, int reqWidth, int reqHeight) throws IOException {

        final BitmapFactory.Options options = new BitmapFactory.Options();
        try (InputStream is = resolver.openInputStream(uri)) {
            // First decode with inJustDecodeBounds=true to check dimensions
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, null, options);

            options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        }

        // need to do this twice as the InputStream is consumed in the first call,
        // and not all InputStreams support marks
        try (InputStream is = resolver.openInputStream(uri)) {
            options.inJustDecodeBounds = false;
            return BitmapFactory.decodeStream(is, null, options);
        }
    }
}
+76 −7
Original line number Diff line number Diff line
@@ -59,12 +59,84 @@
                  android:layout_centerHorizontal="true"/>
    </RelativeLayout>

    <!-- The following 3 layouts are mutually exclusive. One of them will be
         set VISIBLE programatically, when the optimal preview type can be 
         determined by inspecting the data being shared. This path was chosen
         b/c inflating layouts in code had sizing problems with this widget. -->

    <!-- Layout Option 1: Supporting up to 3 images for preview -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:background="?attr/colorBackgroundFloating">
        <RelativeLayout
            android:id="@+id/content_preview_image_area"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:paddingBottom="@dimen/chooser_view_spacing"
            android:visibility="gone"
            android:background="?attr/colorBackgroundFloating">

            <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
                  android:id="@+id/content_preview_image_1_large"
                  android:visibility="gone"
                  android:layout_width="120dp"
                  android:layout_height="140dp"
                  android:layout_alignParentTop="true"
                  android:adjustViewBounds="true"
                  android:gravity="center"
                  android:scaleType="centerCrop"/>

            <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
                  android:id="@+id/content_preview_image_2_large"
                  android:visibility="gone"
                  android:layout_width="120dp"
                  android:layout_height="140dp"
                  android:layout_alignParentTop="true"
                  android:layout_toRightOf="@id/content_preview_image_1_large"
                  android:layout_marginLeft="10dp"
                  android:adjustViewBounds="true"
                  android:gravity="center"
                  android:scaleType="centerCrop"/>

            <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
                  android:id="@+id/content_preview_image_2_small"
                  android:visibility="gone"
                  android:layout_width="120dp"
                  android:layout_height="65dp"
                  android:layout_alignParentTop="true"
                  android:layout_toRightOf="@id/content_preview_image_1_large"
                  android:layout_marginLeft="10dp"
                  android:adjustViewBounds="true"
                  android:gravity="center"
                  android:scaleType="centerCrop"/>

            <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
                  android:id="@+id/content_preview_image_3_small"
                  android:visibility="gone"
                  android:layout_width="120dp"
                  android:layout_height="65dp"
                  android:layout_below="@id/content_preview_image_2_small"
                  android:layout_toRightOf="@id/content_preview_image_1_large"
                  android:layout_marginLeft="10dp"
                  android:layout_marginTop="10dp"
                  android:adjustViewBounds="true"
                  android:gravity="center"
                  android:scaleType="centerCrop"/>

        </RelativeLayout>
    </LinearLayout>

    <!-- Layout Option 2: Text preview, with optional title and thumbnail -->
    <LinearLayout
        android:id="@+id/content_preview"
        android:id="@+id/content_preview_text_area"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/chooser_view_spacing"
        android:visibility="gone"
        android:background="?attr/colorBackgroundFloating">

        <LinearLayout
@@ -108,15 +180,12 @@

            <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
                  android:id="@+id/content_preview_thumbnail"
                  android:layout_width="80dp"
                  android:layout_height="80dp"
                  android:layout_marginRight="12dp"
                  android:layout_width="75dp"
                  android:layout_height="75dp"
                  android:layout_marginRight="16dp"
                  android:adjustViewBounds="true"
                  android:layout_gravity="center_vertical"
                  android:gravity="center"
                  android:maxWidth="70dp"
                  android:maxHeight="70dp"
                  android:padding="5dp"
                  android:scaleType="centerCrop"/>

            <TextView
+1 −0
Original line number Diff line number Diff line
@@ -720,4 +720,5 @@
    <dimen name="chooser_view_spacing">18dp</dimen>
    <dimen name="chooser_edge_margin_thin">16dp</dimen>
    <dimen name="chooser_edge_margin_normal">24dp</dimen>
    <dimen name="chooser_preview_image_font_size">20sp</dimen>
</resources>
+7 −1
Original line number Diff line number Diff line
@@ -50,9 +50,14 @@
  <java-symbol type="id" name="characterPicker" />
  <java-symbol type="id" name="clearDefaultHint" />
  <java-symbol type="id" name="contentPanel" />
  <java-symbol type="id" name="content_preview" />
  <java-symbol type="id" name="content_preview_image_area" />
  <java-symbol type="id" name="content_preview_image_1_large" />
  <java-symbol type="id" name="content_preview_image_2_large" />
  <java-symbol type="id" name="content_preview_image_2_small" />
  <java-symbol type="id" name="content_preview_image_3_small" />
  <java-symbol type="id" name="content_preview_thumbnail" />
  <java-symbol type="id" name="content_preview_text" />
  <java-symbol type="id" name="content_preview_text_area" />
  <java-symbol type="id" name="content_preview_text_layout" />
  <java-symbol type="id" name="content_preview_title" />
  <java-symbol type="id" name="content_preview_title_layout" />
@@ -2731,6 +2736,7 @@
  <java-symbol type="dimen" name="chooser_view_spacing" />
  <java-symbol type="dimen" name="chooser_edge_margin_thin" />
  <java-symbol type="dimen" name="chooser_edge_margin_normal" />
  <java-symbol type="dimen" name="chooser_preview_image_font_size"/>
  <java-symbol type="layout" name="chooser_grid" />
  <java-symbol type="layout" name="resolve_grid_item" />
  <java-symbol type="id" name="day_picker_view_pager" />
Loading