Merge "Ensure cleanup when interrupting scroll capture"
This commit is contained in:
@@ -24,9 +24,9 @@ import android.annotation.UiThread;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.IBinder;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Trace;
|
||||
import android.util.CloseGuard;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -44,7 +44,8 @@ import java.util.function.Consumer;
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub implements
|
||||
IBinder.DeathRecipient {
|
||||
|
||||
private static final String TAG = "ScrollCaptureConnection";
|
||||
|
||||
@@ -54,15 +55,13 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
private final Executor mUiThread;
|
||||
private final CloseGuard mCloseGuard = new CloseGuard();
|
||||
|
||||
|
||||
private ScrollCaptureCallback mLocal;
|
||||
private IScrollCaptureCallbacks mRemote;
|
||||
|
||||
private ScrollCaptureSession mSession;
|
||||
|
||||
private CancellationSignal mCancellation;
|
||||
|
||||
private volatile boolean mActive;
|
||||
private volatile boolean mConnected;
|
||||
|
||||
/**
|
||||
* Constructs a ScrollCaptureConnection.
|
||||
@@ -87,13 +86,14 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
@Override
|
||||
public ICancellationSignal startCapture(@NonNull Surface surface,
|
||||
@NonNull IScrollCaptureCallbacks remote) throws RemoteException {
|
||||
|
||||
mCloseGuard.open("close");
|
||||
|
||||
if (!surface.isValid()) {
|
||||
throw new RemoteException(new IllegalArgumentException("surface must be valid"));
|
||||
}
|
||||
mRemote = requireNonNull(remote, "<callbacks> must non-null");
|
||||
mRemote.asBinder().linkToDeath(this, 0);
|
||||
mConnected = true;
|
||||
|
||||
ICancellationSignal cancellation = CancellationSignal.createTransport();
|
||||
mCancellation = CancellationSignal.fromTransport(cancellation);
|
||||
@@ -115,14 +115,14 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
Log.w(TAG, "Shutting down due to error: ", e);
|
||||
close();
|
||||
}
|
||||
mCancellation = null;
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@Override
|
||||
public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
|
||||
Trace.beginSection("requestImage");
|
||||
checkActive();
|
||||
|
||||
cancelPendingAction();
|
||||
ICancellationSignal cancellation = CancellationSignal.createTransport();
|
||||
mCancellation = CancellationSignal.fromTransport(cancellation);
|
||||
|
||||
@@ -131,7 +131,6 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
// -> UiThread
|
||||
mUiThread.execute(() -> mLocal.onScrollCaptureImageRequest(
|
||||
mSession, mCancellation, new Rect(requestRect), listener));
|
||||
Trace.endSection();
|
||||
return cancellation;
|
||||
}
|
||||
|
||||
@@ -142,6 +141,8 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Shutting down due to error: ", e);
|
||||
close();
|
||||
} finally {
|
||||
mCancellation = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +150,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
@Override
|
||||
public ICancellationSignal endCapture() throws RemoteException {
|
||||
checkActive();
|
||||
|
||||
cancelPendingAction();
|
||||
ICancellationSignal cancellation = CancellationSignal.createTransport();
|
||||
mCancellation = CancellationSignal.fromTransport(cancellation);
|
||||
|
||||
@@ -170,26 +171,32 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Caught exception confirming capture end!", e);
|
||||
} finally {
|
||||
mCancellation = null;
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Log.e(TAG, "Controlling process just died.");
|
||||
close();
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@Override
|
||||
public void close() {
|
||||
if (mActive) {
|
||||
if (mCancellation != null) {
|
||||
Log.w(TAG, "close(): cancelling pending operation.");
|
||||
mCancellation.cancel();
|
||||
mCancellation = null;
|
||||
}
|
||||
Log.w(TAG, "close(): capture session still active! Ending now.");
|
||||
// -> UiThread
|
||||
cancelPendingAction();
|
||||
final ScrollCaptureCallback callback = mLocal;
|
||||
mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
|
||||
mActive = false;
|
||||
}
|
||||
if (mRemote != null) {
|
||||
mRemote.asBinder().unlinkToDeath(this, 0);
|
||||
}
|
||||
mActive = false;
|
||||
mConnected = false;
|
||||
mSession = null;
|
||||
mRemote = null;
|
||||
mLocal = null;
|
||||
@@ -197,6 +204,19 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
Reference.reachabilityFence(this);
|
||||
}
|
||||
|
||||
private void cancelPendingAction() {
|
||||
if (mCancellation != null) {
|
||||
Log.w(TAG, "cancelling pending operation.");
|
||||
mCancellation.cancel();
|
||||
mCancellation = null;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean isConnected() {
|
||||
return mConnected;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean isActive() {
|
||||
return mActive;
|
||||
@@ -236,7 +256,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
|
||||
protected SafeCallback(CancellationSignal signal, Executor executor, T value) {
|
||||
mSignal = signal;
|
||||
mValue = new AtomicReference<T>(value);
|
||||
mValue = new AtomicReference<>(value);
|
||||
mExecutor = executor;
|
||||
}
|
||||
|
||||
@@ -257,7 +277,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
|
||||
|
||||
static <T> Consumer<T> create(CancellationSignal signal, Executor executor,
|
||||
Consumer<T> target) {
|
||||
return new ConsumerCallback<T>(signal, executor, target);
|
||||
return new ConsumerCallback<>(signal, executor, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,16 +21,19 @@ import static androidx.test.InstrumentationRegistry.getTargetContext;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.RemoteException;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
@@ -60,6 +63,7 @@ public class ScrollCaptureConnectionTest {
|
||||
|
||||
private ScrollCaptureTarget mTarget;
|
||||
private ScrollCaptureConnection mConnection;
|
||||
private IBinder mConnectionBinder = new Binder("ScrollCaptureConnection Test");
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
@@ -76,6 +80,7 @@ public class ScrollCaptureConnectionTest {
|
||||
mHandler = new Handler(getTargetContext().getMainLooper());
|
||||
when(mSurface.isValid()).thenReturn(true);
|
||||
when(mView.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE);
|
||||
when(mRemote.asBinder()).thenReturn(mConnectionBinder);
|
||||
|
||||
mTarget = new ScrollCaptureTarget(mView, mLocalVisibleRect, mPositionInWindow, mCallback);
|
||||
mTarget.setScrollBounds(mScrollBounds);
|
||||
@@ -107,12 +112,13 @@ public class ScrollCaptureConnectionTest {
|
||||
@Test
|
||||
public void testStartCapture() throws Exception {
|
||||
mConnection.startCapture(mSurface, mRemote);
|
||||
assertTrue(mConnection.isConnected());
|
||||
assertFalse(mConnection.isActive());
|
||||
|
||||
mCallback.completeStartRequest();
|
||||
assertTrue(mConnection.isActive());
|
||||
|
||||
verify(mRemote, times(1)).onCaptureStarted();
|
||||
verifyNoMoreInteractions(mRemote);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,7 +129,7 @@ public class ScrollCaptureConnectionTest {
|
||||
mCallback.completeStartRequest();
|
||||
assertFalse(mConnection.isActive());
|
||||
|
||||
verifyNoMoreInteractions(mRemote);
|
||||
verify(mRemote, never()).onCaptureStarted();
|
||||
}
|
||||
|
||||
/** @see ScrollCaptureConnection#requestImage(Rect) */
|
||||
@@ -131,42 +137,63 @@ public class ScrollCaptureConnectionTest {
|
||||
public void testRequestImage() throws Exception {
|
||||
mConnection.startCapture(mSurface, mRemote);
|
||||
mCallback.completeStartRequest();
|
||||
reset(mRemote);
|
||||
|
||||
mConnection.requestImage(new Rect(1, 2, 3, 4));
|
||||
mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
|
||||
|
||||
verify(mRemote, times(1))
|
||||
.onImageRequestCompleted(eq(0), eq(new Rect(1, 2, 3, 4)));
|
||||
verifyNoMoreInteractions(mRemote);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestImage_cancellation() throws Exception {
|
||||
mConnection.startCapture(mSurface, mRemote);
|
||||
mCallback.completeStartRequest();
|
||||
reset(mRemote);
|
||||
|
||||
ICancellationSignal signal = mConnection.requestImage(new Rect(1, 2, 3, 4));
|
||||
signal.cancel();
|
||||
mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
|
||||
|
||||
verifyNoMoreInteractions(mRemote);
|
||||
verify(mRemote, never()).onImageRequestCompleted(anyInt(), any(Rect.class));
|
||||
}
|
||||
|
||||
/** @see ScrollCaptureConnection#endCapture() */
|
||||
@Test
|
||||
public void testEndCapture() throws Exception {
|
||||
mConnection.startCapture(mSurface, mRemote);
|
||||
assertTrue(mConnection.isConnected());
|
||||
|
||||
mCallback.completeStartRequest();
|
||||
reset(mRemote);
|
||||
assertTrue(mConnection.isActive());
|
||||
|
||||
mConnection.endCapture();
|
||||
mCallback.completeEndRequest();
|
||||
assertFalse(mConnection.isActive());
|
||||
|
||||
// And the reply is sent
|
||||
verify(mRemote, times(1)).onCaptureEnded();
|
||||
verifyNoMoreInteractions(mRemote);
|
||||
|
||||
assertFalse(mConnection.isConnected());
|
||||
}
|
||||
|
||||
/** @see ScrollCaptureConnection#endCapture() */
|
||||
@Test
|
||||
public void testClose_withPendingOperation() throws Exception {
|
||||
mConnection.startCapture(mSurface, mRemote);
|
||||
mCallback.completeStartRequest();
|
||||
|
||||
mConnection.requestImage(new Rect(1, 2, 3, 4));
|
||||
assertFalse(mCallback.getLastCancellationSignal().isCanceled());
|
||||
|
||||
mConnection.close();
|
||||
|
||||
// And the reply is sent
|
||||
assertTrue(mCallback.onScrollCaptureEndCalled());
|
||||
assertTrue(mCallback.getLastCancellationSignal().isCanceled());
|
||||
assertFalse(mConnection.isActive());
|
||||
assertFalse(mConnection.isConnected());
|
||||
verify(mRemote, never()).onCaptureEnded();
|
||||
verify(mRemote, never()).onImageRequestCompleted(anyInt(), any(Rect.class));
|
||||
}
|
||||
|
||||
/** @see ScrollCaptureConnection#endCapture() */
|
||||
@@ -174,20 +201,19 @@ public class ScrollCaptureConnectionTest {
|
||||
public void testEndCapture_cancellation() throws Exception {
|
||||
mConnection.startCapture(mSurface, mRemote);
|
||||
mCallback.completeStartRequest();
|
||||
reset(mRemote);
|
||||
|
||||
ICancellationSignal signal = mConnection.endCapture();
|
||||
signal.cancel();
|
||||
mCallback.completeEndRequest();
|
||||
|
||||
verifyNoMoreInteractions(mRemote);
|
||||
verify(mRemote, never()).onCaptureEnded();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() {
|
||||
mConnection.close();
|
||||
assertFalse(mConnection.isActive());
|
||||
verifyNoMoreInteractions(mRemote);
|
||||
assertFalse(mConnection.isConnected());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -196,9 +222,11 @@ public class ScrollCaptureConnectionTest {
|
||||
|
||||
mCallback.completeStartRequest();
|
||||
assertTrue(mConnection.isActive());
|
||||
assertTrue(mConnection.isConnected());
|
||||
|
||||
mConnection.close();
|
||||
mCallback.completeEndRequest();
|
||||
assertFalse(mConnection.isActive());
|
||||
assertFalse(mConnection.isConnected());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,13 @@ class TestScrollCaptureCallback implements ScrollCaptureCallback {
|
||||
private Consumer<Rect> mImageOnComplete;
|
||||
private Runnable mOnEndReady;
|
||||
private volatile int mModCount;
|
||||
private boolean mOnScrollCaptureEndCalled;
|
||||
private CancellationSignal mLastCancellationSignal;
|
||||
|
||||
@Override
|
||||
public void onScrollCaptureSearch(@NonNull CancellationSignal signal,
|
||||
@NonNull Consumer<Rect> onReady) {
|
||||
mLastCancellationSignal = signal;
|
||||
mSearchConsumer = onReady;
|
||||
mModCount++;
|
||||
}
|
||||
@@ -42,6 +45,7 @@ class TestScrollCaptureCallback implements ScrollCaptureCallback {
|
||||
@Override
|
||||
public void onScrollCaptureStart(@NonNull ScrollCaptureSession session,
|
||||
@NonNull CancellationSignal signal, @NonNull Runnable onReady) {
|
||||
mLastCancellationSignal = signal;
|
||||
mStartOnReady = onReady;
|
||||
mModCount++;
|
||||
}
|
||||
@@ -50,6 +54,7 @@ class TestScrollCaptureCallback implements ScrollCaptureCallback {
|
||||
public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session,
|
||||
@NonNull CancellationSignal signal, @NonNull Rect captureArea,
|
||||
@NonNull Consumer<Rect> onComplete) {
|
||||
mLastCancellationSignal = signal;
|
||||
mImageOnComplete = onComplete;
|
||||
mModCount++;
|
||||
}
|
||||
@@ -57,8 +62,12 @@ class TestScrollCaptureCallback implements ScrollCaptureCallback {
|
||||
@Override
|
||||
public void onScrollCaptureEnd(@NonNull Runnable onReady) {
|
||||
mOnEndReady = onReady;
|
||||
mOnScrollCaptureEndCalled = true;
|
||||
}
|
||||
|
||||
public boolean onScrollCaptureEndCalled() {
|
||||
return mOnScrollCaptureEndCalled;
|
||||
}
|
||||
void completeSearchRequest(Rect scrollBounds) {
|
||||
assertNotNull("Did not receive search request", mSearchConsumer);
|
||||
mSearchConsumer.accept(scrollBounds);
|
||||
@@ -71,16 +80,26 @@ class TestScrollCaptureCallback implements ScrollCaptureCallback {
|
||||
|
||||
void completeStartRequest() {
|
||||
assertNotNull("Did not receive start request", mStartOnReady);
|
||||
mStartOnReady.run();
|
||||
if (mLastCancellationSignal != null && !mLastCancellationSignal.isCanceled()) {
|
||||
mStartOnReady.run();
|
||||
}
|
||||
}
|
||||
|
||||
void completeImageRequest(Rect captured) {
|
||||
assertNotNull("Did not receive image request", mImageOnComplete);
|
||||
mImageOnComplete.accept(captured);
|
||||
if (mLastCancellationSignal != null && !mLastCancellationSignal.isCanceled()) {
|
||||
mImageOnComplete.accept(captured);
|
||||
}
|
||||
}
|
||||
|
||||
void completeEndRequest() {
|
||||
assertNotNull("Did not receive end request", mOnEndReady);
|
||||
mOnEndReady.run();
|
||||
if (mLastCancellationSignal != null && !mLastCancellationSignal.isCanceled()) {
|
||||
mOnEndReady.run();
|
||||
}
|
||||
}
|
||||
|
||||
public CancellationSignal getLastCancellationSignal() {
|
||||
return mLastCancellationSignal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,11 +422,6 @@ public class ScreenshotController {
|
||||
} else {
|
||||
mScreenshotView.animateDismissal();
|
||||
}
|
||||
|
||||
if (mLastScrollCaptureResponse != null) {
|
||||
mLastScrollCaptureResponse.close();
|
||||
mLastScrollCaptureResponse = null;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPendingSharedTransition() {
|
||||
@@ -456,6 +451,9 @@ public class ScreenshotController {
|
||||
mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
|
||||
@Override
|
||||
public void onUserInteraction() {
|
||||
if (DEBUG_INPUT) {
|
||||
Log.d(TAG, "onUserInteraction");
|
||||
}
|
||||
resetTimeout();
|
||||
}
|
||||
|
||||
@@ -670,52 +668,7 @@ public class ScreenshotController {
|
||||
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
|
||||
mScreenshotTakenInPortrait);
|
||||
// delay starting scroll capture to make sure the scrim is up before the app moves
|
||||
mScreenshotView.post(() -> {
|
||||
// Clear the reference to prevent close() in dismissScreenshot
|
||||
mLastScrollCaptureResponse = null;
|
||||
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);
|
||||
mScreenshotView.restoreNonScrollingUi();
|
||||
return;
|
||||
}
|
||||
|
||||
if (longScreenshot.getHeight() == 0) {
|
||||
mScreenshotView.restoreNonScrollingUi();
|
||||
return;
|
||||
}
|
||||
|
||||
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
|
||||
mLongScreenshotHolder.setTransitionDestinationCallback(
|
||||
(transitionDestination, onTransitionEnd) ->
|
||||
mScreenshotView.startLongScreenshotTransition(
|
||||
transitionDestination, onTransitionEnd,
|
||||
longScreenshot));
|
||||
|
||||
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
|
||||
intent.setFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
mContext.startActivity(intent,
|
||||
ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
|
||||
RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
|
||||
SCREENSHOT_REMOTE_RUNNER, 0, 0);
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error overriding screenshot app transition", e);
|
||||
}
|
||||
}, mMainExecutor);
|
||||
});
|
||||
mScreenshotView.post(() -> runBatchScrollCapture(response));
|
||||
});
|
||||
} catch (CancellationException e) {
|
||||
// Ignore
|
||||
@@ -723,6 +676,57 @@ public class ScreenshotController {
|
||||
Log.e(TAG, "requestScrollCapture failed", e);
|
||||
}
|
||||
}
|
||||
ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
|
||||
|
||||
private void runBatchScrollCapture(ScrollCaptureResponse response) {
|
||||
// Clear the reference to prevent close() in dismissScreenshot
|
||||
mLastScrollCaptureResponse = null;
|
||||
|
||||
if (mLongScreenshotFuture != null) {
|
||||
mLongScreenshotFuture.cancel(true);
|
||||
}
|
||||
mLongScreenshotFuture = mScrollCaptureController.run(response);
|
||||
mLongScreenshotFuture.addListener(() -> {
|
||||
ScrollCaptureController.LongScreenshot longScreenshot;
|
||||
try {
|
||||
longScreenshot = mLongScreenshotFuture.get();
|
||||
} catch (CancellationException e) {
|
||||
Log.e(TAG, "Long screenshot cancelled");
|
||||
return;
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.e(TAG, "Exception", e);
|
||||
mScreenshotView.restoreNonScrollingUi();
|
||||
return;
|
||||
}
|
||||
|
||||
if (longScreenshot.getHeight() == 0) {
|
||||
mScreenshotView.restoreNonScrollingUi();
|
||||
return;
|
||||
}
|
||||
|
||||
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
|
||||
mLongScreenshotHolder.setTransitionDestinationCallback(
|
||||
(transitionDestination, onTransitionEnd) ->
|
||||
mScreenshotView.startLongScreenshotTransition(
|
||||
transitionDestination, onTransitionEnd,
|
||||
longScreenshot));
|
||||
|
||||
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
|
||||
intent.setFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
mContext.startActivity(intent,
|
||||
ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
|
||||
RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
|
||||
SCREENSHOT_REMOTE_RUNNER, 0, 0);
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error overriding screenshot app transition", e);
|
||||
}
|
||||
}, mMainExecutor);
|
||||
}
|
||||
|
||||
private void withWindowAttached(Runnable action) {
|
||||
View decorView = mWindow.getDecorView();
|
||||
@@ -824,16 +828,27 @@ public class ScreenshotController {
|
||||
|
||||
/** Reset screenshot view and then call onCompleteRunnable */
|
||||
private void finishDismiss() {
|
||||
if (DEBUG_UI) {
|
||||
if (DEBUG_DISMISS) {
|
||||
Log.d(TAG, "finishDismiss");
|
||||
}
|
||||
cancelTimeout();
|
||||
removeWindow();
|
||||
mScreenshotView.reset();
|
||||
if (mLastScrollCaptureRequest != null) {
|
||||
mLastScrollCaptureRequest.cancel(true);
|
||||
mLastScrollCaptureRequest = null;
|
||||
}
|
||||
if (mLastScrollCaptureResponse != null) {
|
||||
mLastScrollCaptureResponse.close();
|
||||
mLastScrollCaptureResponse = null;
|
||||
}
|
||||
if (mLongScreenshotFuture != null) {
|
||||
mLongScreenshotFuture.cancel(true);
|
||||
}
|
||||
if (mCurrentRequestCallback != null) {
|
||||
mCurrentRequestCallback.onFinish();
|
||||
mCurrentRequestCallback = null;
|
||||
}
|
||||
mScreenshotView.reset();
|
||||
removeWindow();
|
||||
cancelTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -861,6 +876,9 @@ public class ScreenshotController {
|
||||
}
|
||||
|
||||
private void cancelTimeout() {
|
||||
if (DEBUG_DISMISS) {
|
||||
Log.d(TAG, "cancel timeout");
|
||||
}
|
||||
mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
|
||||
}
|
||||
|
||||
@@ -876,7 +894,7 @@ public class ScreenshotController {
|
||||
mScreenshotHandler.sendMessageDelayed(
|
||||
mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
|
||||
timeoutMs);
|
||||
if (DEBUG_UI) {
|
||||
if (DEBUG_DISMISS) {
|
||||
Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms");
|
||||
}
|
||||
|
||||
|
||||
@@ -1117,9 +1117,11 @@ public class ScreenshotView extends FrameLayout implements
|
||||
mPreviousX = mStartX;
|
||||
return true;
|
||||
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
||||
if (isPastDismissThreshold()
|
||||
&& (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
|
||||
if (DEBUG_INPUT) {
|
||||
if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
|
||||
return true;
|
||||
}
|
||||
if (isPastDismissThreshold()) {
|
||||
if (DEBUG_DISMISS) {
|
||||
Log.d(TAG, "dismiss triggered via swipe gesture");
|
||||
}
|
||||
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED);
|
||||
@@ -1129,9 +1131,7 @@ public class ScreenshotView extends FrameLayout implements
|
||||
if (DEBUG_DISMISS) {
|
||||
Log.d(TAG, "swipe gesture abandoned");
|
||||
}
|
||||
if ((mDismissAnimation == null || !mDismissAnimation.isRunning())) {
|
||||
createSwipeReturnAnimation().start();
|
||||
}
|
||||
createSwipeReturnAnimation().start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.android.systemui.screenshot.ScrollCaptureClient.Session;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -72,6 +73,7 @@ public class ScrollCaptureController {
|
||||
private ListenableFuture<CaptureResult> mTileFuture;
|
||||
private ListenableFuture<Void> mEndFuture;
|
||||
private String mWindowOwner;
|
||||
private volatile boolean mCancelled;
|
||||
|
||||
static class LongScreenshot {
|
||||
private final ImageTileSet mImageTileSet;
|
||||
@@ -159,9 +161,11 @@ public class ScrollCaptureController {
|
||||
* @return a future ImageTile set containing the result
|
||||
*/
|
||||
ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
|
||||
mCancelled = false;
|
||||
return CallbackToFutureAdapter.getFuture(completer -> {
|
||||
mCaptureCompleter = completer;
|
||||
mWindowOwner = response.getPackageName();
|
||||
mCaptureCompleter.addCancellationListener(this::onCancelled, mBgExecutor);
|
||||
mBgExecutor.execute(() -> {
|
||||
float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
|
||||
SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
|
||||
@@ -172,6 +176,24 @@ public class ScrollCaptureController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The ListenableFuture for the long screenshot was cancelled. Be sure to cancel all downstream
|
||||
* futures that might be pending.
|
||||
*/
|
||||
private void onCancelled() {
|
||||
mCancelled = true;
|
||||
if (mSessionFuture != null) {
|
||||
mSessionFuture.cancel(true);
|
||||
}
|
||||
if (mTileFuture != null) {
|
||||
mTileFuture.cancel(true);
|
||||
}
|
||||
if (mSession != null) {
|
||||
mSession.end();
|
||||
}
|
||||
mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
|
||||
}
|
||||
|
||||
private void onStartComplete() {
|
||||
try {
|
||||
mSession = mSessionFuture.get();
|
||||
@@ -192,18 +214,21 @@ public class ScrollCaptureController {
|
||||
if (LogConfig.DEBUG_SCROLL) {
|
||||
Log.d(TAG, "requestNextTile: " + topPx);
|
||||
}
|
||||
if (mCancelled) {
|
||||
Log.d(TAG, "requestNextTile: CANCELLED");
|
||||
return;
|
||||
}
|
||||
mTileFuture = mSession.requestTile(topPx);
|
||||
mTileFuture.addListener(() -> {
|
||||
try {
|
||||
if (LogConfig.DEBUG_SCROLL) {
|
||||
Log.d(TAG, "onCaptureResult");
|
||||
}
|
||||
onCaptureResult(mTileFuture.get());
|
||||
} catch (CancellationException e) {
|
||||
Log.e(TAG, "requestTile cancelled");
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.e(TAG, "requestTile failed!", e);
|
||||
mCaptureCompleter.setException(e);
|
||||
}
|
||||
}, mContext.getMainExecutor());
|
||||
}, mBgExecutor);
|
||||
}
|
||||
|
||||
private void onCaptureResult(CaptureResult result) {
|
||||
|
||||
Reference in New Issue
Block a user