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

Commit 7fbe02a1 authored by Matt Casey's avatar Matt Casey Committed by Android (Google) Code Review
Browse files

Merge "Image state restoration for LongScreenshotActivity" into sc-dev

parents ee971e56 2bb3ca0c
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -338,6 +338,7 @@
        <activity android:name=".screenshot.LongScreenshotActivity"
        <activity android:name=".screenshot.LongScreenshotActivity"
                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
                  android:process=":screenshot"
                  android:process=":screenshot"
                  android:exported="false"
                  android:finishOnTaskLaunch="true" />
                  android:finishOnTaskLaunch="true" />


        <activity android:name=".screenrecord.ScreenRecordDialog"
        <activity android:name=".screenrecord.ScreenRecordDialog"
+19 −0
Original line number Original line Diff line number Diff line
@@ -139,6 +139,25 @@ public class CropView extends View {
        return super.onTouchEvent(event);
        return super.onTouchEvent(event);
    }
    }


    /**
     * Set the given boundary to the given value without animation.
     */
    public void setBoundaryTo(CropBoundary boundary, float value) {
        switch (boundary) {
            case TOP:
                mTopCrop = value;
                break;
            case BOTTOM:
                mBottomCrop = value;
                break;
            case NONE:
                Log.w(TAG, "No boundary selected for animation");
                break;
        }

        invalidate();
    }

    /**
    /**
     * Animate the given boundary to the given value.
     * Animate the given boundary to the given value.
     */
     */
+158 −39
Original line number Original line Diff line number Diff line
@@ -20,8 +20,17 @@ import android.app.Activity;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.HardwareRenderer;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.Uri;
import android.os.Bundle;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;
@@ -33,6 +42,14 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.Main;


import com.google.common.util.concurrent.ListenableFuture;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;


import javax.inject.Inject;
import javax.inject.Inject;
@@ -44,9 +61,21 @@ import javax.inject.Inject;
public class LongScreenshotActivity extends Activity {
public class LongScreenshotActivity extends Activity {
    private static final String TAG = "LongScreenshotActivity";
    private static final String TAG = "LongScreenshotActivity";


    private static final String IMAGE_PATH_KEY = "saved-image";
    private static final String TOP_BOUNDARY_KEY = "top-boundary";
    private static final String BOTTOM_BOUNDARY_KEY = "bottom-boundary";

    private final UiEventLogger mUiEventLogger;
    private final UiEventLogger mUiEventLogger;
    private final ScrollCaptureController mScrollCaptureController;
    private final ScrollCaptureController mScrollCaptureController;
    private final ScrollCaptureClient.Connection mConnection;
    private final ScrollCaptureClient.Connection mConnection;
    private final Executor mUiExecutor;
    private final Executor mBackgroundExecutor;
    private final ImageExporter mImageExporter;

    private String mSavedImagePath;
    // If true, the activity is re-loading an image from storage, which should either succeed and
    // populate the UI or fail and finish the activity.
    private boolean mRestoringInstance;


    private ImageView mPreview;
    private ImageView mPreview;
    private View mSave;
    private View mSave;
@@ -69,6 +98,9 @@ public class LongScreenshotActivity extends Activity {
            @Background Executor bgExecutor,
            @Background Executor bgExecutor,
            Context context) {
            Context context) {
        mUiEventLogger = uiEventLogger;
        mUiEventLogger = uiEventLogger;
        mUiExecutor = mainExecutor;
        mBackgroundExecutor = bgExecutor;
        mImageExporter = imageExporter;


        mScrollCaptureController = new ScrollCaptureController(context, mainExecutor, bgExecutor,
        mScrollCaptureController = new ScrollCaptureController(context, mainExecutor, bgExecutor,
                imageExporter);
                imageExporter);
@@ -95,12 +127,42 @@ public class LongScreenshotActivity extends Activity {
        mCancel.setOnClickListener(this::onClicked);
        mCancel.setOnClickListener(this::onClicked);
        mEdit.setOnClickListener(this::onClicked);
        mEdit.setOnClickListener(this::onClicked);
        mShare.setOnClickListener(this::onClicked);
        mShare.setOnClickListener(this::onClicked);

        if (savedInstanceState != null) {
            String imagePath = savedInstanceState.getString(IMAGE_PATH_KEY);
            if (!TextUtils.isEmpty(imagePath)) {
                mRestoringInstance = true;
                mBackgroundExecutor.execute(() -> {
                    Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
                    if (bitmap == null) {
                        Log.e(TAG, "Failed to read bitmap from " + imagePath);
                        finishAndRemoveTask();
                    } else {
                        runOnUiThread(() -> {
                            BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
                            mPreview.setImageDrawable(drawable);
                            mMagnifierView.setDrawable(drawable, bitmap.getWidth(),
                                    bitmap.getHeight());

                            mCropView.setBoundaryTo(CropView.CropBoundary.TOP,
                                    savedInstanceState.getFloat(TOP_BOUNDARY_KEY, 0f));
                            mCropView.setBoundaryTo(CropView.CropBoundary.BOTTOM,
                                    savedInstanceState.getFloat(BOTTOM_BOUNDARY_KEY, 1f));
                            mRestoringInstance = false;
                            // Reuse the same path for subsequent restoration.
                            mSavedImagePath = imagePath;
                            Log.d(TAG, "Loaded bitmap from " + imagePath);
                        });
                    }
                });
            }
        }
    }
    }


