summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mark Renouf <mrenouf@google.com> 2021-01-20 10:15:30 -0500
committer Mark Renouf <mrenouf@google.com> 2021-01-20 14:04:15 -0500
commit3ae3b8367d9a181872b5f7aeecbf288c28c658ab (patch)
tree6959256e7479e03a606dceab1cbef504b81ed01e
parentee605b646562fa32cccceb18c610fc8c37df1ed7 (diff)
Return more information from ImageExporter
Adds filename, compress format and timestamp This eliminates additional duplicated code between SaveImageInBackgroundTask and ImageExporer, related to determining the filename and the capture time. Bug: 177996487 Test: atest ImageExporterTest Change-Id: I794727f8c67bc6b39de81978df0482de67894616
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java26
4 files changed, 95 insertions, 44 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index f77431a7047e..87e236ceb894 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -35,6 +35,8 @@ import android.util.Log;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.exifinterface.media.ExifInterface;
+import com.android.internal.annotations.VisibleForTesting;
+
import com.google.common.util.concurrent.ListenableFuture;
import java.io.File;
@@ -114,8 +116,8 @@ class ImageExporter {
*
* @return a listenable future result
*/
- ListenableFuture<Uri> export(Executor executor, Bitmap bitmap) {
- return export(executor, bitmap, ZonedDateTime.now());
+ ListenableFuture<Result> export(Executor executor, String requestId, Bitmap bitmap) {
+ return export(executor, requestId, bitmap, ZonedDateTime.now());
}
/**
@@ -126,8 +128,10 @@ class ImageExporter {
*
* @return a listenable future result
*/
- ListenableFuture<Uri> export(Executor executor, Bitmap bitmap, ZonedDateTime captureTime) {
- final Task task = new Task(mResolver, bitmap, captureTime, mCompressFormat, mQuality);
+ ListenableFuture<Result> export(Executor executor, String requestId, Bitmap bitmap,
+ ZonedDateTime captureTime) {
+ final Task task =
+ new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality);
return CallbackToFutureAdapter.getFuture(
(completer) -> {
executor.execute(() -> {
@@ -142,32 +146,46 @@ class ImageExporter {
);
}
+ static class Result {
+ String requestId;
+ String fileName;
+ long timestamp;
+ Uri uri;
+ CompressFormat format;
+ }
+
private static class Task {
private final ContentResolver mResolver;
+ private final String mRequestId;
+ private final Bitmap mBitmap;
private final ZonedDateTime mCaptureTime;
private final CompressFormat mFormat;
private final int mQuality;
- private final Bitmap mBitmap;
+ private final String mFileName;
- Task(ContentResolver resolver, Bitmap bitmap, ZonedDateTime captureTime,
+ Task(ContentResolver resolver, String requestId, Bitmap bitmap, ZonedDateTime captureTime,
CompressFormat format, int quality) {
mResolver = resolver;
+ mRequestId = requestId;
mBitmap = bitmap;
mCaptureTime = captureTime;
mFormat = format;
mQuality = quality;
+ mFileName = createFilename(mCaptureTime, mFormat);
}
- public Uri execute() throws ImageExportException, InterruptedException {
+ public Result execute() throws ImageExportException, InterruptedException {
Trace.beginSection("ImageExporter_execute");
Uri uri = null;
Instant start = null;
+ Result result = new Result();
try {
if (LogConfig.DEBUG_STORAGE) {
Log.d(TAG, "image export started");
start = Instant.now();
}
- uri = createEntry(mFormat, mCaptureTime);
+
+ uri = createEntry(mFormat, mCaptureTime, mFileName);
throwIfInterrupted();
writeImage(mBitmap, mFormat, mQuality, uri);
@@ -178,6 +196,12 @@ class ImageExporter {
publishEntry(uri);
+ result.timestamp = mCaptureTime.toInstant().toEpochMilli();
+ result.requestId = mRequestId;
+ result.uri = uri;
+ result.fileName = mFileName;
+ result.format = mFormat;
+
if (LogConfig.DEBUG_STORAGE) {
Log.d(TAG, "image export completed: "
+ Duration.between(start, Instant.now()).toMillis() + " ms");
@@ -190,13 +214,15 @@ class ImageExporter {
} finally {
Trace.endSection();
}
- return uri;
+ return result;
}
- Uri createEntry(CompressFormat format, ZonedDateTime time) throws ImageExportException {
+ Uri createEntry(CompressFormat format, ZonedDateTime time, String fileName)
+ throws ImageExportException {
Trace.beginSection("ImageExporter_createEntry");
try {
- final ContentValues values = createMetadata(time, format);
+ final ContentValues values = createMetadata(time, format, fileName);
+
Uri uri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri == null) {
throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
@@ -276,14 +302,16 @@ class ImageExporter {
}
}
+ @VisibleForTesting
static String createFilename(ZonedDateTime time, CompressFormat format) {
return String.format(FILENAME_PATTERN, time, fileExtension(format));
}
- static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format) {
+ static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format,
+ String fileName) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.RELATIVE_PATH, SCREENSHOTS_PATH);
- values.put(MediaStore.MediaColumns.DISPLAY_NAME, createFilename(captureTime, format));
+ values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(format));
values.put(MediaStore.MediaColumns.DATE_ADDED, captureTime.toEpochSecond());
values.put(MediaStore.MediaColumns.DATE_MODIFIED, captureTime.toEpochSecond());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index db2750b8842f..d42847d0afb5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -29,6 +29,7 @@ import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
@@ -49,8 +50,9 @@ import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ShareTransition;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -65,7 +67,6 @@ import java.util.function.Supplier;
class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private static final String TAG = logTag(SaveImageInBackgroundTask.class);
- private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
@@ -73,14 +74,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final ScreenshotSmartActions mScreenshotSmartActions;
private final ScreenshotController.SaveImageInBackgroundData mParams;
private final ScreenshotController.SavedImageData mImageData;
- private final String mImageFileName;
- private final long mImageTime;
+
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
- private final String mScreenshotId;
+ private String mScreenshotId;
private final boolean mSmartActionsEnabled;
private final Random mRandom = new Random();
private final Supplier<ShareTransition> mSharedElementTransition;
private final ImageExporter mImageExporter;
+ private long mImageTime;
SaveImageInBackgroundTask(Context context, ImageExporter exporter,
ScreenshotSmartActions screenshotSmartActions,
@@ -94,10 +95,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Prepare all the output metadata
mParams = data;
- mImageTime = System.currentTimeMillis();
- String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
- mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
- mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
// Initialize screenshot notification smart actions provider.
mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -121,18 +118,27 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
}
return null;
}
+ // TODO: move to constructor / from ScreenshotRequest
+ final UUID uuid = UUID.randomUUID();
+ final UserHandle user = getUserHandleOfForegroundApplication(mContext);
+
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
Bitmap image = mParams.image;
-
+ String requestId = uuid.toString();
+ mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, uuid);
try {
// Call synchronously here since already on a background thread.
- Uri uri = mImageExporter.export(Runnable::run, image).get();
+ ListenableFuture<ImageExporter.Result> future =
+ mImageExporter.export(Runnable::run, requestId, image);
+ ImageExporter.Result result = future.get();
+ final Uri uri = result.uri;
+ mImageTime = result.timestamp;
CompletableFuture<List<Notification.Action>> smartActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, uri, image, mSmartActionsProvider,
- mSmartActionsEnabled, getUserHandle(mContext));
+ mSmartActionsEnabled, user);
List<Notification.Action> smartActions = new ArrayList<>();
if (mSmartActionsEnabled) {
@@ -331,22 +337,21 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
return deleteActionBuilder.build();
}
- private int getUserHandleOfForegroundApplication(Context context) {
+ private UserHandle getUserHandleOfForegroundApplication(Context context) {
+ UserManager manager = UserManager.get(context);
+ int result;
// This logic matches
// com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
try {
- return ActivityTaskManager.getService().getLastResumedActivityUserId();
+ result = ActivityTaskManager.getService().getLastResumedActivityUserId();
} catch (RemoteException e) {
if (DEBUG_ACTIONS) {
Log.d(TAG, "Failed to get UserHandle of foreground app: ", e);
}
- return context.getUserId();
+ result = context.getUserId();
}
- }
-
- private UserHandle getUserHandle(Context context) {
- UserManager manager = UserManager.get(context);
- return manager.getUserInfo(getUserHandleOfForegroundApplication(context)).getUserHandle();
+ UserInfo userInfo = manager.getUserInfo(result);
+ return userInfo.getUserHandle();
}
private List<Notification.Action> buildSmartActions(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index c75efbcc5f80..2ccb2ce0ec48 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -29,6 +29,8 @@ import com.android.systemui.screenshot.ScrollCaptureClient.Session;
import com.google.common.util.concurrent.ListenableFuture;
+import java.time.ZonedDateTime;
+import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -52,6 +54,9 @@ public class ScrollCaptureController {
private final ImageExporter mImageExporter;
private final ImageTileSet mImageTileSet;
+ private ZonedDateTime mCaptureTime;
+ private String mRequestId;
+
public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
Executor bgExecutor, ImageExporter exporter) {
mContext = context;
@@ -68,6 +73,8 @@ public class ScrollCaptureController {
* @param after action to take after the flow is complete
*/
public void run(final Runnable after) {
+ mCaptureTime = ZonedDateTime.now();
+ mRequestId = UUID.randomUUID().toString();
mConnection.start((session) -> startCapture(session, after));
}
@@ -109,11 +116,12 @@ public class ScrollCaptureController {
void exportToFile(Bitmap bitmap, Session session, Runnable afterEnd) {
mImageExporter.setFormat(Bitmap.CompressFormat.PNG);
mImageExporter.setQuality(6);
- ListenableFuture<Uri> future =
- mImageExporter.export(mBgExecutor, bitmap);
+ ListenableFuture<ImageExporter.Result> future =
+ mImageExporter.export(mBgExecutor, mRequestId, bitmap, mCaptureTime);
future.addListener(() -> {
try {
- launchViewer(future.get());
+ ImageExporter.Result result = future.get();
+ launchViewer(result.uri);
} catch (InterruptedException | ExecutionException e) {
Toast.makeText(mContext, "Failed to write image", Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error storing screenshot to media store", e.getCause());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index f2bf7aa3d842..c2a08ba54087 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -32,7 +32,6 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
@@ -93,21 +92,31 @@ public class ImageExporterTest extends SysuiTestCase {
ContentResolver contentResolver = context.getContentResolver();
ImageExporter exporter = new ImageExporter(contentResolver);
+ String requestId = "some_random_unique_id";
Bitmap original = createCheckerBitmap(10, 10, 10);
- ListenableFuture<Uri> direct = exporter.export(DIRECT_EXECUTOR, original, CAPTURE_TIME);
+ ListenableFuture<ImageExporter.Result> direct =
+ exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME);
assertTrue("future should be done", direct.isDone());
assertFalse("future should not be canceled", direct.isCancelled());
- Uri result = direct.get();
+ ImageExporter.Result result = direct.get();
+
+ assertEquals("Result should contain the same request id", requestId, result.requestId);
+ assertEquals("Filename should contain the correct filename",
+ "Screenshot_20201215-131500.png", result.fileName);
+ assertNotNull("CompressFormat should be set", result.format);
+ assertEquals("The default CompressFormat should be PNG", CompressFormat.PNG, result.format);
+ assertNotNull("Uri should not be null", result.uri);
+ assertEquals("Timestamp should match input", CAPTURE_TIME.toInstant().toEpochMilli(),
+ result.timestamp);
- assertNotNull("Uri should not be null", result);
Bitmap decoded = null;
- try (InputStream in = contentResolver.openInputStream(result)) {
+ try (InputStream in = contentResolver.openInputStream(result.uri)) {
decoded = BitmapFactory.decodeStream(in);
assertNotNull("decoded image should not be null", decoded);
assertTrue("original and decoded image should be identical", original.sameAs(decoded));
- try (ParcelFileDescriptor pfd = contentResolver.openFile(result, "r", null)) {
+ try (ParcelFileDescriptor pfd = contentResolver.openFile(result.uri, "r", null)) {
assertNotNull(pfd);
ExifInterface exifInterface = new ExifInterface(pfd.getFileDescriptor());
@@ -130,13 +139,14 @@ public class ImageExporterTest extends SysuiTestCase {
if (decoded != null) {
decoded.recycle();
}
- contentResolver.delete(result, null);
+ contentResolver.delete(result.uri, null);
}
}
@Test
public void testMediaStoreMetadata() {
- ContentValues values = ImageExporter.createMetadata(CAPTURE_TIME, CompressFormat.PNG);
+ String name = ImageExporter.createFilename(CAPTURE_TIME, CompressFormat.PNG);
+ ContentValues values = ImageExporter.createMetadata(CAPTURE_TIME, CompressFormat.PNG, name);
assertEquals("Pictures/Screenshots",
values.getAsString(MediaStore.MediaColumns.RELATIVE_PATH));
assertEquals("Screenshot_20201215-131500.png",