Merge changes Ie275f5c3,I1cab53a6 into sc-dev

* changes:
  Move RecyclerView scroll capture to the center
  Move scroll capture back to ScreenshotController.
This commit is contained in:
Matt Casey
2021-04-17 02:23:28 +00:00
committed by Android (Google) Code Review
5 changed files with 118 additions and 56 deletions

View File

@@ -105,6 +105,14 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr
int prevAnchorTop = anchor.getTop();
// Note: requestChildRectangleOnScreen may modify rectangle, must pass pass in a copy here
Rect input = new Rect(requestedContentBounds);
// Expand input rect to get the requested rect to be in the center
int remainingHeight = recyclerView.getHeight() - recyclerView.getPaddingTop()
- recyclerView.getPaddingBottom() - input.height();
if (remainingHeight > 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");

View File

@@ -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);
}

View File

@@ -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<File> mCacheSaveFuture;
private ListenableFuture<ImageLoader.Result> mCacheLoadFuture;
private ListenableFuture<LongScreenshot> 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<ImageLoader.Result> 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();
}

View File

@@ -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<ScrollCaptureController.LongScreenshot> 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);
}
}

View File

@@ -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<ScrollCaptureController.LongScreenshot> 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