diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java index 934241acd851c..75bf1ce136c93 100644 --- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java +++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java @@ -105,6 +105,14 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper 0) { + input.inset(0, -remainingHeight / 2); + } + Log.d(TAG, "input (post center adjustment) = " + input); + if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) { int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement Log.d(TAG, "RecyclerView scrolled by " + scrolled + " px"); diff --git a/core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java index 88bbcc29e71aa..a6b26be2b9fcb 100644 --- a/core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java +++ b/core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java @@ -145,7 +145,9 @@ public class RecyclerViewCaptureHelperTest { ScrollResult scrollResult = rvc.onScrollRequested(mTarget, scrollBounds, request); assertThat(request).isEqualTo(scrollResult.requestedArea); assertThat(request).isEqualTo(scrollResult.availableArea); - assertThat(scrollResult.scrollDelta).isEqualTo(CAPTURE_HEIGHT); + // Capture height centered in the window + assertThat(scrollResult.scrollDelta).isEqualTo( + CAPTURE_HEIGHT + (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2); assertAvailableAreaCompletelyVisible(scrollResult, mTarget); } @@ -163,7 +165,8 @@ public class RecyclerViewCaptureHelperTest { ScrollResult scrollResult = helper.onScrollRequested(mTarget, scrollBounds, request); assertThat(request).isEqualTo(scrollResult.requestedArea); assertThat(request).isEqualTo(scrollResult.availableArea); - assertThat(scrollResult.scrollDelta).isEqualTo(-CAPTURE_HEIGHT); + assertThat(scrollResult.scrollDelta).isEqualTo( + -CAPTURE_HEIGHT - (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2); assertAvailableAreaCompletelyVisible(scrollResult, mTarget); } @@ -182,7 +185,8 @@ public class RecyclerViewCaptureHelperTest { ScrollResult scrollResult = helper.onScrollRequested(mTarget, scrollBounds, request); assertThat(request).isEqualTo(scrollResult.requestedArea); assertThat(request).isEqualTo(scrollResult.availableArea); - assertThat(scrollResult.scrollDelta).isEqualTo(CAPTURE_HEIGHT); + assertThat(scrollResult.scrollDelta).isEqualTo( + CAPTURE_HEIGHT + (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2); assertAvailableAreaCompletelyVisible(scrollResult, mTarget); } @@ -200,7 +204,8 @@ public class RecyclerViewCaptureHelperTest { ScrollResult scrollResult = helper.onScrollRequested(mTarget, scrollBounds, request); assertThat(request).isEqualTo(scrollResult.requestedArea); assertThat(request).isEqualTo(scrollResult.availableArea); - assertThat(scrollResult.scrollDelta).isEqualTo(-CAPTURE_HEIGHT); + assertThat(scrollResult.scrollDelta).isEqualTo( + -CAPTURE_HEIGHT - (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2); assertAvailableAreaCompletelyVisible(scrollResult, mTarget); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 33041d280b9b5..20aa6dd4a380c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -18,7 +18,6 @@ package com.android.systemui.screenshot; import android.app.Activity; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; @@ -32,7 +31,6 @@ import android.os.Bundle; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; -import android.view.IWindowManager; import android.view.ScrollCaptureResponse; import android.view.View; import android.widget.ImageView; @@ -65,14 +63,10 @@ public class LongScreenshotActivity extends Activity { private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path"; private final UiEventLogger mUiEventLogger; - private final ScrollCaptureController mScrollCaptureController; private final Executor mUiExecutor; private final Executor mBackgroundExecutor; private final ImageExporter mImageExporter; - - // 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 final LongScreenshotHolder mLongScreenshotHolder; private ImageView mPreview; private View mSave; @@ -86,7 +80,6 @@ public class LongScreenshotActivity extends Activity { private ListenableFuture mCacheSaveFuture; private ListenableFuture mCacheLoadFuture; - private ListenableFuture mLongScreenshotFuture; private LongScreenshot mLongScreenshot; private enum PendingAction { @@ -97,13 +90,13 @@ public class LongScreenshotActivity extends Activity { @Inject public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter, - @Main Executor mainExecutor, @Background Executor bgExecutor, IWindowManager wms, - Context context, ScrollCaptureController scrollCaptureController) { + @Main Executor mainExecutor, @Background Executor bgExecutor, + LongScreenshotHolder longScreenshotHolder) { mUiEventLogger = uiEventLogger; mUiExecutor = mainExecutor; mBackgroundExecutor = bgExecutor; mImageExporter = imageExporter; - mScrollCaptureController = scrollCaptureController; + mLongScreenshotHolder = longScreenshotHolder; } @@ -152,7 +145,7 @@ public class LongScreenshotActivity extends Activity { super.onStart(); if (mCacheLoadFuture != null) { - Log.d(TAG, "mRestoringInstance = true"); + Log.d(TAG, "mCacheLoadFuture != null"); final ListenableFuture future = mCacheLoadFuture; mCacheLoadFuture.addListener(() -> { Log.d(TAG, "cached bitmap load complete"); @@ -170,40 +163,19 @@ public class LongScreenshotActivity extends Activity { }, mUiExecutor); mCacheLoadFuture = null; return; - } - - if (mLongScreenshotFuture == null) { - Log.d(TAG, "mLongScreenshotFuture == null"); - // First run through, ensure we have a connection to use (see #onCreate) - if (mScrollCaptureResponse == null || !mScrollCaptureResponse.isConnected()) { - Log.e(TAG, "Did not receive a live scroll capture connection, bailing out!"); - finishAndRemoveTask(); - return; - } - mLongScreenshotFuture = mScrollCaptureController.run(mScrollCaptureResponse); - mLongScreenshotFuture.addListener(() -> { - LongScreenshot longScreenshot; - try { - longScreenshot = mLongScreenshotFuture.get(); - } catch (CancellationException | InterruptedException | ExecutionException e) { - Log.e(TAG, "Error capturing long screenshot!", e); - finishAndRemoveTask(); - return; - } - if (longScreenshot.getHeight() == 0) { - Log.e(TAG, "Got a zero height result"); - finishAndRemoveTask(); - return; - } - onCaptureCompleted(longScreenshot); - }, mUiExecutor); } else { - Log.d(TAG, "mLongScreenshotFuture != null"); + LongScreenshot longScreenshot = mLongScreenshotHolder.takeLongScreenshot(); + if (longScreenshot != null) { + onLongScreenshotReceived(longScreenshot); + } else { + Log.e(TAG, "No long screenshot available!"); + finishAndRemoveTask(); + } } } - private void onCaptureCompleted(LongScreenshot longScreenshot) { - Log.d(TAG, "onCaptureCompleted(longScreenshot=" + longScreenshot + ")"); + private void onLongScreenshotReceived(LongScreenshot longScreenshot) { + Log.d(TAG, "onLongScreenshotReceived(longScreenshot=" + longScreenshot + ")"); mLongScreenshot = longScreenshot; mPreview.setImageDrawable(mLongScreenshot.getDrawable()); updateImageDimensions(); @@ -283,9 +255,6 @@ public class LongScreenshotActivity extends Activity { } cleanupCache(); - if (mLongScreenshotFuture != null) { - mLongScreenshotFuture.cancel(true); - } if (mLongScreenshot != null) { mLongScreenshot.release(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotHolder.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotHolder.java new file mode 100644 index 0000000000000..39c6f07989885 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotHolder.java @@ -0,0 +1,59 @@ +/* + * 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 com.android.systemui.dagger.SysUISingleton; + +import java.util.concurrent.atomic.AtomicReference; + +import javax.inject.Inject; + +/** + * LongScreenshotHolder holds on to 1 LongScreenshot reference to facilitate indirect in-process + * passing. + */ +@SysUISingleton +public class LongScreenshotHolder { + private final AtomicReference mLongScreenshot; + + @Inject + public LongScreenshotHolder() { + mLongScreenshot = new AtomicReference<>(); + } + + /** + * Set the holder's stored LongScreenshot. + */ + public void setLongScreenshot(ScrollCaptureController.LongScreenshot longScreenshot) { + mLongScreenshot.set(longScreenshot); + } + + /** + * @return true if the holder has a non-null LongScreenshot. + */ + public boolean hasLongScreenshot() { + return mLongScreenshot.get() != null; + } + + /** + * Return the current stored LongScreenshot, clear the holder's storage. + */ + public ScrollCaptureController.LongScreenshot takeLongScreenshot() { + return mLongScreenshot.getAndSet(null); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index badffce3259ab..9d0198615dcd5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -191,6 +191,8 @@ public class ScreenshotController { private final ScrollCaptureClient mScrollCaptureClient; private final PhoneWindow mWindow; private final DisplayManager mDisplayManager; + private final ScrollCaptureController mScrollCaptureController; + private final LongScreenshotHolder mLongScreenshotHolder; private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; @@ -233,13 +235,17 @@ public class ScreenshotController { ScrollCaptureClient scrollCaptureClient, UiEventLogger uiEventLogger, ImageExporter imageExporter, - @Main Executor mainExecutor) { + @Main Executor mainExecutor, + ScrollCaptureController scrollCaptureController, + LongScreenshotHolder longScreenshotHolder) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mScrollCaptureClient = scrollCaptureClient; mUiEventLogger = uiEventLogger; mImageExporter = imageExporter; mMainExecutor = mainExecutor; + mScrollCaptureController = scrollCaptureController; + mLongScreenshotHolder = longScreenshotHolder; mBgExecutor = Executors.newSingleThreadExecutor(); mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); @@ -569,15 +575,30 @@ public class ScreenshotController { } Log.d(TAG, "ScrollCapture: connected to window [" + mLastScrollCaptureResponse.getWindowTitle() + "]"); - final Intent intent = new Intent(mContext, LongScreenshotActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(LongScreenshotActivity.EXTRA_CAPTURE_RESPONSE, - mLastScrollCaptureResponse); + + final ScrollCaptureResponse response = mLastScrollCaptureResponse; mScreenshotView.showScrollChip(/* onClick */ () -> { // Clear the reference to prevent close() in dismissScreenshot mLastScrollCaptureResponse = null; - mContext.startActivity(intent); - dismissScreenshot(false); + final ListenableFuture future = + mScrollCaptureController.run(response); + future.addListener(() -> { + ScrollCaptureController.LongScreenshot longScreenshot; + try { + longScreenshot = future.get(); + } catch (CancellationException | InterruptedException | ExecutionException e) { + Log.e(TAG, "Exception", e); + return; + } + + mLongScreenshotHolder.setLongScreenshot(longScreenshot); + + final Intent intent = new Intent(mContext, LongScreenshotActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + + dismissScreenshot(false); + }, mMainExecutor); }); } catch (CancellationException e) { // Ignore