summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorMatt Pietal <mpietal@google.com>2019-02-05 08:07:07 -0500
committerMatt Pietal <mpietal@google.com>2019-02-12 07:28:15 -0500
commit46d828c99e328e43524e030c19ca4ac64f3f47fc (patch)
tree20a9d8104516e2bb0532349460faf9e2dd150485 /core/java
parent2148e7f00994e9b6b8cbd2dd89c27b7829196867 (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.java191
-rw-r--r--core/java/com/android/internal/util/ImageUtils.java62
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);
+ }
+ });
}
}
}