Loading packages/SystemUI/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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" Loading packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java 0 → 100644 +201 −0 Original line number Diff line number Diff line /* * 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); } }); } } packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +8 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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); } /** Loading packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +34 −152 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); } } Loading
packages/SystemUI/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +7 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); }
packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java 0 → 100644 +201 −0 Original line number Diff line number Diff line /* * 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); } }); } }
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +8 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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); } /** Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +34 −152 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); } }