summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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",