summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Adam Powell <adamp@google.com> 2019-02-28 21:11:59 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-02-28 21:11:59 +0000
commitb10113347d1c008a3ccbb02b86722a6c02ab0e18 (patch)
treea454c300e06a7606d9fa4436e70601f7e0aaafd7
parent357f00384986fa0120752d4b3b97e071fc4643e7 (diff)
parent769b8638f970c6cf62abb7ac46f80211b7c3afb6 (diff)
Merge "System gesture exclusion rects for Views"
-rw-r--r--api/current.txt4
-rw-r--r--core/java/android/view/GestureExclusionTracker.java125
-rw-r--r--core/java/android/view/SurfaceView.java2
-rw-r--r--core/java/android/view/View.java99
-rw-r--r--core/java/android/view/ViewRootImpl.java82
-rw-r--r--core/java/android/view/ViewTreeObserver.java55
-rw-r--r--graphics/java/android/graphics/RenderNode.java67
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java2
8 files changed, 402 insertions, 34 deletions
diff --git a/api/current.txt b/api/current.txt
index 3cec35fd7208..968811d85d31 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -50502,6 +50502,7 @@ package android.view {
method public android.animation.StateListAnimator getStateListAnimator();
method protected int getSuggestedMinimumHeight();
method protected int getSuggestedMinimumWidth();
+ method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
method public int getSystemUiVisibility();
method @android.view.ViewDebug.ExportedProperty public Object getTag();
method public Object getTag(int);
@@ -50840,6 +50841,7 @@ package android.view {
method public void setSelected(boolean);
method public void setSoundEffectsEnabled(boolean);
method public void setStateListAnimator(android.animation.StateListAnimator);
+ method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
method public void setSystemUiVisibility(int);
method public void setTag(Object);
method public void setTag(int, Object);
@@ -51668,6 +51670,7 @@ package android.view {
method public void addOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
method public void addOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
method public void addOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
+ method public void addOnSystemGestureExclusionRectsChangedListener(java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
method public void addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
method public void addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
@@ -51682,6 +51685,7 @@ package android.view {
method public void removeOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
method public void removeOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
+ method public void removeOnSystemGestureExclusionRectsChangedListener(java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
method public void removeOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
method public void removeOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
diff --git a/core/java/android/view/GestureExclusionTracker.java b/core/java/android/view/GestureExclusionTracker.java
new file mode 100644
index 000000000000..8eccc04fa647
--- /dev/null
+++ b/core/java/android/view/GestureExclusionTracker.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 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.Nullable;
+import android.graphics.Rect;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Used by {@link ViewRootImpl} to track system gesture exclusion rects reported by views.
+ */
+class GestureExclusionTracker {
+ private boolean mGestureExclusionViewsChanged = false;
+ private List<GestureExclusionViewInfo> mGestureExclusionViewInfos = new ArrayList<>();
+ private List<Rect> mGestureExclusionRects = Collections.emptyList();
+
+ public void updateRectsForView(@NonNull View view) {
+ boolean found = false;
+ final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
+ while (i.hasNext()) {
+ final GestureExclusionViewInfo info = i.next();
+ final View v = info.getView();
+ if (v == null || !v.isAttachedToWindow()) {
+ mGestureExclusionViewsChanged = true;
+ i.remove();
+ continue;
+ }
+ if (v == view) {
+ found = true;
+ info.mDirty = true;
+ break;
+ }
+ }
+ if (!found && view.isAttachedToWindow()) {
+ mGestureExclusionViewInfos.add(new GestureExclusionViewInfo(view));
+ mGestureExclusionViewsChanged = true;
+ }
+ }
+
+ @Nullable
+ public List<Rect> computeChangedRects() {
+ boolean changed = false;
+ final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
+ final List<Rect> rects = new ArrayList<>();
+ while (i.hasNext()) {
+ final GestureExclusionViewInfo info = i.next();
+ switch (info.update()) {
+ case GestureExclusionViewInfo.CHANGED:
+ changed = true;
+ // Deliberate fall-through
+ case GestureExclusionViewInfo.UNCHANGED:
+ rects.addAll(info.mExclusionRects);
+ break;
+ case GestureExclusionViewInfo.GONE:
+ mGestureExclusionViewsChanged = true;
+ i.remove();
+ break;
+ }
+ }
+ if (changed || mGestureExclusionViewsChanged) {
+ mGestureExclusionViewsChanged = false;
+ if (!mGestureExclusionRects.equals(rects)) {
+ mGestureExclusionRects = rects;
+ return rects;
+ }
+ }
+ return null;
+ }
+
+ private static class GestureExclusionViewInfo {
+ public static final int CHANGED = 0;
+ public static final int UNCHANGED = 1;
+ public static final int GONE = 2;
+
+ private final WeakReference<View> mView;
+ boolean mDirty = true;
+ List<Rect> mExclusionRects = Collections.emptyList();
+
+ GestureExclusionViewInfo(View view) {
+ mView = new WeakReference<>(view);
+ }
+
+ public View getView() {
+ return mView.get();
+ }
+
+ public int update() {
+ final View excludedView = getView();
+ if (excludedView == null || !excludedView.isAttachedToWindow()) return GONE;
+ final List<Rect> localRects = excludedView.getSystemGestureExclusionRects();
+ final List<Rect> newRects = new ArrayList<>(localRects.size());
+ for (Rect src : localRects) {
+ Rect mappedRect = new Rect(src);
+ ViewParent p = excludedView.getParent();
+ if (p != null && p.getChildVisibleRect(excludedView, mappedRect, null)) {
+ newRects.add(mappedRect);
+ }
+ }
+
+ if (mExclusionRects.equals(localRects)) return UNCHANGED;
+ mExclusionRects = newRects;
+ return CHANGED;
+ }
+ }
+}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index fe9aa234ba54..ee8d66313ea2 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -210,7 +210,7 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb
public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mRenderNode.requestPositionUpdates(mPositionListener);
+ mRenderNode.addPositionUpdateListener(mPositionListener);
setWillNotDraw(true);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2d452a6b7c83..cc585b0294c7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4597,6 +4597,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
+
+ /**
+ * This lives here since it's only valid for interactive views.
+ */
+ private List<Rect> mSystemGestureExclusionRects;
+
+ /**
+ * Used to track {@link #mSystemGestureExclusionRects}
+ */
+ public RenderNode.PositionUpdateListener mPositionUpdateListener;
}
@UnsupportedAppUsage
@@ -10963,6 +10973,95 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Sets a list of areas within this view's post-layout coordinate space where the system
+ * should not intercept touch or other pointing device gestures. <em>This method should
+ * be called by {@link #onLayout(boolean, int, int, int, int)} or {@link #onDraw(Canvas)}.</em>
+ *
+ * <p>Use this to tell the system which specific sub-areas of a view need to receive gesture
+ * input in order to function correctly in the presence of global system gestures that may
+ * conflict. For example, if the system wishes to capture swipe-in-from-screen-edge gestures
+ * to provide system-level navigation functionality, a view such as a navigation drawer
+ * container can mark the left (or starting) edge of itself as requiring gesture capture
+ * priority using this API. The system may then choose to relax its own gesture recognition
+ * to allow the app to consume the user's gesture. It is not necessary for an app to register
+ * exclusion rects for broadly spanning regions such as the entirety of a
+ * <code>ScrollView</code> or for simple press and release click targets such as
+ * <code>Button</code>. Mark an exclusion rect when interacting with a view requires
+ * a precision touch gesture in a small area in either the X or Y dimension, such as
+ * an edge swipe or dragging a <code>SeekBar</code> thumb.</p>
+ *
+ * <p>Do not modify the provided list after this method is called.</p>
+ *
+ * @param rects A list of precision gesture regions that this view needs to function correctly
+ */
+ public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) {
+ if (rects.isEmpty() && mListenerInfo == null) return;
+
+ final ListenerInfo info = getListenerInfo();
+ if (rects.isEmpty()) {
+ info.mSystemGestureExclusionRects = null;
+ if (info.mPositionUpdateListener != null) {
+ mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
+ }
+ } else {
+ info.mSystemGestureExclusionRects = rects;
+ if (info.mPositionUpdateListener == null) {
+ info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() {
+ @Override
+ public void positionChanged(long n, int l, int t, int r, int b) {
+ postUpdateSystemGestureExclusionRects();
+ }
+
+ @Override
+ public void positionLost(long frameNumber) {
+ postUpdateSystemGestureExclusionRects();
+ }
+ };
+ mRenderNode.addPositionUpdateListener(info.mPositionUpdateListener);
+ }
+ }
+ postUpdateSystemGestureExclusionRects();
+ }
+
+ /**
+ * WARNING: this can be called by a hwui worker thread, not just the UI thread!
+ */
+ void postUpdateSystemGestureExclusionRects() {
+ // Potentially racey from a background thread. It's ok if it's not perfect.
+ final Handler h = getHandler();
+ if (h != null) {
+ h.postAtFrontOfQueue(this::updateSystemGestureExclusionRects);
+ }
+ }
+
+ void updateSystemGestureExclusionRects() {
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ ai.mViewRootImpl.updateSystemGestureExclusionRectsForView(this);
+ }
+ }
+
+ /**
+ * Retrieve the list of areas within this view's post-layout coordinate space where the system
+ * should not intercept touch or other pointing device gestures.
+ *
+ * <p>Do not modify the returned list.</p>
+ *
+ * @return the list set by {@link #setSystemGestureExclusionRects(List)}
+ */
+ @NonNull
+ public List<Rect> getSystemGestureExclusionRects() {
+ final ListenerInfo info = mListenerInfo;
+ if (info != null) {
+ final List<Rect> list = info.mSystemGestureExclusionRects;
+ if (list != null) {
+ return list;
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
* Compute the view's coordinate within the surface.
*
* <p>Computes the coordinates of this view in its surface. The argument
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ad0aaa6edff6..65b1abd715d9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -605,6 +605,8 @@ public final class ViewRootImpl implements ViewParent,
private final InsetsController mInsetsController = new InsetsController(this);
+ private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
+
static final class SystemUiVisibilityInfo {
int seq;
int globalVisibility;
@@ -3977,6 +3979,20 @@ public final class ViewRootImpl implements ViewParent,
return mAttachInfo.mAccessibilityFocusDrawable;
}
+ void updateSystemGestureExclusionRectsForView(View view) {
+ mGestureExclusionTracker.updateRectsForView(view);
+ mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
+ }
+
+ void systemGestureExclusionChanged() {
+ final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects();
+ if (rectsForWindowManager != null) {
+ // TODO Send to WM
+ mAttachInfo.mTreeObserver
+ .dispatchOnSystemGestureExclusionRectsChanged(rectsForWindowManager);
+ }
+ }
+
/**
* Requests that the root render node is invalidated next time we perform a draw, such that
* {@link WindowCallbacks#onPostDraw} gets called.
@@ -4433,35 +4449,36 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private final static int MSG_INVALIDATE = 1;
- private final static int MSG_INVALIDATE_RECT = 2;
- private final static int MSG_DIE = 3;
- private final static int MSG_RESIZED = 4;
- private final static int MSG_RESIZED_REPORT = 5;
- private final static int MSG_WINDOW_FOCUS_CHANGED = 6;
- private final static int MSG_DISPATCH_INPUT_EVENT = 7;
- private final static int MSG_DISPATCH_APP_VISIBILITY = 8;
- private final static int MSG_DISPATCH_GET_NEW_SURFACE = 9;
- private final static int MSG_DISPATCH_KEY_FROM_IME = 11;
- private final static int MSG_DISPATCH_KEY_FROM_AUTOFILL = 12;
- private final static int MSG_CHECK_FOCUS = 13;
- private final static int MSG_CLOSE_SYSTEM_DIALOGS = 14;
- private final static int MSG_DISPATCH_DRAG_EVENT = 15;
- private final static int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
- private final static int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
- private final static int MSG_UPDATE_CONFIGURATION = 18;
- private final static int MSG_PROCESS_INPUT_EVENTS = 19;
- private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
- private final static int MSG_INVALIDATE_WORLD = 22;
- private final static int MSG_WINDOW_MOVED = 23;
- private final static int MSG_SYNTHESIZE_INPUT_EVENT = 24;
- private final static int MSG_DISPATCH_WINDOW_SHOWN = 25;
- private final static int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
- private final static int MSG_UPDATE_POINTER_ICON = 27;
- private final static int MSG_POINTER_CAPTURE_CHANGED = 28;
- private final static int MSG_DRAW_FINISHED = 29;
- private final static int MSG_INSETS_CHANGED = 30;
- private final static int MSG_INSETS_CONTROL_CHANGED = 31;
+ private static final int MSG_INVALIDATE = 1;
+ private static final int MSG_INVALIDATE_RECT = 2;
+ private static final int MSG_DIE = 3;
+ private static final int MSG_RESIZED = 4;
+ private static final int MSG_RESIZED_REPORT = 5;
+ private static final int MSG_WINDOW_FOCUS_CHANGED = 6;
+ private static final int MSG_DISPATCH_INPUT_EVENT = 7;
+ private static final int MSG_DISPATCH_APP_VISIBILITY = 8;
+ private static final int MSG_DISPATCH_GET_NEW_SURFACE = 9;
+ private static final int MSG_DISPATCH_KEY_FROM_IME = 11;
+ private static final int MSG_DISPATCH_KEY_FROM_AUTOFILL = 12;
+ private static final int MSG_CHECK_FOCUS = 13;
+ private static final int MSG_CLOSE_SYSTEM_DIALOGS = 14;
+ private static final int MSG_DISPATCH_DRAG_EVENT = 15;
+ private static final int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
+ private static final int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
+ private static final int MSG_UPDATE_CONFIGURATION = 18;
+ private static final int MSG_PROCESS_INPUT_EVENTS = 19;
+ private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
+ private static final int MSG_INVALIDATE_WORLD = 22;
+ private static final int MSG_WINDOW_MOVED = 23;
+ private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24;
+ private static final int MSG_DISPATCH_WINDOW_SHOWN = 25;
+ private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
+ private static final int MSG_UPDATE_POINTER_ICON = 27;
+ private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
+ private static final int MSG_DRAW_FINISHED = 29;
+ private static final int MSG_INSETS_CHANGED = 30;
+ private static final int MSG_INSETS_CONTROL_CHANGED = 31;
+ private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
final class ViewRootHandler extends Handler {
@Override
@@ -4519,6 +4536,10 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_DRAW_FINISHED";
case MSG_INSETS_CHANGED:
return "MSG_INSETS_CHANGED";
+ case MSG_INSETS_CONTROL_CHANGED:
+ return "MSG_INSETS_CONTROL_CHANGED";
+ case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED:
+ return "MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED";
}
return super.getMessageName(message);
}
@@ -4750,6 +4771,9 @@ public final class ViewRootImpl implements ViewParent,
case MSG_DRAW_FINISHED: {
pendingDrawFinished();
} break;
+ case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
+ systemGestureExclusionChanged();
+ } break;
}
}
}
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 763ce4f45b01..2896bd049e7c 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -26,7 +26,9 @@ import android.os.Build;
import android.util.Log;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
/**
* A view tree observer is used to register listeners that can be notified of global
@@ -57,6 +59,7 @@ public final class ViewTreeObserver {
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
+ private CopyOnWriteArray<Consumer<List<Rect>>> mGestureExclusionListeners;
// These listeners cannot be mutated during dispatch
private boolean mInDispatchOnDraw;
@@ -450,6 +453,14 @@ public final class ViewTreeObserver {
}
}
+ if (observer.mGestureExclusionListeners != null) {
+ if (mGestureExclusionListeners != null) {
+ mGestureExclusionListeners.addAll(observer.mGestureExclusionListeners);
+ } else {
+ mGestureExclusionListeners = observer.mGestureExclusionListeners;
+ }
+ }
+
observer.kill();
}
@@ -913,6 +924,35 @@ public final class ViewTreeObserver {
mOnEnterAnimationCompleteListeners.remove(listener);
}
+ /**
+ * Add a listener to be notified when the tree's <em>transformed</em> gesture exclusion rects
+ * change. This could be the result of an animation or other layout change, or a view calling
+ * {@link View#setSystemGestureExclusionRects(List)}.
+ *
+ * @param listener listener to add
+ * @see View#setSystemGestureExclusionRects(List)
+ */
+ public void addOnSystemGestureExclusionRectsChangedListener(Consumer<List<Rect>> listener) {
+ checkIsAlive();
+ if (mGestureExclusionListeners == null) {
+ mGestureExclusionListeners = new CopyOnWriteArray<>();
+ }
+ mGestureExclusionListeners.add(listener);
+ }
+
+ /**
+ * Unsubscribe the given listener from gesture exclusion rect changes.
+ * @see #addOnSystemGestureExclusionRectsChangedListener(Consumer)
+ * @see View#setSystemGestureExclusionRects(List)
+ */
+ public void removeOnSystemGestureExclusionRectsChangedListener(Consumer<List<Rect>> listener) {
+ checkIsAlive();
+ if (mGestureExclusionListeners == null) {
+ return;
+ }
+ mGestureExclusionListeners.remove(listener);
+ }
+
private void checkIsAlive() {
if (!mAlive) {
throw new IllegalStateException("This ViewTreeObserver is not alive, call "
@@ -1178,6 +1218,21 @@ public final class ViewTreeObserver {
}
}
+ void dispatchOnSystemGestureExclusionRectsChanged(@NonNull List<Rect> rects) {
+ final CopyOnWriteArray<Consumer<List<Rect>>> listeners = mGestureExclusionListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<Consumer<List<Rect>>> access = listeners.start();
+ try {
+ final int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).accept(rects);
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ }
+
/**
* Copy on write array. This array is not thread safe, and only one loop can
* iterate over this array at any given time. This class avoids allocations
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index e98879d0c5bd..42b6acd3b25a 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -27,6 +27,8 @@ import android.view.RenderNodeAnimator;
import android.view.Surface;
import android.view.View;
+import com.android.internal.util.ArrayUtils;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -179,6 +181,10 @@ public final class RenderNode {
private final AnimationHost mAnimationHost;
private RecordingCanvas mCurrentRecordingCanvas;
+ // Will be null if not currently registered
+ @Nullable
+ private CompositePositionUpdateListener mCompositePositionUpdateListener;
+
/**
* Creates a new RenderNode that can be used to record batches of
* drawing operations, and store / apply render properties when drawn.
@@ -248,15 +254,70 @@ public final class RenderNode {
}
+ private static final class CompositePositionUpdateListener implements PositionUpdateListener {
+ private final PositionUpdateListener[] mListeners;
+
+ CompositePositionUpdateListener(PositionUpdateListener... listeners) {
+ mListeners = listeners;
+ }
+
+ public CompositePositionUpdateListener with(PositionUpdateListener listener) {
+ return new CompositePositionUpdateListener(
+ ArrayUtils.appendElement(PositionUpdateListener.class, mListeners, listener));
+ }
+
+ public CompositePositionUpdateListener without(PositionUpdateListener listener) {
+ return new CompositePositionUpdateListener(
+ ArrayUtils.removeElement(PositionUpdateListener.class, mListeners, listener));
+ }
+
+ @Override
+ public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
+ for (PositionUpdateListener pul : mListeners) {
+ pul.positionChanged(frameNumber, left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void positionLost(long frameNumber) {
+ for (PositionUpdateListener pul : mListeners) {
+ pul.positionLost(frameNumber);
+ }
+ }
+ }
+
/**
- * Enable callbacks for position changes.
+ * Enable callbacks for position changes. Call only from the UI thread or with
+ * external synchronization.
*
* @hide
*/
- public void requestPositionUpdates(PositionUpdateListener listener) {
- nRequestPositionUpdates(mNativeRenderNode, listener);
+ public void addPositionUpdateListener(@NonNull PositionUpdateListener listener) {
+ CompositePositionUpdateListener comp = mCompositePositionUpdateListener;
+ if (comp == null) {
+ comp = new CompositePositionUpdateListener(listener);
+ } else {
+ comp = comp.with(listener);
+ }
+ mCompositePositionUpdateListener = comp;
+ nRequestPositionUpdates(mNativeRenderNode, comp);
}
+ /**
+ * Disable a callback for position changes. Call only from the UI thread or with
+ * external synchronization.
+ *
+ * @param listener Callback to remove
+ * @hide
+ */
+ public void removePositionUpdateListener(@NonNull PositionUpdateListener listener) {
+ CompositePositionUpdateListener comp = mCompositePositionUpdateListener;
+ if (comp != null) {
+ comp = comp.without(listener);
+ mCompositePositionUpdateListener = comp;
+ nRequestPositionUpdates(mNativeRenderNode, comp);
+ }
+ }
/**
* Starts recording a display list for the render node. All
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
index 316aad343d19..818d899413de 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
@@ -53,7 +53,7 @@ public class PositionListenerActivity extends Activity {
MyPositionReporter(Context c) {
super(c);
mNode = new RenderNode("positionListener");
- mNode.requestPositionUpdates(this);
+ mNode.addPositionUpdateListener(this);
setTextAlignment(TEXT_ALIGNMENT_VIEW_START);
}