Loading core/java/com/android/internal/app/ChooserActivity.java +172 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); Loading Loading @@ -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, Loading Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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) { Loading @@ -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) { Loading @@ -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); } } } } core/java/com/android/internal/util/ImageUtils.java +58 −1 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); } } } core/res/res/layout/chooser_grid.xml +76 −7 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading core/res/res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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> core/res/res/values/symbols.xml +7 −1 Original line number Diff line number Diff line Loading @@ -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" /> Loading Loading @@ -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 Loading
core/java/com/android/internal/app/ChooserActivity.java +172 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); Loading Loading @@ -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, Loading Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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) { Loading @@ -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) { Loading @@ -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); } } } }
core/java/com/android/internal/util/ImageUtils.java +58 −1 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); } } }
core/res/res/layout/chooser_grid.xml +76 −7 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading
core/res/res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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>
core/res/res/values/symbols.xml +7 −1 Original line number Diff line number Diff line Loading @@ -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" /> Loading Loading @@ -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