summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/CropView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java197
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java27
5 files changed, 185 insertions, 73 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index fbe58c505662..58e129f4af7c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -337,6 +337,7 @@
<activity android:name=".screenshot.LongScreenshotActivity"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
android:process=":screenshot"
+ android:exported="false"
android:finishOnTaskLaunch="true" />
<activity android:name=".screenrecord.ScreenRecordDialog"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 53d9f1c08e6f..5b55864eed8a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -140,6 +140,25 @@ public class CropView extends View {
}
/**
+ * 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.
*/
public void animateBoundaryTo(CropBoundary boundary, float value) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 89efda98a5b6..5a13ea55222d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -20,8 +20,17 @@ import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
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.os.Bundle;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
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.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 javax.inject.Inject;
@@ -44,9 +61,21 @@ import javax.inject.Inject;
public class LongScreenshotActivity extends Activity {
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 ScrollCaptureController mScrollCaptureController;
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 View mSave;
@@ -69,6 +98,9 @@ public class LongScreenshotActivity extends Activity {
@Background Executor bgExecutor,
Context context) {
mUiEventLogger = uiEventLogger;
+ mUiExecutor = mainExecutor;
+ mBackgroundExecutor = bgExecutor;
+ mImageExporter = imageExporter;
mScrollCaptureController = new ScrollCaptureController(context, mainExecutor, bgExecutor,
imageExporter);
@@ -95,12 +127,42 @@ public class LongScreenshotActivity extends Activity {
mCancel.setOnClickListener(this::onClicked);
mEdit.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
public void onStart() {
super.onStart();
- if (mPreview.getDrawable() == null) {
+ if (mPreview.getDrawable() == null && !mRestoringInstance) {
if (mConnection == null) {
Log.e(TAG, "Failed to get scroll capture connection, bailing out");
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) {
mSave.setEnabled(enabled);
mCancel.setEnabled(enabled);
@@ -161,50 +241,89 @@ public class LongScreenshotActivity extends Activity {
}
private void startExport(PendingAction action) {
- mScrollCaptureController.startExport(mCropView.getTopBoundary(),
- mCropView.getBottomBoundary(), new ScrollCaptureController.ExportCallback() {
- @Override
- public void onError() {
- Log.e(TAG, "Error exporting image data.");
- setButtonsEnabled(true);
- }
+ Drawable drawable = mPreview.getDrawable();
- @Override
- public void onExportComplete(Uri outputUri) {
- setButtonsEnabled(true);
- switch (action) {
- case EDIT:
- doEdit(outputUri);
- break;
- case SHARE:
- doShare(outputUri);
- break;
- case SAVE:
- // Nothing more to do
- finishAndRemoveTask();
- break;
- }
- }
- });
+ Rect croppedPortion = new Rect(
+ 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);
+ switch (action) {
+ case EDIT:
+ doEdit(result.uri);
+ break;
+ case SHARE:
+ doShare(result.uri);
+ break;
+ case SAVE:
+ // Nothing more to do
+ finishAndRemoveTask();
+ 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() {
mScrollCaptureController.start(mConnection,
new ScrollCaptureController.ScrollCaptureCallback() {
- @Override
- public void onError() {
- Log.e(TAG, "Error!");
- finishAndRemoveTask();
- }
+ @Override
+ public void onError() {
+ Log.e(TAG, "Error capturing long screenshot!");
+ finishAndRemoveTask();
+ }
- @Override
- public void onComplete(ImageTileSet imageTileSet) {
- Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x "
- + imageTileSet.getHeight());
- mPreview.setImageDrawable(imageTileSet.getDrawable());
- mMagnifierView.setImageTileset(imageTileSet);
- mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
- }
- });
+ @Override
+ public void onComplete(ImageTileSet imageTileSet) {
+ Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x "
+ + imageTileSet.getHeight());
+ mPreview.setImageDrawable(imageTileSet.getDrawable());
+ mMagnifierView.setDrawable(imageTileSet.getDrawable(),
+ imageTileSet.getWidth(), imageTileSet.getHeight());
+ mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
+ mBackgroundExecutor.execute(() -> saveCacheBitmap(imageTileSet));
+ }
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index f8f1d3ac9a5b..7a0ec4c520b2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -77,13 +78,12 @@ public class MagnifierView extends View implements CropView.CropInteractionListe
mCheckerboardPaint.setColor(Color.GRAY);
}
- public void setImageTileset(ImageTileSet tiles) {
- if (tiles != null) {
- mDrawable = tiles.getDrawable();
- mDrawable.setBounds(0, 0, tiles.getWidth(), tiles.getHeight());
- } else {
- mDrawable = null;
- }
+ /**
+ * Set the drawable to be displayed by the magnifier.
+ */
+ public void setDrawable(@NonNull Drawable drawable, int width, int height) {
+ mDrawable = drawable;
+ mDrawable.setBounds(0, 0, width, height);
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 4a3ffa45ab81..bf65132166b6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -18,7 +18,6 @@ package com.android.systemui.screenshot;
import android.annotation.UiThread;
import android.content.Context;
-import android.graphics.Rect;
import android.net.Uri;
import android.provider.Settings;
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.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;
/**
@@ -89,29 +85,6 @@ public class ScrollCaptureController {
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) {
Log.d(TAG, "onCaptureResult: " + result);
boolean emptyResult = result.captured.height() == 0;