    @Override
    @Override
    public void onStart() {
    public void onStart() {
        super.onStart();
        super.onStart();
        if (mPreview.getDrawable() == null) {
        if (mPreview.getDrawable() == null && !mRestoringInstance) {
            if (mConnection == null) {
            if (mConnection == null) {
                Log.e(TAG, "Failed to get scroll capture connection, bailing out");
                Log.e(TAG, "Failed to get scroll capture connection, bailing out");
                finishAndRemoveTask();
                finishAndRemoveTask();
@@ -110,6 +172,24 @@ public class LongScreenshotActivity extends Activity {
        }
        }
    }
    }


    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(IMAGE_PATH_KEY, mSavedImagePath);
        outState.putFloat(TOP_BOUNDARY_KEY, mCropView.getTopBoundary());
        outState.putFloat(BOTTOM_BOUNDARY_KEY, mCropView.getBottomBoundary());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isFinishing() && !TextUtils.isEmpty(mSavedImagePath)) {
            Log.d(TAG, "Deleting " + mSavedImagePath);
            File file = new File(mSavedImagePath);
            file.delete();
        }
    }

    private void setButtonsEnabled(boolean enabled) {
    private void setButtonsEnabled(boolean enabled) {
        mSave.setEnabled(enabled);
        mSave.setEnabled(enabled);
        mCancel.setEnabled(enabled);
        mCancel.setEnabled(enabled);
@@ -161,31 +241,68 @@ public class LongScreenshotActivity extends Activity {
    }
    }


    private void startExport(PendingAction action) {
    private void startExport(PendingAction action) {
        mScrollCaptureController.startExport(mCropView.getTopBoundary(),
        Drawable drawable = mPreview.getDrawable();
                mCropView.getBottomBoundary(), new ScrollCaptureController.ExportCallback() {
                    @Override
                    public void onError() {
                        Log.e(TAG, "Error exporting image data.");
                        setButtonsEnabled(true);
                    }


                    @Override
        Rect croppedPortion = new Rect(
                    public void onExportComplete(Uri outputUri) {
                0,
                (int) (drawable.getIntrinsicHeight() * mCropView.getTopBoundary()),
                drawable.getIntrinsicWidth(),
                (int) (drawable.getIntrinsicHeight() * mCropView.getBottomBoundary()));
        ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
                mBackgroundExecutor, UUID.randomUUID(), getBitmap(croppedPortion, drawable),
                ZonedDateTime.now());
        exportFuture.addListener(() -> {
            try {
                ImageExporter.Result result = exportFuture.get();
                setButtonsEnabled(true);
                setButtonsEnabled(true);
                switch (action) {
                switch (action) {
                    case EDIT:
                    case EDIT:
                                doEdit(outputUri);
                        doEdit(result.uri);
                        break;
                        break;
                    case SHARE:
                    case SHARE:
                                doShare(outputUri);
                        doShare(result.uri);
                        break;
                        break;
                    case SAVE:
                    case SAVE:
                        // Nothing more to do
                        // Nothing more to do
                        finishAndRemoveTask();
                        finishAndRemoveTask();
                        break;
                        break;
                }
                }
            } catch (InterruptedException | ExecutionException e) {
                Log.e(TAG, "failed to export", e);
                setButtonsEnabled(true);
            }
        }, mUiExecutor);
    }

    private Bitmap getBitmap(Rect bounds, Drawable drawable) {
        final RenderNode output = new RenderNode("Bitmap Export");
        output.setPosition(0, 0, bounds.width(), bounds.height());
        RecordingCanvas canvas = output.beginRecording();
        // Translating the canvas instead of setting drawable bounds since the drawable is still
        // used in the preview.
        canvas.translate(0, -bounds.top);
        drawable.draw(canvas);
        output.endRecording();
        return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
    }

    private void saveCacheBitmap(ImageTileSet tileSet) {
        long startTime = SystemClock.uptimeMillis();
        Bitmap bitmap = tileSet.toBitmap();
        // TODO(b/181562529) Remove this
        mPreview.setImageDrawable(tileSet.getDrawable());
        try {
            File file = File.createTempFile("long_screenshot", ".png", null);
            FileOutputStream stream = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
            stream.flush();
            stream.close();
            mSavedImagePath = file.getAbsolutePath();
            Log.d(TAG, "Saved to " + file.getAbsolutePath() + " in "
                    + (SystemClock.uptimeMillis() - startTime) + "ms");
        } catch (IOException e) {
            Log.e(TAG, "Failed to save bitmap", e);
        }
        }
                });
    }
    }


    private void doCapture() {
    private void doCapture() {
@@ -193,7 +310,7 @@ public class LongScreenshotActivity extends Activity {
                new ScrollCaptureController.ScrollCaptureCallback() {
                new ScrollCaptureController.ScrollCaptureCallback() {
                    @Override
                    @Override
                    public void onError() {
                    public void onError() {
                Log.e(TAG, "Error!");
                        Log.e(TAG, "Error capturing long screenshot!");
                        finishAndRemoveTask();
                        finishAndRemoveTask();
                    }
                    }


@@ -202,8 +319,10 @@ public class LongScreenshotActivity extends Activity {
                        Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x "
                        Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x "
                                + imageTileSet.getHeight());
                                + imageTileSet.getHeight());
                        mPreview.setImageDrawable(imageTileSet.getDrawable());
                        mPreview.setImageDrawable(imageTileSet.getDrawable());
                mMagnifierView.setImageTileset(imageTileSet);
                        mMagnifierView.setDrawable(imageTileSet.getDrawable(),
                                imageTileSet.getWidth(), imageTileSet.getHeight());
                        mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
                        mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
                        mBackgroundExecutor.execute(() -> saveCacheBitmap(imageTileSet));
                    }
                    }
                });
                });
    }
    }
