Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a927929d authored by Mark Renouf's avatar Mark Renouf
Browse files

Scroll Capture Framework

This is an implementation of long screenshots supporting
interactive, incremental capture of scrolling content using
a cooperative API between the app process and the system.

Design goals:

 - Provide for tile based incremental screenshots of scrolling content
 - Support existing apps without developer action
 - Provide support for non View-based Apps & UI toolkits

Bug: 148131831
Test: atest \
      FrameworksCoreTests:android.view.ScrollCaptureClientTest \
      FrameworksCoreTests:android.view.ScrollCaptureTargetResolverTest \
      FrameworksCoreTests:com.android.internal.view.ViewGroupScrollCaptureTest \
      FrameworksCoreTests:android.view.ScrollViewCaptureHelperTest \
      WmTests:com.android.server.wm.DisplayContentTest

Merged-In: I6c66a623faba274c35b8fa857d3a72030a763aea
Change-Id: I6c66a623faba274c35b8fa857d3a72030a763aea
parent 31b94bfc
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 android.view;

import android.graphics.Rect;
import android.view.Surface;


 /**
   * Interface implemented by a client of the Scroll Capture framework to receive requests
   * to start, capture images and end the session.
   *
   * {@hide}
   */
interface IScrollCaptureClient {

    /**
     * Informs the client that it has been selected for scroll capture and should prepare to
     * to begin handling capture requests.
     */
    oneway void startCapture(in Surface surface);

    /**
     * Request the client capture an image within the provided rectangle.
     *
     * @see android.view.ScrollCaptureCallback#onScrollCaptureRequest
     */
    oneway void requestImage(in Rect captureArea);

    /**
     * Inform the client that capture has ended. The client should shut down and release all
     * local resources in use and prepare for return to normal interactive usage.
     */
    oneway void endCapture();
}
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 android.view;

import android.graphics.Point;
import android.graphics.Rect;
import android.view.Surface;

import android.view.IScrollCaptureClient;

/**
 * Interface to a controller passed to the {@link IScrollCaptureClient} which provides the client an
 * asynchronous callback channel for responses.
 *
 * {@hide}
 */
interface IScrollCaptureController {
    /**
     * Scroll capture is available, and a client connect has been returned.
     *
     * @param client interface to a ScrollCaptureCallback in the window process
     * @param scrollAreaInWindow the location of scrolling in global (window) coordinate space
     */
    oneway void onClientConnected(in IScrollCaptureClient client, in Rect scrollBounds,
            in Point positionInWindow);

    /**
     * Nothing in the window can be scrolled, scroll capture not offered.
     */
    oneway void onClientUnavailable();

    /**
     * Notifies the system that the client has confirmed the request and is ready to begin providing
     * image requests.
     */
    oneway void onCaptureStarted();

    /**
     * Received a response from a capture request.
     */
    oneway void onCaptureBufferSent(long frameNumber, in Rect capturedArea);

    /**
     * Signals that the capture session has completed and the target window may be returned to
     * normal interactive use. This may be due to normal shutdown, or after a timeout or other
     * unrecoverable state change such as activity lifecycle, window visibility or focus.
     */
    oneway void onConnectionClosed();
}
+12 −4
Original line number Diff line number Diff line
@@ -21,15 +21,16 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.DragEvent;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.IScrollCaptureController;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.DisplayCutout;
import android.view.InsetsState;
import android.view.InsetsSourceControl;

import com.android.internal.os.IResultReceiver;
import android.util.MergedConfiguration;

/**
 * API back to a client window that the Window Manager uses to inform it of
@@ -139,4 +140,11 @@ oneway interface IWindow {
     * Tell the window that it is either gaining or losing pointer capture.
     */
    void dispatchPointerCaptureChanged(boolean hasCapture);

    /**
     * Called when Scroll Capture support is requested for a window.
     *
     * @param controller the controller to receive responses
     */
    void requestScrollCapture(in IScrollCaptureController controller);
}
+15 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.view.IDisplayFoldListener;
import android.view.IDisplayWindowRotationController;
import android.view.IOnKeyguardExitResult;
import android.view.IPinnedStackListener;
import android.view.IScrollCaptureController;
import android.view.RemoteAnimationAdapter;
import android.view.IRotationWatcher;
import android.view.ISystemGestureExclusionListener;
@@ -749,4 +750,18 @@ interface IWindowManager
     * @param flags see definition in SurfaceTracing.cpp
     */
    void setLayerTracingFlags(int flags);

    /**
     * Forwards a scroll capture request to the appropriate window, if available.
     *
     * @param displayId the id of the display to target
     * @param behindClient token for a window, used to filter the search to windows behind it, or
     *                     {@code null} to accept a window at any zOrder
     * @param taskId specifies the id of a task the result must belong to, or -1 to ignore task ids
     * @param controller the controller to receive results, a call to either
     *      {@link IScrollCaptureController#onClientConnected} or
     *      {@link IScrollCaptureController#onClientUnavailable}.
     */
    void requestScrollCapture(int displayId, IBinder behindClient, int taskId,
            IScrollCaptureController controller);
}
+151 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 android.view;

import android.annotation.NonNull;
import android.annotation.UiThread;
import android.graphics.Rect;

import java.util.function.Consumer;

