summaryrefslogtreecommitdiff
path: root/core/java/android/view/ViewRootImpl.java
diff options
context:
space:
mode:
authorMark Renouf <mrenouf@google.com>2020-04-17 12:22:50 -0400
committerMark Renouf <mrenouf@google.com>2020-04-20 15:07:59 +0000
commita927929d99d76440e542f7c80e38119518e223fd (patch)
tree1bd4627e8ee135068e412164a7e7dcd52c41f7fd /core/java/android/view/ViewRootImpl.java
parent31b94bfc1e3dadf9794641c738ced9eb71617dc7 (diff)
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
Diffstat (limited to 'core/java/android/view/ViewRootImpl.java')
-rw-r--r--core/java/android/view/ViewRootImpl.java146
1 files changed, 146 insertions, 0 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ed1edc3bd526..68a185d36315 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -206,6 +206,7 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV;
private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV;
private static final boolean DEBUG_CONTENT_CAPTURE = false || LOCAL_LOGV;
+ private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV;
/**
* Set to false if we do not want to use the multi threaded renderer even though
@@ -653,6 +654,8 @@ public final class ViewRootImpl implements ViewParent,
private final InsetsController mInsetsController;
private final ImeFocusController mImeFocusController;
+ private ScrollCaptureClient mScrollCaptureClient;
+
/**
* @return {@link ImeFocusController} for this instance.
*/
@@ -661,6 +664,11 @@ public final class ViewRootImpl implements ViewParent,
return mImeFocusController;
}
+ /** @return The current {@link ScrollCaptureClient} for this instance, if any is active. */
+ @Nullable
+ public ScrollCaptureClient getScrollCaptureClient() {
+ return mScrollCaptureClient;
+ }
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
@@ -694,6 +702,8 @@ public final class ViewRootImpl implements ViewParent,
// draw returns.
private SurfaceControl.Transaction mRtBLASTSyncTransaction = new SurfaceControl.Transaction();
+ private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
+
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
@@ -4778,6 +4788,7 @@ public final class ViewRootImpl implements ViewParent,
private static final int MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED = 33;
private static final int MSG_SHOW_INSETS = 34;
private static final int MSG_HIDE_INSETS = 35;
+ private static final int MSG_REQUEST_SCROLL_CAPTURE = 36;
final class ViewRootHandler extends Handler {
@@ -5080,6 +5091,9 @@ public final class ViewRootImpl implements ViewParent,
case MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED: {
updateLocationInParentDisplay(msg.arg1, msg.arg2);
} break;
+ case MSG_REQUEST_SCROLL_CAPTURE:
+ handleScrollCaptureRequest((IScrollCaptureController) msg.obj);
+ break;
}
}
}
@@ -8789,6 +8803,131 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
+ /**
+ * Adds a scroll capture callback to this window.
+ *
+ * @param callback the callback to add
+ */
+ public void addScrollCaptureCallback(ScrollCaptureCallback callback) {
+ if (mRootScrollCaptureCallbacks == null) {
+ mRootScrollCaptureCallbacks = new HashSet<>();
+ }
+ mRootScrollCaptureCallbacks.add(callback);
+ }
+
+ /**
+ * Removes a scroll capture callback from this window.
+ *
+ * @param callback the callback to remove
+ */
+ public void removeScrollCaptureCallback(ScrollCaptureCallback callback) {
+ if (mRootScrollCaptureCallbacks != null) {
+ mRootScrollCaptureCallbacks.remove(callback);
+ if (mRootScrollCaptureCallbacks.isEmpty()) {
+ mRootScrollCaptureCallbacks = null;
+ }
+ }
+ }
+
+ /**
+ * Dispatches a scroll capture request to the view hierarchy on the ui thread.
+ *
+ * @param controller the controller to receive replies
+ */
+ public void dispatchScrollCaptureRequest(@NonNull IScrollCaptureController controller) {
+ mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, controller).sendToTarget();
+ }
+
+ /**
+ * Collect and include any ScrollCaptureCallback instances registered with the window.
+ *
+ * @see #addScrollCaptureCallback(ScrollCaptureCallback)
+ * @param targets the search queue for targets
+ */
+ private void collectRootScrollCaptureTargets(Queue<ScrollCaptureTarget> targets) {
+ for (ScrollCaptureCallback cb : mRootScrollCaptureCallbacks) {
+ // Add to the list for consideration
+ Point offset = new Point(mView.getLeft(), mView.getTop());
+ Rect rect = new Rect(0, 0, mView.getWidth(), mView.getHeight());
+ targets.add(new ScrollCaptureTarget(mView, rect, offset, cb));
+ }
+ }
+
+ /**
+ * Handles an inbound request for scroll capture from the system. If a client is not already
+ * active, a search will be dispatched through the view tree to locate scrolling content.
+ * <p>
+ * Either {@link IScrollCaptureController#onClientConnected(IScrollCaptureClient, Rect,
+ * Point)} or {@link IScrollCaptureController#onClientUnavailable()} will be returned
+ * depending on the results of the search.
+ *
+ * @param controller the interface to the system controller
+ * @see ScrollCaptureTargetResolver
+ */
+ private void handleScrollCaptureRequest(@NonNull IScrollCaptureController controller) {
+ LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+
+ // Window (root) level callbacks
+ collectRootScrollCaptureTargets(targetList);
+
+ // Search through View-tree
+ View rootView = getView();
+ Point point = new Point();
+ Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight());
+ getChildVisibleRect(rootView, rect, point);
+ rootView.dispatchScrollCaptureSearch(rect, point, targetList);
+
+ // No-op path. Scroll capture not offered for this window.
+ if (targetList.isEmpty()) {
+ dispatchScrollCaptureSearchResult(controller, null);
+ return;
+ }
+
+ // Request scrollBounds from each of the targets.
+ // Continues with the consumer once all responses are consumed, or the timeout expires.
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetList);
+ resolver.start(mHandler, 1000,
+ (selected) -> dispatchScrollCaptureSearchResult(controller, selected));
+ }
+
+ /** Called by {@link #handleScrollCaptureRequest} when a result is returned */
+ private void dispatchScrollCaptureSearchResult(
+ @NonNull IScrollCaptureController controller,
+ @Nullable ScrollCaptureTarget selectedTarget) {
+
+ // If timeout or no eligible targets found.
+ if (selectedTarget == null) {
+ try {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.d(TAG, "scrollCaptureSearch returned no targets available.");
+ }
+ controller.onClientUnavailable();
+ } catch (RemoteException e) {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.w(TAG, "Failed to notify controller of scroll capture search result.", e);
+ }
+ }
+ return;
+ }
+
+ // Create a client instance and return it to the caller
+ mScrollCaptureClient = new ScrollCaptureClient(selectedTarget, controller);
+ try {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.d(TAG, "scrollCaptureSearch returning client: " + getScrollCaptureClient());
+ }
+ controller.onClientConnected(
+ mScrollCaptureClient,
+ selectedTarget.getScrollBounds(),
+ selectedTarget.getPositionInWindow());
+ } catch (RemoteException e) {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.w(TAG, "Failed to notify controller of scroll capture search result.", e);
+ }
+ mScrollCaptureClient.disconnect();
+ mScrollCaptureClient = null;
+ }
+ }
private void reportNextDraw() {
if (mReportNextDraw == false) {
@@ -9091,6 +9230,13 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ @Override
+ public void requestScrollCapture(IScrollCaptureController controller) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchScrollCaptureRequest(controller);
+ }
+ }
}
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {