summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java201
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java186
5 files changed, 255 insertions, 166 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2faca8dbdcbf..fbe58c505662 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -334,6 +334,11 @@
</intent-filter>
</receiver>
+ <activity android:name=".screenshot.LongScreenshotActivity"
+ android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+ android:process=":screenshot"
+ android:finishOnTaskLaunch="true" />
+
<activity android:name=".screenrecord.ScreenRecordDialog"
android:theme="@style/ScreenRecord"
android:showForAllUsers="true"
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 2b362b94d1f5..8d2639d4cdd0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -22,6 +22,7 @@ import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.screenrecord.ScreenRecordDialog;
+import com.android.systemui.screenshot.LongScreenshotActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
import com.android.systemui.tuner.TunerActivity;
@@ -99,4 +100,10 @@ public abstract class DefaultActivityBinder {
@IntoMap
@ClassKey(PeopleSpaceActivity.class)
public abstract Activity bindPeopleSpaceActivity(PeopleSpaceActivity activity);
+
+ /** Inject into LongScreenshotActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(LongScreenshotActivity.class)
+ public abstract Activity bindLongScreenshotActivity(LongScreenshotActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
new file mode 100644
index 000000000000..a6433ae94b2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -0,0 +1,201 @@
+/*
+ * 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.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * LongScreenshotActivity acquires bitmap data for a long screenshot and lets the user trim the top
+ * and bottom before saving/sharing/editing.
+ */
+public class LongScreenshotActivity extends Activity {
+ private static final String TAG = "LongScreenshotActivity";
+
+ private final UiEventLogger mUiEventLogger;
+ private final ScrollCaptureController mScrollCaptureController;
+
+ private ImageView mPreview;
+ private View mSave;
+ private View mCancel;
+ private View mEdit;
+ private View mShare;
+ private CropView mCropView;
+ private MagnifierView mMagnifierView;
+
+ private enum PendingAction {
+ SHARE,
+ EDIT,
+ SAVE
+ }
+
+ @Inject
+ public LongScreenshotActivity(UiEventLogger uiEventLogger,
+ ImageExporter imageExporter,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ Context context) {
+ mUiEventLogger = uiEventLogger;
+
+ mScrollCaptureController = new ScrollCaptureController(context,
+ ScreenshotController.sScrollConnection, mainExecutor, bgExecutor, imageExporter);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.long_screenshot);
+
+ mPreview = findViewById(R.id.preview);
+ mSave = findViewById(R.id.save);
+ mCancel = findViewById(R.id.cancel);
+ mEdit = findViewById(R.id.edit);
+ mShare = findViewById(R.id.share);
+ mCropView = findViewById(R.id.crop_view);
+ mMagnifierView = findViewById(R.id.magnifier);
+ mCropView.setCropInteractionListener(mMagnifierView);
+
+ mSave.setOnClickListener(this::onClicked);
+ mCancel.setOnClickListener(this::onClicked);
+ mEdit.setOnClickListener(this::onClicked);
+ mShare.setOnClickListener(this::onClicked);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (mPreview.getDrawable() == null) {
+ doCapture();
+ }
+ }
+
+ private void disableButtons() {
+ mSave.setEnabled(false);
+ mCancel.setEnabled(false);
+ mEdit.setEnabled(false);
+ mShare.setEnabled(false);
+ }
+
+ private void doEdit(Uri uri) {
+ String editorPackage = getString(R.string.config_screenshotEditor);
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ intent.setType("image/png");
+ intent.setData(uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ finishAndRemoveTask();
+ }
+
+ private void doShare(Uri uri) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("image/png");
+ intent.setData(uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Intent sharingChooserIntent = Intent.createChooser(intent, null)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ }
+
+ private void onClicked(View v) {
+ int id = v.getId();
+ v.setPressed(true);
+ disableButtons();
+ if (id == R.id.save) {
+ startExport(PendingAction.SAVE);
+ } else if (id == R.id.cancel) {
+ finishAndRemoveTask();
+ } else if (id == R.id.edit) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
+ startExport(PendingAction.EDIT);
+ } else if (id == R.id.share) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
+ startExport(PendingAction.SHARE);
+ }
+ }
+
+ 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.");
+ }
+
+ @Override
+ public void onExportComplete(Uri outputUri) {
+ switch (action) {
+ case EDIT:
+ doEdit(outputUri);
+ break;
+ case SHARE:
+ doShare(outputUri);
+ break;
+ case SAVE:
+ // Nothing more to do
+ finishAndRemoveTask();
+ break;
+ }
+ }
+ });
+ }
+
+ private void doCapture() {
+ mScrollCaptureController.start(new ScrollCaptureController.ScrollCaptureCallback() {
+ @Override
+ public void onError() {
+ Log.e(TAG, "Error!");
+ 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);
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 953b40b6e17b..31c693bdde1f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -41,6 +41,7 @@ import android.app.Notification;
import android.app.WindowContext;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
@@ -100,6 +101,8 @@ import javax.inject.Inject;
public class ScreenshotController {
private static final String TAG = logTag(ScreenshotController.class);
+ public static ScrollCaptureClient.Connection sScrollConnection;
+
/**
* POD used in the AsyncTask which saves an image in the background.
*/
@@ -597,21 +600,12 @@ public class ScreenshotController {
}
private void runScrollCapture(ScrollCaptureClient.Connection connection) {
- cancelTimeout();
- ScrollCaptureController controller = new ScrollCaptureController(mContext, connection,
- mMainExecutor, mBgExecutor, mImageExporter, mUiEventLogger);
- controller.attach(mWindow);
- controller.start(new TakeScreenshotService.RequestCallback() {
- @Override
- public void reportError() {
- }
+ sScrollConnection = connection; // For LongScreenshotActivity to pick up.
- @Override
- public void onFinish() {
- Log.d(TAG, "onFinish from ScrollCaptureController");
- finishDismiss();
- }
- });
+ Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ dismissScreenshot(false);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index ad5e637b189e..863116a22ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,29 +16,16 @@
package com.android.systemui.screenshot;
-import android.annotation.IdRes;
import android.annotation.UiThread;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.UserHandle;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.Window;
-import android.widget.ImageView;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.R;
+
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
import com.google.common.util.concurrent.ListenableFuture;
@@ -50,7 +37,7 @@ import java.util.concurrent.Executor;
/**
* Interaction controller between the UI and ScrollCaptureClient.
*/
-public class ScrollCaptureController implements OnComputeInternalInsetsListener {
+public class ScrollCaptureController {
private static final String TAG = "ScrollCaptureController";
private static final float MAX_PAGES_DEFAULT = 3f;
@@ -64,13 +51,6 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener
private boolean mAtTopEdge;
private Session mSession;
- // TODO: Support saving without additional action.
- private enum PendingAction {
- SHARE,
- EDIT,
- SAVE
- }
-
public static final int MAX_HEIGHT = 12000;
private final Connection mConnection;
@@ -80,172 +60,59 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener
private final Executor mBgExecutor;
private final ImageExporter mImageExporter;
private final ImageTileSet mImageTileSet;
- private final UiEventLogger mUiEventLogger;
private ZonedDateTime mCaptureTime;
private UUID mRequestId;
- private RequestCallback mCallback;
- private Window mWindow;
- private ImageView mPreview;
- private View mSave;
- private View mCancel;
- private View mEdit;
- private View mShare;
- private CropView mCropView;
- private MagnifierView mMagnifierView;
+ private ScrollCaptureCallback mCaptureCallback;
public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
- Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) {
+ Executor bgExecutor, ImageExporter exporter) {
mContext = context;
mConnection = connection;
mUiExecutor = uiExecutor;
mBgExecutor = bgExecutor;
mImageExporter = exporter;
- mUiEventLogger = uiEventLogger;
mImageTileSet = new ImageTileSet(context.getMainThreadHandler());
}
/**
- * @param window the window to display the preview
- */
- public void attach(Window window) {
- mWindow = window;
- }
-
- /**
* Run scroll capture!
*
* @param callback request callback to report back to the service
*/
- public void start(RequestCallback callback) {
+ public void start(ScrollCaptureCallback callback) {
mCaptureTime = ZonedDateTime.now();
mRequestId = UUID.randomUUID();
- mCallback = callback;
-
- setContentView(R.layout.long_screenshot);
- mWindow.getDecorView().getViewTreeObserver()
- .addOnComputeInternalInsetsListener(this);
- mPreview = findViewById(R.id.preview);
-
- mSave = findViewById(R.id.save);
- mCancel = findViewById(R.id.cancel);
- mEdit = findViewById(R.id.edit);
- mShare = findViewById(R.id.share);
- mCropView = findViewById(R.id.crop_view);
- mMagnifierView = findViewById(R.id.magnifier);
- mCropView.setCropInteractionListener(mMagnifierView);
-
- mSave.setOnClickListener(this::onClicked);
- mCancel.setOnClickListener(this::onClicked);
- mEdit.setOnClickListener(this::onClicked);
- mShare.setOnClickListener(this::onClicked);
+ mCaptureCallback = callback;
float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
mConnection.start(this::startCapture, maxPages);
}
-
- /** Ensure the entire window is touchable */
- public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- }
-
- void disableButtons() {
- mSave.setEnabled(false);
- mCancel.setEnabled(false);
- mEdit.setEnabled(false);
- mShare.setEnabled(false);
- }
-
- private void onClicked(View v) {
- Log.d(TAG, "button clicked!");
-
- int id = v.getId();
- v.setPressed(true);
- disableButtons();
- if (id == R.id.save) {
- startExport(PendingAction.SAVE);
- } else if (id == R.id.cancel) {
- doFinish();
- } else if (id == R.id.edit) {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
- startExport(PendingAction.EDIT);
- } else if (id == R.id.share) {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
- startExport(PendingAction.SHARE);
- }
- }
-
- private void doFinish() {
- mPreview.setImageDrawable(null);
- mMagnifierView.setImageTileset(null);
- mImageTileSet.clear();
- mCallback.onFinish();
- mWindow.getDecorView().getViewTreeObserver()
- .removeOnComputeInternalInsetsListener(this);
- }
-
- private void startExport(PendingAction action) {
+ /**
+ * @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() * mCropView.getTopBoundary()),
+ (int) (mImageTileSet.getHeight() * topCrop),
mImageTileSet.getWidth(),
- (int) (mImageTileSet.getHeight() * mCropView.getBottomBoundary()));
+ (int) (mImageTileSet.getHeight() * bottomCrop));
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
mBgExecutor, mRequestId, mImageTileSet.toBitmap(croppedPortion), mCaptureTime);
exportFuture.addListener(() -> {
try {
ImageExporter.Result result = exportFuture.get();
- if (action == PendingAction.EDIT) {
- doEdit(result.uri);
- } else if (action == PendingAction.SHARE) {
- doShare(result.uri);
- }
- doFinish();
+ callback.onExportComplete(result.uri);
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "failed to export", e);
- mCallback.onFinish();
+ callback.onError();
}
}, mUiExecutor);
}
- private void doEdit(Uri uri) {
- String editorPackage = mContext.getString(R.string.config_screenshotEditor);
- Intent intent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- intent.setComponent(ComponentName.unflattenFromString(editorPackage));
- }
- intent.setType("image/png");
- intent.setData(uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- }
-
- private void doShare(Uri uri) {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("image/png");
- intent.setData(uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- Intent sharingChooserIntent = Intent.createChooser(intent, null)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- mContext.startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
- }
-
- private void setContentView(@IdRes int id) {
- mWindow.setContentView(id);
- }
-
- <T extends View> T findViewById(@IdRes int res) {
- return mWindow.findViewById(res);
- }
-
-
private void onCaptureResult(CaptureResult result) {
Log.d(TAG, "onCaptureResult: " + result);
boolean emptyResult = result.captured.height() == 0;
@@ -327,11 +194,26 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener
Log.d(TAG, "afterCaptureComplete");
if (mImageTileSet.isEmpty()) {
- session.end(mCallback::onFinish);
+ mCaptureCallback.onError();
} else {
- mPreview.setImageDrawable(mImageTileSet.getDrawable());
- mMagnifierView.setImageTileset(mImageTileSet);
- mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
+ mCaptureCallback.onComplete(mImageTileSet);
}
}
+
+ /**
+ * Callback for image capture completion or error.
+ */
+ public interface ScrollCaptureCallback {
+ void onComplete(ImageTileSet imageTileSet);
+ void onError();
+ }
+
+ /**
+ * Callback for image export completion or error.
+ */
+ public interface ExportCallback {
+ void onExportComplete(Uri outputUri);
+ void onError();
+ }
+
}