/**
 * A ScrollCaptureCallback is responsible for providing rendered snapshots of scrolling content for
 * the scroll capture system. A single callback is responsible for providing support to a single
 * scrolling UI element. At request time, the system will select the best candidate from among all
 * callbacks registered within the window.
 * <p>
 * A callback is assigned to a View using {@link View#setScrollCaptureCallback}, or to the window as
 * {@link Window#addScrollCaptureCallback}. The point where the callback is registered defines the
 * frame of reference for the bounds measurements used.
 * <p>
 * <b>Terminology</b>
 * <dl>
 * <dt>Containing View</dt>
 * <dd>The view on which this callback is attached, or the root view of the window if the callback
 * is assigned  directly to a window.</dd>
 *
 * <dt>Scroll Bounds</dt>
 * <dd>A rectangle which describes an area within the containing view where scrolling content may
 * be positioned. This may be the Containing View bounds itself, or any rectangle within.
 * Requested by {@link #onScrollCaptureSearch}.</dd>
 *
 * <dt>Scroll Delta</dt>
 * <dd>The distance the scroll position has moved since capture started. Implementations are
 * responsible for tracking changes in vertical scroll position during capture. This is required to
 * map the capture area to the correct location, given the current scroll position.
 *
 * <dt>Capture Area</dt>
 * <dd>A rectangle which describes the area to capture, relative to scroll bounds. The vertical
 * position remains relative to the starting scroll position and any movement since ("Scroll Delta")
 * should be subtracted to locate the correct local position, and scrolled into view as necessary.
 * </dd>
 * </dl>
 *
 * @see View#setScrollCaptureHint(int)
 * @see View#setScrollCaptureCallback(ScrollCaptureCallback)
 * @see Window#addScrollCaptureCallback(ScrollCaptureCallback)
 *
 * @hide
 */
@UiThread
public interface ScrollCaptureCallback {

    /**
     * The system is searching for the appropriate scrolling container to capture and would like to
     * know the size and position of scrolling content handled by this callback.
     * <p>
     * Implementations should inset {@code containingViewBounds} to cover only the area within the
     * containing view where scrolling content may be positioned. This should cover only the content
     * which tracks with scrolling movement.
     * <p>
     * Return the updated rectangle to {@code resultConsumer}. If for any reason the scrolling
     * content is not available to capture, a {@code null} rectangle may be returned, and this view
     * will be excluded as the target for this request.
     * <p>
     * Responses received after XXXms will be discarded.
     * <p>
     * TODO: finalize timeout
     *
     * @param onReady              consumer for the updated rectangle
     */
    void onScrollCaptureSearch(@NonNull Consumer<Rect> onReady);

    /**
     * Scroll Capture has selected this callback to provide the scrolling image content.
     * <p>
     * The onReady signal should be called when ready to begin handling image requests.
     */
    void onScrollCaptureStart(@NonNull ScrollCaptureSession session, @NonNull Runnable onReady);

    /**
     * An image capture has been requested from the scrolling content.
     * <p>
     * <code>captureArea</code> contains the bounds of the image requested, relative to the
     * rectangle provided by {@link ScrollCaptureCallback#onScrollCaptureSearch}, referred to as
     * {@code scrollBounds}.
     * here.
     * <p>
     * A series of requests will step by a constant vertical amount relative to {@code
     * scrollBounds}, moving through the scrolling range of content, above and below the current
     * visible area. The rectangle's vertical position will not account for any scrolling movement
     * since capture started. Implementations therefore must track any scroll position changes and
     * subtract this distance from requests.
     * <p>
     * To handle a request, the content should be scrolled to maximize the visible area of the
     * requested rectangle. Offset {@code captureArea} again to account for any further scrolling.
     * <p>
     * Finally, clip this rectangle against scrollBounds to determine what portion, if any is
     * visible content to capture. If the rectangle is completely clipped, set it to {@link
     * Rect#setEmpty() empty} and skip the next step.
     * <p>
     * Make a copy of {@code captureArea}, transform to window coordinates and draw the window,
     * clipped to this rectangle, into the {@link ScrollCaptureSession#getSurface() surface} at
     * offset (0,0).
     * <p>
     * Finally, return the resulting {@code captureArea} using
     * {@link ScrollCaptureSession#notifyBufferSent}.
     * <p>
     * If the response is not supplied within XXXms, the session will end with a call to {@link
     * #onScrollCaptureEnd}, after which {@code session} is invalid and should be discarded.
     * <p>
     * TODO: finalize timeout
     * <p>
     *
     * @param captureArea the area to capture, a rectangle within {@code scrollBounds}
     */
    void onScrollCaptureImageRequest(
            @NonNull ScrollCaptureSession session, @NonNull Rect captureArea);

    /**
     * Signals that capture has ended. Implementations should release any temporary resources or
     * references to objects in use during the capture. Any resources obtained from the session are
     * now invalid and attempts to use them after this point may throw an exception.
     * <p>
     * The window should be returned as much as possible to its original state when capture started.
     * At a minimum, the content should be scrolled to its original position.
     * <p>
     * <code>onReady</code> should be called when the window should be made visible and
     * interactive. The system will wait up to XXXms for this call before proceeding.
     * <p>
     * TODO: finalize timeout
     *
     * @param onReady a callback to inform the system that the application has completed any
     *                cleanup and is ready to become visible
     */
    void onScrollCaptureEnd(@NonNull Runnable onReady);
}
Loading