Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7bba5072 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

More robust persisting of screenshots.

Start using the newly added APIs for contributing new media.  This
gives us strong lifecycle around the "pending" state of the
screenshot while it's being written, before we eventually publish or
abandon it.

All the previous ImageColumns fields are now filled in
automatically by MediaProvider once the image is persisted.

Bug: 115377970, 118167155
Test: atest android.provider.cts.MediaStorePendingTest
Change-Id: I0bd05336f3a309007c6d0f4fb711c5b467bc18ea
parent 8c33a548
Loading
Loading
Loading
Loading
+24 −41
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -64,7 +63,6 @@ import android.os.UserHandle;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.view.Display;
import android.view.LayoutInflater;
@@ -85,8 +83,9 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.NotificationChannels;

import java.io.File;
import java.io.FileOutputStream;
import libcore.io.IoUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -121,16 +120,13 @@ class SaveImageInBackgroundData {
class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
    private static final String TAG = "SaveImageInBackgroundTask";

    private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
    private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
    private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";

    private final SaveImageInBackgroundData mParams;
    private final NotificationManager mNotificationManager;
    private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
    private final File mScreenshotDir;
    private final String mImageFileName;
    private final String mImageFilePath;
    private final long mImageTime;
    private final BigPictureStyle mNotificationStyle;
    private final int mImageWidth;
@@ -146,10 +142,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
        mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);

        mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);
        mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();

        // Create the large notification icon
        mImageWidth = data.image.getWidth();
        mImageHeight = data.image.getHeight();
@@ -238,7 +230,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
    }

    @Override
    protected Void doInBackground(Void... params) {
    protected Void doInBackground(Void... paramsUnused) {
        if (isCancelled()) {
            return null;
        }
@@ -252,36 +244,27 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
        Resources r = context.getResources();

        try {
            // Create screenshot directory if it doesn't exist
            boolean madeDirs = mScreenshotDir.mkdirs();
            if (madeDirs == false) {
                Log.e(TAG, "Couldn't create screenshot directory: " + mScreenshotDir);
            }

            // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
            // for DATE_TAKEN
            long dateSeconds = mImageTime / 1000;

            // Save
            OutputStream out = new FileOutputStream(mImageFilePath);
            image.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();

            // Save the screenshot to the MediaStore
            ContentValues values = new ContentValues();
            ContentResolver resolver = context.getContentResolver();
            values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
            values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
            values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
            values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
            values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
            values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            final MediaStore.PendingParams params = new MediaStore.PendingParams(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
            params.setPrimaryDirectory(Environment.DIRECTORY_PICTURES);
            params.setSecondaryDirectory(Environment.DIRECTORY_SCREENSHOTS);

            final Uri uri = MediaStore.createPending(context, params);
            final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
            try {
                try (OutputStream out = session.openOutputStream()) {
                    if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
                        throw new IOException("Failed to compress");
                    }
                }
                session.publish();
            } catch (Exception e) {
                session.abandon();
                throw e;
            } finally {
                IoUtils.closeQuietly(session);
            }

            // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
            // order to do some common work like dismissing the keyguard and sending