+7 −7
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.systemui.screenshot;
package com.android.systemui.screenshot;


import android.annotation.NonNull;
import android.content.Context;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Canvas;
@@ -77,13 +78,12 @@ public class MagnifierView extends View implements CropView.CropInteractionListe
        mCheckerboardPaint.setColor(Color.GRAY);
        mCheckerboardPaint.setColor(Color.GRAY);
    }
    }


    public void setImageTileset(ImageTileSet tiles) {
    /**
        if (tiles != null) {
     * Set the drawable to be displayed by the magnifier.
            mDrawable = tiles.getDrawable();
     */
            mDrawable.setBounds(0, 0, tiles.getWidth(), tiles.getHeight());
    public void setDrawable(@NonNull Drawable drawable, int width, int height) {
        } else {
        mDrawable = drawable;
            mDrawable = null;
        mDrawable.setBounds(0, 0, width, height);
        }
        invalidate();
        invalidate();
    }
    }


+0 −27
Original line number Original line Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.screenshot;


import android.annotation.UiThread;
import android.annotation.UiThread;
import android.content.Context;
import android.content.Context;
import android.graphics.Rect;
import android.net.Uri;
import android.net.Uri;
import android.provider.Settings;
import android.provider.Settings;
import android.util.Log;
import android.util.Log;
@@ -27,11 +26,8 @@ import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;


import com.google.common.util.concurrent.ListenableFuture;

import java.time.ZonedDateTime;
import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;


/**
/**
@@ -89,29 +85,6 @@ public class ScrollCaptureController {
        connection.start(this::startCapture, maxPages);
        connection.start(this::startCapture, maxPages);
    }
    }


    /**
     * @param topCrop    [0,1) fraction of the top of the image to be cropped out.
     * @param bottomCrop (0, 1] fraction to be cropped out, e.g. 0.7 will crop out the bottom 30%.
     */
    public void startExport(float topCrop, float bottomCrop, ExportCallback callback) {
        Rect croppedPortion = new Rect(
                0,
                (int) (mImageTileSet.getHeight() * topCrop),
                mImageTileSet.getWidth(),
                (int) (mImageTileSet.getHeight() * bottomCrop));
        ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
                mBgExecutor, mRequestId, mImageTileSet.toBitmap(croppedPortion), mCaptureTime);
        exportFuture.addListener(() -> {
            try {
                ImageExporter.Result result = exportFuture.get();
                callback.onExportComplete(result.uri);
            } catch (InterruptedException | ExecutionException e) {
                Log.e(TAG, "failed to export", e);
                callback.onError();
            }
        }, mUiExecutor);
    }

    private void onCaptureResult(CaptureResult result) {
    private void onCaptureResult(CaptureResult result) {
        Log.d(TAG, "onCaptureResult: " + result);
        Log.d(TAG, "onCaptureResult: " + result);
        boolean emptyResult = result.captured.height() == 0;
        boolean emptyResult = result.captured.height() == 0;