diff options
| author | Matt Pietal <mpietal@google.com> | 2019-02-05 08:07:07 -0500 |
|---|---|---|
| committer | Matt Pietal <mpietal@google.com> | 2019-02-12 07:28:15 -0500 |
| commit | 46d828c99e328e43524e030c19ca4ac64f3f47fc (patch) | |
| tree | 20a9d8104516e2bb0532349460faf9e2dd150485 /core/java | |
| parent | 2148e7f00994e9b6b8cbd2dd89c27b7829196867 (diff) | |
Sharesheet - file preview support
Support sharing 1 or more non-image type files, with potential
for system generated thumbnail image
Bug: 120419296
Test: atest ChooserActivityTest
Change-Id: I17c44435bb0444035e2ec7675cbc367b75cc3a8e
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/com/android/internal/app/ChooserActivity.java | 191 | ||||
| -rw-r--r-- | core/java/com/android/internal/util/ImageUtils.java | 62 |
2 files changed, 171 insertions, 82 deletions
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 119a015cd5ea..8ebcef5133b6 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -46,6 +46,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; +import android.database.Cursor; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -69,6 +70,8 @@ import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import android.provider.DocumentsContract; +import android.provider.OpenableColumns; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import android.service.chooser.IChooserTargetResult; @@ -87,7 +90,6 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.BaseAdapter; -import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; @@ -373,50 +375,6 @@ public class ChooserActivity extends ResolverActivity { super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); - Button copyButton = findViewById(R.id.copy_button); - copyButton.setOnClickListener(view -> { - Intent targetIntent = getTargetIntent(); - if (targetIntent == null) { - finish(); - } else { - final String action = targetIntent.getAction(); - - ClipData clipData = null; - if (Intent.ACTION_SEND.equals(action)) { - String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT); - Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); - - if (extraText != null) { - clipData = ClipData.newPlainText(null, extraText); - } else if (extraStream != null) { - clipData = ClipData.newUri(getContentResolver(), null, extraStream); - } else { - Log.w(TAG, "No data available to copy to clipboard"); - return; - } - } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { - final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra( - Intent.EXTRA_STREAM); - clipData = ClipData.newUri(getContentResolver(), null, streams.get(0)); - for (int i = 1; i < streams.size(); i++) { - clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i))); - } - } else { - // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE - // so warn about unexpected action - Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard"); - return; - } - - ClipboardManager clipboardManager = (ClipboardManager) getSystemService( - Context.CLIPBOARD_SERVICE); - clipboardManager.setPrimaryClip(clipData); - Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show(); - - finish(); - } - }); - mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; @@ -474,6 +432,10 @@ public class ChooserActivity extends ResolverActivity { return; } + if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) { + return; + } + int previewType = findPreferredContentPreview(targetIntent, getContentResolver()); getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW) @@ -481,6 +443,49 @@ public class ChooserActivity extends ResolverActivity { displayContentPreview(previewType, targetIntent); } + private void onCopyButtonClicked(View v) { + Intent targetIntent = getTargetIntent(); + if (targetIntent == null) { + finish(); + } else { + final String action = targetIntent.getAction(); + + ClipData clipData = null; + if (Intent.ACTION_SEND.equals(action)) { + String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT); + Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); + + if (extraText != null) { + clipData = ClipData.newPlainText(null, extraText); + } else if (extraStream != null) { + clipData = ClipData.newUri(getContentResolver(), null, extraStream); + } else { + Log.w(TAG, "No data available to copy to clipboard"); + return; + } + } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { + final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra( + Intent.EXTRA_STREAM); + clipData = ClipData.newUri(getContentResolver(), null, streams.get(0)); + for (int i = 1; i < streams.size(); i++) { + clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i))); + } + } else { + // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE + // so warn about unexpected action + Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard"); + return; + } + + ClipboardManager clipboardManager = (ClipboardManager) getSystemService( + Context.CLIPBOARD_SERVICE); + clipboardManager.setPrimaryClip(clipData); + Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show(); + + finish(); + } + } + private void displayContentPreview(@ContentPreviewType int previewType, Intent targetIntent) { switch (previewType) { case CONTENT_PREVIEW_TEXT: @@ -501,6 +506,8 @@ public class ChooserActivity extends ResolverActivity { ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_text_area); contentPreviewLayout.setVisibility(View.VISIBLE); + findViewById(R.id.copy_button).setOnClickListener(this::onCopyButtonClicked); + CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); if (sharingText == null) { findViewById(R.id.content_preview_text_layout).setVisibility(View.GONE); @@ -510,7 +517,7 @@ public class ChooserActivity extends ResolverActivity { } String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE); - if (previewTitle == null || previewTitle.trim().isEmpty()) { + if (TextUtils.isEmpty(previewTitle)) { findViewById(R.id.content_preview_title_layout).setVisibility(View.GONE); } else { TextView previewTitleView = findViewById(R.id.content_preview_title); @@ -561,6 +568,7 @@ public class ChooserActivity extends ResolverActivity { if (imageUris.size() == 0) { Log.i(TAG, "Attempted to display image preview area with zero" + " available images detected in EXTRA_STREAM list"); + contentPreviewLayout.setVisibility(View.GONE); return; } @@ -580,15 +588,95 @@ public class ChooserActivity extends ResolverActivity { } } + private static class FileInfo { + public final String name; + public final boolean hasThumbnail; + + FileInfo(String name, boolean hasThumbnail) { + this.name = name; + this.hasThumbnail = hasThumbnail; + } + } + + private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) { + String fileName = null; + boolean hasThumbnail = false; + Cursor cursor = resolver.query(uri, null, null, null, null); + if (cursor != null && cursor.getCount() > 0) { + int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS); + + cursor.moveToFirst(); + fileName = cursor.getString(nameIndex); + if (flagsIndex != -1) { + hasThumbnail = (cursor.getInt(flagsIndex) + & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0; + } + } + + if (TextUtils.isEmpty(fileName)) { + fileName = uri.getPath(); + int index = fileName.lastIndexOf('/'); + if (index != -1) { + fileName = fileName.substring(index + 1); + } + } + + return new FileInfo(fileName, hasThumbnail); + } + private void displayFileContentPreview(Intent targetIntent) { - // support coming + ViewGroup contentPreviewLayout = findViewById(R.id.content_preview_file_area); + contentPreviewLayout.setVisibility(View.VISIBLE); + + // TODO(b/120417119): Disable file copy until after moving to sysui, + // due to permissions issues + findViewById(R.id.file_copy_button).setVisibility(View.GONE); + + ContentResolver resolver = getContentResolver(); + TextView fileNameView = findViewById(R.id.content_preview_filename); + String action = targetIntent.getAction(); + if (Intent.ACTION_SEND.equals(action)) { + Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); + + FileInfo fileInfo = extractFileInfo(uri, resolver); + fileNameView.setText(fileInfo.name); + + if (fileInfo.hasThumbnail) { + loadUriIntoView(R.id.content_preview_file_thumbnail, uri); + } else { + ImageView fileIconView = findViewById(R.id.content_preview_file_icon); + fileIconView.setVisibility(View.VISIBLE); + fileIconView.setImageResource(R.drawable.ic_doc_generic); + } + } else { + List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + if (uris.size() == 0) { + contentPreviewLayout.setVisibility(View.GONE); + Log.i(TAG, + "Appears to be no uris available in EXTRA_STREAM, removing preview area"); + return; + } + + FileInfo fileInfo = extractFileInfo(uris.get(0), resolver); + int remFileCount = uris.size() - 1; + String fileName = getResources().getQuantityString(R.plurals.file_count, + remFileCount, fileInfo.name, remFileCount); + + fileNameView.setText(fileName); + ImageView fileIconView = findViewById(R.id.content_preview_file_icon); + fileIconView.setVisibility(View.VISIBLE); + fileIconView.setImageResource(R.drawable.ic_file_copy); + } } 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); + if (bmp != null) { + imageView.setVisibility(View.VISIBLE); + imageView.setImageBitmap(bmp); + } return imageView; } @@ -1261,9 +1349,8 @@ public class ChooserActivity extends ResolverActivity { } try { - return ImageUtils.decodeSampledBitmapFromStream(getContentResolver(), - uri, size.getWidth(), size.getHeight()); - } catch (IOException | NullPointerException ex) { + return ImageUtils.loadThumbnail(getContentResolver(), uri, size); + } catch (IOException | NullPointerException | SecurityException ex) { Log.w(TAG, "Error loading preview thumbnail for uri: " + uri.toString(), ex); } return null; diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java index 195ae52ce977..274a5136d8e9 100644 --- a/core/java/com/android/internal/util/ImageUtils.java +++ b/core/java/com/android/internal/util/ImageUtils.java @@ -16,20 +16,25 @@ package com.android.internal.util; +import android.content.ContentProviderClient; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.ImageDecoder; +import android.graphics.ImageDecoder.ImageInfo; +import android.graphics.ImageDecoder.Source; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Bundle; +import android.util.Size; import java.io.IOException; -import java.io.InputStream; /** * Utility class for image analysis and processing. @@ -166,21 +171,18 @@ public class ImageUtils { /** * @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; + public static int calculateSampleSize(Size currentSize, Size requestedSize) { int inSampleSize = 1; - if (height > reqHeight || width > reqWidth) { - final int halfHeight = height / 2; - final int halfWidth = width / 2; + if (currentSize.getHeight() > requestedSize.getHeight() + || currentSize.getWidth() > requestedSize.getWidth()) { + final int halfHeight = currentSize.getHeight() / 2; + final int halfWidth = currentSize.getWidth() / 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) { + while ((halfHeight / inSampleSize) >= requestedSize.getHeight() + && (halfWidth / inSampleSize) >= requestedSize.getWidth()) { inSampleSize *= 2; } } @@ -190,27 +192,27 @@ public class ImageUtils { /** * Load a bitmap, and attempt to downscale to the required size, to save - * on memory. + * on memory. Updated to use newer and more compatible ImageDecoder. * * @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); + public static Bitmap loadThumbnail(ContentResolver resolver, Uri uri, Size size) + throws IOException { + + try (ContentProviderClient client = resolver.acquireContentProviderClient(uri)) { + final Bundle opts = new Bundle(); + opts.putParcelable(ContentResolver.EXTRA_SIZE, Point.convert(size)); + + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { + return client.openTypedAssetFile(uri, "image/*", opts, null); + }), (ImageDecoder decoder, ImageInfo info, Source source) -> { + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + + final int sample = calculateSampleSize(info.getSize(), size); + if (sample > 1) { + decoder.setTargetSampleSize(sample); + } + }); } } } |
