diff options
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java | 222 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java | 94 |
2 files changed, 239 insertions, 77 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index 8fc2830aa422..bc8adc9dad5b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -41,6 +41,7 @@ import com.google.common.util.concurrent.ListenableFuture; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.time.Duration; @@ -110,6 +111,39 @@ class ImageExporter { } /** + * Stores the given Bitmap to a temp file. + */ + ListenableFuture<File> exportAsTempFile(Executor executor, Bitmap bitmap) { + return CallbackToFutureAdapter.getFuture( + (completer) -> { + executor.execute(() -> { + File cachePath; + try { + cachePath = File.createTempFile("long_screenshot_cache_", ".tmp"); + try (FileOutputStream stream = new FileOutputStream(cachePath)) { + bitmap.compress(mCompressFormat, mQuality, stream); + } catch (IOException e) { + if (cachePath.exists()) { + //noinspection ResultOfMethodCallIgnored + cachePath.delete(); + cachePath = null; + } + completer.setException(e); + } + if (cachePath != null) { + completer.set(cachePath); + } + } catch (IOException e) { + // Failed to create a new file + completer.setException(e); + } + }); + return "Bitmap#compress"; + } + ); + } + + /** * Export the image using the given executor. * * @param executor the thread for execution @@ -122,7 +156,7 @@ class ImageExporter { } /** - * Export the image using the given executor. + * Export the image to MediaStore and publish. * * @param executor the thread for execution * @param bitmap the bitmap to export @@ -131,8 +165,10 @@ class ImageExporter { */ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime) { - final Task task = - new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality); + + final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, + mQuality, /* publish */ true); + return CallbackToFutureAdapter.getFuture( (completer) -> { executor.execute(() -> { @@ -147,12 +183,36 @@ class ImageExporter { ); } + /** + * Delete the entry. + * + * @param executor the thread for execution + * @param uri the uri of the image to publish + * + * @return a listenable future result + */ + ListenableFuture<Result> delete(Executor executor, Uri uri) { + return CallbackToFutureAdapter.getFuture((completer) -> { + executor.execute(() -> { + mResolver.delete(uri, null); + + Result result = new Result(); + result.uri = uri; + result.deleted = true; + completer.set(result); + }); + return "ContentResolver#delete"; + }); + } + static class Result { + Uri uri; UUID requestId; String fileName; long timestamp; - Uri uri; CompressFormat format; + boolean published; + boolean deleted; } private static class Task { @@ -163,9 +223,10 @@ class ImageExporter { private final CompressFormat mFormat; private final int mQuality; private final String mFileName; + private final boolean mPublish; Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, - CompressFormat format, int quality) { + CompressFormat format, int quality, boolean publish) { mResolver = resolver; mRequestId = requestId; mBitmap = bitmap; @@ -173,6 +234,7 @@ class ImageExporter { mFormat = format; mQuality = quality; mFileName = createFilename(mCaptureTime, mFormat); + mPublish = publish; } public Result execute() throws ImageExportException, InterruptedException { @@ -186,16 +248,21 @@ class ImageExporter { start = Instant.now(); } - uri = createEntry(mFormat, mCaptureTime, mFileName); + uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName); throwIfInterrupted(); - writeImage(mBitmap, mFormat, mQuality, uri); + writeImage(mResolver, mBitmap, mFormat, mQuality, uri); throwIfInterrupted(); - writeExif(uri, mRequestId, mBitmap.getWidth(), mBitmap.getHeight(), mCaptureTime); + int width = mBitmap.getWidth(); + int height = mBitmap.getHeight(); + writeExif(mResolver, uri, mRequestId, width, height, mCaptureTime); throwIfInterrupted(); - publishEntry(uri); + if (mPublish) { + publishEntry(mResolver, uri); + result.published = true; + } result.timestamp = mCaptureTime.toInstant().toEpochMilli(); result.requestId = mRequestId; @@ -218,88 +285,89 @@ class ImageExporter { return result; } - Uri createEntry(CompressFormat format, ZonedDateTime time, String fileName) - throws ImageExportException { - Trace.beginSection("ImageExporter_createEntry"); - try { - final ContentValues values = createMetadata(time, format, fileName); + @Override + public String toString() { + return "export [" + mBitmap + "] to [" + mFormat + "] at quality " + mQuality; + } + } - Uri uri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - if (uri == null) { - throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); - } - return uri; - } finally { - Trace.endSection(); + private static Uri createEntry(ContentResolver resolver, CompressFormat format, + ZonedDateTime time, String fileName) throws ImageExportException { + Trace.beginSection("ImageExporter_createEntry"); + try { + final ContentValues values = createMetadata(time, format, fileName); + + Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + if (uri == null) { + throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); } + return uri; + } finally { + Trace.endSection(); } + } - void writeImage(Bitmap bitmap, CompressFormat format, int quality, - Uri contentUri) throws ImageExportException { - Trace.beginSection("ImageExporter_writeImage"); - try (OutputStream out = mResolver.openOutputStream(contentUri)) { - long start = SystemClock.elapsedRealtime(); - if (!bitmap.compress(format, quality, out)) { - throw new ImageExportException(IMAGE_COMPRESS_RETURNED_FALSE); - } else if (LogConfig.DEBUG_STORAGE) { - Log.d(TAG, "Bitmap.compress took " - + (SystemClock.elapsedRealtime() - start) + " ms"); - } - } catch (IOException ex) { - throw new ImageExportException(OPEN_OUTPUT_STREAM_EXCEPTION, ex); - } finally { - Trace.endSection(); + private static void writeImage(ContentResolver resolver, Bitmap bitmap, CompressFormat format, + int quality, Uri contentUri) throws ImageExportException { + Trace.beginSection("ImageExporter_writeImage"); + try (OutputStream out = resolver.openOutputStream(contentUri)) { + long start = SystemClock.elapsedRealtime(); + if (!bitmap.compress(format, quality, out)) { + throw new ImageExportException(IMAGE_COMPRESS_RETURNED_FALSE); + } else if (LogConfig.DEBUG_STORAGE) { + Log.d(TAG, "Bitmap.compress took " + + (SystemClock.elapsedRealtime() - start) + " ms"); } + } catch (IOException ex) { + throw new ImageExportException(OPEN_OUTPUT_STREAM_EXCEPTION, ex); + } finally { + Trace.endSection(); } + } - void writeExif(Uri uri, UUID requestId, int width, int height, ZonedDateTime captureTime) - throws ImageExportException { - Trace.beginSection("ImageExporter_writeExif"); - ParcelFileDescriptor pfd = null; + private static void writeExif(ContentResolver resolver, Uri uri, UUID requestId, int width, + int height, ZonedDateTime captureTime) throws ImageExportException { + Trace.beginSection("ImageExporter_writeExif"); + ParcelFileDescriptor pfd = null; + try { + pfd = resolver.openFile(uri, "rw", null); + if (pfd == null) { + throw new ImageExportException(RESOLVER_OPEN_FILE_RETURNED_NULL); + } + ExifInterface exif; try { - pfd = mResolver.openFile(uri, "rw", null); - if (pfd == null) { - throw new ImageExportException(RESOLVER_OPEN_FILE_RETURNED_NULL); - } - ExifInterface exif; - try { - exif = new ExifInterface(pfd.getFileDescriptor()); - } catch (IOException e) { - throw new ImageExportException(EXIF_READ_EXCEPTION, e); - } - - updateExifAttributes(exif, requestId, width, height, captureTime); - try { - exif.saveAttributes(); - } catch (IOException e) { - throw new ImageExportException(EXIF_WRITE_EXCEPTION, e); - } - } catch (FileNotFoundException e) { - throw new ImageExportException(RESOLVER_OPEN_FILE_EXCEPTION, e); - } finally { - closeQuietly(pfd); - Trace.endSection(); + exif = new ExifInterface(pfd.getFileDescriptor()); + } catch (IOException e) { + throw new ImageExportException(EXIF_READ_EXCEPTION, e); } - } - void publishEntry(Uri uri) throws ImageExportException { - Trace.beginSection("ImageExporter_publishEntry"); + updateExifAttributes(exif, requestId, width, height, captureTime); try { - ContentValues values = new ContentValues(); - values.put(MediaStore.MediaColumns.IS_PENDING, 0); - values.putNull(MediaStore.MediaColumns.DATE_EXPIRES); - final int rowsUpdated = mResolver.update(uri, values, /* extras */ null); - if (rowsUpdated < 1) { - throw new ImageExportException(RESOLVER_UPDATE_ZERO_ROWS); - } - } finally { - Trace.endSection(); + exif.saveAttributes(); + } catch (IOException e) { + throw new ImageExportException(EXIF_WRITE_EXCEPTION, e); } + } catch (FileNotFoundException e) { + throw new ImageExportException(RESOLVER_OPEN_FILE_EXCEPTION, e); + } finally { + closeQuietly(pfd); + Trace.endSection(); } + } - @Override - public String toString() { - return "compress [" + mBitmap + "] to [" + mFormat + "] at quality " + mQuality; + private static void publishEntry(ContentResolver resolver, Uri uri) + throws ImageExportException { + Trace.beginSection("ImageExporter_publishEntry"); + try { + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.IS_PENDING, 0); + values.putNull(MediaStore.MediaColumns.DATE_EXPIRES); + final int rowsUpdated = resolver.update(uri, values, /* extras */ null); + if (rowsUpdated < 1) { + throw new ImageExportException(RESOLVER_UPDATE_ZERO_ROWS); + } + } finally { + Trace.endSection(); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java new file mode 100644 index 000000000000..988b93c8ca59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.ParcelFileDescriptor; + +import androidx.concurrent.futures.CallbackToFutureAdapter; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.inject.Inject; + +/** Loads images. */ +public class ImageLoader { + private final ContentResolver mResolver; + + static class Result { + @Nullable Uri uri; + @Nullable File fileName; + @Nullable Bitmap bitmap; + } + + @Inject + ImageLoader(ContentResolver resolver) { + mResolver = resolver; + } + + /** + * Loads an image via URI from ContentResolver. + * + * @param uri the identifier of the image to load + * @return a listenable future result + */ + ListenableFuture<Result> load(Uri uri) { + return CallbackToFutureAdapter.getFuture(completer -> { + Result result = new Result(); + try (InputStream in = mResolver.openInputStream(uri)) { + result.uri = uri; + result.bitmap = BitmapFactory.decodeStream(in); + completer.set(result); + } + catch (IOException e) { + completer.setException(e); + } + return "BitmapFactory#decodeStream"; + }); + } + + /** + * Loads an image by physical filesystem name. The current user must have filesystem + * permissions to read this file/path. + * + * @param file the system file path of the image to load + * @return a listenable future result + */ + ListenableFuture<Result> load(File file) { + return CallbackToFutureAdapter.getFuture(completer -> { + try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { + Result result = new Result(); + result.fileName = file; + result.bitmap = BitmapFactory.decodeStream(in); + completer.set(result); + } catch (IOException e) { + completer.setException(e); + } + return "BitmapFactory#decodeStream"; + }); + } +} |