diff options
author | Adam Powell <adamp@google.com> | 2013-07-18 19:42:41 -0700 |
---|---|---|
committer | Adam Powell <adamp@google.com> | 2013-09-05 00:55:20 +0000 |
commit | 1487466dc2ce14cccf0ff2bd2f824238aaa0044e (patch) | |
tree | 4bf31a9b198846b45f9a15b3ebc53532bb748e6b | |
parent | 7f71206d009e737e91c4c767183befe6006497c6 (diff) | |
download | frameworks_base-1487466dc2ce14cccf0ff2bd2f824238aaa0044e.zip frameworks_base-1487466dc2ce14cccf0ff2bd2f824238aaa0044e.tar.gz frameworks_base-1487466dc2ce14cccf0ff2bd2f824238aaa0044e.tar.bz2 |
Add View#cancelPendingInputEvents API
This API allows an application to cancel deferred high-level input
events already in flight. It forms one tool of several to help apps
debounce input events and prevent things like multiple startActivity
calls, FragmentTransactions, etc. from executing when only one was
desired since it's otherwise not desirable for things like click
events to fire synchronously.
Change-Id: I60b12cd5350898065f0019d616e24d779eb8cff9
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/Activity.java | 7 | ||||
-rw-r--r-- | core/java/android/app/ActivityThread.java | 7 | ||||
-rw-r--r-- | core/java/android/app/Fragment.java | 1 | ||||
-rw-r--r-- | core/java/android/app/FragmentManager.java | 1 | ||||
-rw-r--r-- | core/java/android/util/SuperNotCalledException.java | 27 | ||||
-rw-r--r-- | core/java/android/view/View.java | 66 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 11 | ||||
-rw-r--r-- | core/java/android/widget/AbsListView.java | 17 |
9 files changed, 133 insertions, 6 deletions
diff --git a/api/current.txt b/api/current.txt index e50d769..283880e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27431,6 +27431,7 @@ package android.view { method public boolean canScrollHorizontally(int); method public boolean canScrollVertically(int); method public void cancelLongPress(); + method public final void cancelPendingInputEvents(); method public boolean checkInputConnectionProxy(android.view.View); method public void clearAnimation(); method public void clearFocus(); @@ -27658,6 +27659,7 @@ package android.view { method protected void onAnimationEnd(); method protected void onAnimationStart(); method protected void onAttachedToWindow(); + method public void onCancelPendingInputEvents(); method public boolean onCheckIsTextEditor(); method protected void onConfigurationChanged(android.content.res.Configuration); method protected void onCreateContextMenu(android.view.ContextMenu); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e02410a..57686a4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -17,6 +17,7 @@ package android.app; import android.util.ArrayMap; +import android.util.SuperNotCalledException; import com.android.internal.app.ActionBarImpl; import com.android.internal.policy.PolicyManager; @@ -3436,6 +3437,12 @@ public class Activity extends ContextThemeWrapper // activity is finished, no matter what happens to it. mStartedActivity = true; } + + final View decor = mWindow != null ? mWindow.peekDecorView() : null; + if (decor != null) { + decor.cancelPendingInputEvents(); + } + // TODO Consider clearing/flushing other event sources and events for child windows. } else { if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e6960b3..018fbe0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -76,6 +76,7 @@ import android.util.Log; import android.util.LogPrinter; import android.util.PrintWriterPrinter; import android.util.Slog; +import android.util.SuperNotCalledException; import android.view.Display; import android.view.HardwareRenderer; import android.view.View; @@ -116,12 +117,6 @@ import libcore.io.IoUtils; import dalvik.system.CloseGuard; -final class SuperNotCalledException extends AndroidRuntimeException { - public SuperNotCalledException(String msg) { - super(msg); - } -} - final class RemoteServiceException extends AndroidRuntimeException { public RemoteServiceException(String msg) { super(msg); diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index f8a1d82..d626e5f 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -31,6 +31,7 @@ import android.util.AttributeSet; import android.util.DebugUtils; import android.util.Log; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index a7789d6..4371907 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -31,6 +31,7 @@ import android.util.DebugUtils; import android.util.Log; import android.util.LogWriter; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; diff --git a/core/java/android/util/SuperNotCalledException.java b/core/java/android/util/SuperNotCalledException.java new file mode 100644 index 0000000..1836142 --- /dev/null +++ b/core/java/android/util/SuperNotCalledException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013 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.util; + +/** + * @hide + */ +public final class SuperNotCalledException extends AndroidRuntimeException { + public SuperNotCalledException(String msg) { + super(msg); + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f2b3e89..398ad17 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -57,6 +57,7 @@ import android.util.LongSparseLongArray; import android.util.Pools.SynchronizedPool; import android.util.Property; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; import android.view.AccessibilityIterators.TextSegmentIterator; @@ -2204,6 +2205,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT = 0x8; + /** + * Flag indicating that an overridden method correctly called down to + * the superclass implementation as required by the API spec. + */ + static final int PFLAG3_CALLED_SUPER = 0x10; + /* End of masks for mPrivateFlags3 */ @@ -5955,6 +5962,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Invalidate too, since the default behavior for views is to be // be drawn at 50% alpha rather than to change the drawable. invalidate(true); + + if (!enabled) { + cancelPendingInputEvents(); + } } /** @@ -12364,6 +12375,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Cancel any deferred high-level input events that were previously posted to the event queue. + * + * <p>Many views post high-level events such as click handlers to the event queue + * to run deferred in order to preserve a desired user experience - clearing visible + * pressed states before executing, etc. This method will abort any events of this nature + * that are currently in flight.</p> + * + * <p>Custom views that generate their own high-level deferred input events should override + * {@link #onCancelPendingInputEvents()} and remove those pending events from the queue.</p> + * + * <p>This will also cancel pending input events for any child views.</p> + * + * <p>Note that this may not be sufficient as a debouncing strategy for clicks in all cases. + * This will not impact newer events posted after this call that may occur as a result of + * lower-level input events still waiting in the queue. If you are trying to prevent + * double-submitted events for the duration of some sort of asynchronous transaction + * you should also take other steps to protect against unexpected double inputs e.g. calling + * {@link #setEnabled(boolean) setEnabled(false)} and re-enabling the view when + * the transaction completes, tracking already submitted transaction IDs, etc.</p> + */ + public final void cancelPendingInputEvents() { + dispatchCancelPendingInputEvents(); + } + + /** + * Called by {@link #cancelPendingInputEvents()} to cancel input events in flight. + * Overridden by ViewGroup to dispatch. Package scoped to prevent app-side meddling. + */ + void dispatchCancelPendingInputEvents() { + mPrivateFlags3 &= ~PFLAG3_CALLED_SUPER; + onCancelPendingInputEvents(); + if ((mPrivateFlags3 & PFLAG3_CALLED_SUPER) != PFLAG3_CALLED_SUPER) { + throw new SuperNotCalledException("View " + getClass().getSimpleName() + + " did not call through to super.onCancelPendingInputEvents()"); + } + } + + /** + * Called as the result of a call to {@link #cancelPendingInputEvents()} on this view or + * a parent view. + * + * <p>This method is responsible for removing any pending high-level input events that were + * posted to the event queue to run later. Custom view classes that post their own deferred + * high-level events via {@link #post(Runnable)}, {@link #postDelayed(Runnable, long)} or + * {@link android.os.Handler} should override this method, call + * <code>super.onCancelPendingInputEvents()</code> and remove those callbacks as appropriate. + * </p> + */ + public void onCancelPendingInputEvents() { + removePerformClickCallback(); + cancelLongPress(); + mPrivateFlags3 |= PFLAG3_CALLED_SUPER; + } + + /** * Store this view hierarchy's frozen state into the given container. * * @param container The SparseArray in which to save the view's state. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 2d75b06..faeee3f 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3182,6 +3182,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + void dispatchCancelPendingInputEvents() { + super.dispatchCancelPendingInputEvents(); + + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = 0; i < count; i++) { + children[i].dispatchCancelPendingInputEvents(); + } + } + /** * When this property is set to true, this ViewGroup supports static transformations on * children; this causes diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index c308024..29b7cf2 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2843,6 +2843,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return new AdapterContextMenuInfo(view, position, id); } + @Override + public void onCancelPendingInputEvents() { + super.onCancelPendingInputEvents(); + if (mPerformClick != null) { + removeCallbacks(mPerformClick); + } + if (mPendingCheckForTap != null) { + removeCallbacks(mPendingCheckForTap); + } + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + if (mPendingCheckForKeyLongPress != null) { + removeCallbacks(mPendingCheckForKeyLongPress); + } + } + /** * A base class for Runnables that will check that their view is still attached to * the original window as when the Runnable was created. |