summaryrefslogtreecommitdiff
path: root/android/view
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
commit10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch)
tree8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/view
parent677516fb6b6f207d373984757d3d9450474b6b00 (diff)
downloadandroid-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \ --bid 4335822 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4335822.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/view')
-rw-r--r--android/view/AbsSavedState.java105
-rw-r--r--android/view/AccessibilityInteractionController.java1390
-rw-r--r--android/view/AccessibilityIterators.java322
-rw-r--r--android/view/ActionMode.java371
-rw-r--r--android/view/ActionProvider.java264
-rw-r--r--android/view/AppTransitionAnimationSpec.java60
-rw-r--r--android/view/AttachInfo_Accessor.java58
-rw-r--r--android/view/BatchedInputEventReceiver.java82
-rw-r--r--android/view/BridgeInflater.java498
-rw-r--r--android/view/Choreographer.java988
-rw-r--r--android/view/Choreographer_Delegate.java93
-rw-r--r--android/view/CollapsibleActionView.java41
-rw-r--r--android/view/ContextMenu.java99
-rw-r--r--android/view/ContextThemeWrapper.java201
-rw-r--r--android/view/Display.java1441
-rw-r--r--android/view/DisplayAdjustments.java91
-rw-r--r--android/view/DisplayEventReceiver.java179
-rw-r--r--android/view/DisplayInfo.java702
-rw-r--r--android/view/DisplayListCanvas.java267
-rw-r--r--android/view/Display_Delegate.java36
-rw-r--r--android/view/DragAndDropPermissions.java148
-rw-r--r--android/view/DragEvent.java551
-rw-r--r--android/view/FallbackEventHandler.java26
-rw-r--r--android/view/FocusFinder.java983
-rw-r--r--android/view/FocusFinderHelper.java59
-rw-r--r--android/view/FrameInfo.java118
-rw-r--r--android/view/FrameMetrics.java307
-rw-r--r--android/view/FrameMetricsObserver.java72
-rw-r--r--android/view/FrameStats.java97
-rw-r--r--android/view/GestureDetector.java780
-rw-r--r--android/view/GhostView.java344
-rw-r--r--android/view/Gravity.java443
-rw-r--r--android/view/HandlerActionQueue.java125
-rw-r--r--android/view/HandlerActionQueue_Delegate.java37
-rw-r--r--android/view/HapticFeedbackConstants.java99
-rw-r--r--android/view/HardwareLayer.java153
-rw-r--r--android/view/IWindowManagerImpl.java546
-rw-r--r--android/view/InflateException.java40
-rw-r--r--android/view/InputChannel.java168
-rw-r--r--android/view/InputDevice.java1045
-rw-r--r--android/view/InputEvent.java242
-rw-r--r--android/view/InputEventConsistencyVerifier.java804
-rw-r--r--android/view/InputEventReceiver.java199
-rw-r--r--android/view/InputEventSender.java143
-rw-r--r--android/view/InputFilter.java255
-rw-r--r--android/view/InputQueue.java162
-rw-r--r--android/view/KeyCharacterMap.java784
-rw-r--r--android/view/KeyEvent.java2993
-rw-r--r--android/view/KeyboardShortcutGroup.java137
-rw-r--r--android/view/KeyboardShortcutInfo.java168
-rw-r--r--android/view/LayoutInflater.java1098
-rw-r--r--android/view/LayoutInflater_Delegate.java236
-rw-r--r--android/view/MagnificationSpec.java162
-rw-r--r--android/view/Menu.java460
-rw-r--r--android/view/MenuInflater.java570
-rw-r--r--android/view/MenuInflater_Delegate.java72
-rw-r--r--android/view/MenuItem.java817
-rw-r--r--android/view/MotionEvent.java3827
-rw-r--r--android/view/NotificationHeaderView.java457
-rw-r--r--android/view/OrientationEventListener.java173
-rw-r--r--android/view/OrientationListener.java110
-rw-r--r--android/view/PixelCopy.java290
-rw-r--r--android/view/PointerIcon.java535
-rw-r--r--android/view/PointerIcon_Delegate.java33
-rw-r--r--android/view/RecordingCanvas.java638
-rw-r--r--android/view/RectShadowPainter.java194
-rw-r--r--android/view/RemotableViewMethod.java38
-rw-r--r--android/view/RenderNode.java977
-rw-r--r--android/view/RenderNodeAnimator.java519
-rw-r--r--android/view/RenderNodeAnimatorSetHelper.java48
-rw-r--r--android/view/RenderNode_Delegate.java335
-rw-r--r--android/view/RoundScrollbarRenderer.java123
-rw-r--r--android/view/ScaleGestureDetector.java575
-rw-r--r--android/view/SearchEvent.java38
-rw-r--r--android/view/ShadowPainter.java422
-rw-r--r--android/view/SoundEffectConstants.java59
-rw-r--r--android/view/SubMenu.java111
-rw-r--r--android/view/Surface.java858
-rw-r--r--android/view/SurfaceControl.java938
-rw-r--r--android/view/SurfaceHolder.java323
-rw-r--r--android/view/SurfaceSession.java64
-rw-r--r--android/view/SurfaceView.java1224
-rw-r--r--android/view/TextureView.java847
-rw-r--r--android/view/ThreadedRenderer.java1075
-rw-r--r--android/view/TouchDelegate.java150
-rw-r--r--android/view/VelocityTracker.java301
-rw-r--r--android/view/View.java26480
-rw-r--r--android/view/ViewAnimationUtils.java73
-rw-r--r--android/view/ViewConfiguration.java932
-rw-r--r--android/view/ViewConfiguration_Accessor.java29
-rw-r--r--android/view/ViewDebug.java1677
-rw-r--r--android/view/ViewGroup.java8528
-rw-r--r--android/view/ViewGroupOverlay.java86
-rw-r--r--android/view/ViewGroup_Delegate.java158
-rw-r--r--android/view/ViewHierarchyEncoder.java201
-rw-r--r--android/view/ViewManager.java37
-rw-r--r--android/view/ViewOutlineProvider.java88
-rw-r--r--android/view/ViewOverlay.java366
-rw-r--r--android/view/ViewParent.java663
-rw-r--r--android/view/ViewPerfTest.java58
-rw-r--r--android/view/ViewPropertyAnimator.java1196
-rw-r--r--android/view/ViewPropertyAnimatorRT.java138
-rw-r--r--android/view/ViewRootImpl.java7976
-rw-r--r--android/view/ViewRootImpl_Accessor.java26
-rw-r--r--android/view/ViewRootImpl_Delegate.java34
-rw-r--r--android/view/ViewShowHidePerfTest.java253
-rw-r--r--android/view/ViewStructure.java449
-rw-r--r--android/view/ViewStub.java362
-rw-r--r--android/view/ViewTreeObserver.java1196
-rw-r--r--android/view/View_Delegate.java47
-rw-r--r--android/view/Window.java2331
-rw-r--r--android/view/WindowAnimationFrameStats.java94
-rw-r--r--android/view/WindowCallback.java144
-rw-r--r--android/view/WindowCallbackWrapper.java167
-rw-r--r--android/view/WindowCallbacks.java86
-rw-r--r--android/view/WindowContentFrameStats.java152
-rw-r--r--android/view/WindowId.java224
-rw-r--r--android/view/WindowInfo.java185
-rw-r--r--android/view/WindowInsets.java501
-rw-r--r--android/view/WindowManager.java2638
-rw-r--r--android/view/WindowManagerGlobal.java648
-rw-r--r--android/view/WindowManagerGlobal_Delegate.java43
-rw-r--r--android/view/WindowManagerImpl.java158
-rw-r--r--android/view/WindowManagerInternal.java357
-rw-r--r--android/view/WindowManagerPolicy.java1753
-rw-r--r--android/view/accessibility/AccessibilityCache.java567
-rw-r--r--android/view/accessibility/AccessibilityEvent.java1328
-rw-r--r--android/view/accessibility/AccessibilityEventSource.java61
-rw-r--r--android/view/accessibility/AccessibilityInteractionClient.java825
-rw-r--r--android/view/accessibility/AccessibilityManager.java1138
-rw-r--r--android/view/accessibility/AccessibilityNodeInfo.java4681
-rw-r--r--android/view/accessibility/AccessibilityNodeProvider.java176
-rw-r--r--android/view/accessibility/AccessibilityRecord.java915
-rw-r--r--android/view/accessibility/AccessibilityRequestPreparer.java122
-rw-r--r--android/view/accessibility/AccessibilityWindowInfo.java742
-rw-r--r--android/view/accessibility/CaptioningManager.java555
-rw-r--r--android/view/animation/AccelerateDecelerateInterpolator.java49
-rw-r--r--android/view/animation/AccelerateInterpolator.java90
-rw-r--r--android/view/animation/AlphaAnimation.java89
-rw-r--r--android/view/animation/Animation.java1169
-rw-r--r--android/view/animation/AnimationSet.java524
-rw-r--r--android/view/animation/AnimationUtils.java402
-rw-r--r--android/view/animation/AnticipateInterpolator.java78
-rw-r--r--android/view/animation/AnticipateOvershootInterpolator.java108
-rw-r--r--android/view/animation/BaseInterpolator.java39
-rw-r--r--android/view/animation/BounceInterpolator.java61
-rw-r--r--android/view/animation/ClipRectAnimation.java68
-rw-r--r--android/view/animation/CycleInterpolator.java70
-rw-r--r--android/view/animation/DecelerateInterpolator.java86
-rw-r--r--android/view/animation/GridLayoutAnimationController.java424
-rw-r--r--android/view/animation/Interpolator.java31
-rw-r--r--android/view/animation/LayoutAnimationController.java437
-rw-r--r--android/view/animation/LinearInterpolator.java47
-rw-r--r--android/view/animation/OvershootInterpolator.java81
-rw-r--r--android/view/animation/PathInterpolator.java243
-rw-r--r--android/view/animation/RotateAnimation.java183
-rw-r--r--android/view/animation/ScaleAnimation.java289
-rw-r--r--android/view/animation/Transformation.java247
-rw-r--r--android/view/animation/TranslateAnimation.java178
-rw-r--r--android/view/animation/TranslateXAnimation.java55
-rw-r--r--android/view/animation/TranslateYAnimation.java55
-rw-r--r--android/view/autofill/AutofillId.java132
-rw-r--r--android/view/autofill/AutofillManager.java1830
-rw-r--r--android/view/autofill/AutofillManagerInternal.java29
-rw-r--r--android/view/autofill/AutofillPopupWindow.java343
-rw-r--r--android/view/autofill/AutofillValue.java293
-rw-r--r--android/view/autofill/Helper.java56
-rw-r--r--android/view/autofill/ParcelableMap.java73
-rw-r--r--android/view/inputmethod/BaseInputConnection.java860
-rw-r--r--android/view/inputmethod/CompletionInfo.java170
-rw-r--r--android/view/inputmethod/CorrectionInfo.java104
-rw-r--r--android/view/inputmethod/CursorAnchorInfo.java579
-rw-r--r--android/view/inputmethod/EditorInfo.java597
-rw-r--r--android/view/inputmethod/ExtractedText.java130
-rw-r--r--android/view/inputmethod/ExtractedTextRequest.java85
-rw-r--r--android/view/inputmethod/InputBinding.java151
-rw-r--r--android/view/inputmethod/InputConnection.java902
-rw-r--r--android/view/inputmethod/InputConnectionInspector.java274
-rw-r--r--android/view/inputmethod/InputConnectionWrapper.java280
-rw-r--r--android/view/inputmethod/InputContentInfo.java280
-rw-r--r--android/view/inputmethod/InputMethod.java268
-rw-r--r--android/view/inputmethod/InputMethodInfo.java536
-rw-r--r--android/view/inputmethod/InputMethodManager.java2455
-rw-r--r--android/view/inputmethod/InputMethodManagerInternal.java40
-rw-r--r--android/view/inputmethod/InputMethodManager_Accessor.java27
-rw-r--r--android/view/inputmethod/InputMethodManager_Delegate.java49
-rw-r--r--android/view/inputmethod/InputMethodSession.java188
-rw-r--r--android/view/inputmethod/InputMethodSubtype.java696
-rw-r--r--android/view/inputmethod/InputMethodSubtypeArray.java234
-rw-r--r--android/view/inputmethod/SparseRectFArray.java312
-rw-r--r--android/view/textclassifier/EntityConfidence.java106
-rw-r--r--android/view/textclassifier/LinksInfo.java42
-rw-r--r--android/view/textclassifier/SmartSelection.java117
-rw-r--r--android/view/textclassifier/TextClassification.java277
-rw-r--r--android/view/textclassifier/TextClassificationManager.java63
-rw-r--r--android/view/textclassifier/TextClassifier.java155
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java693
-rw-r--r--android/view/textclassifier/TextSelection.java184
-rw-r--r--android/view/textclassifier/logging/SmartSelectionEventTracker.java560
-rw-r--r--android/view/textservice/SentenceSuggestionsInfo.java144
-rw-r--r--android/view/textservice/SpellCheckerInfo.java290
-rw-r--r--android/view/textservice/SpellCheckerSession.java572
-rw-r--r--android/view/textservice/SpellCheckerSubtype.java321
-rw-r--r--android/view/textservice/SuggestionsInfo.java185
-rw-r--r--android/view/textservice/TextInfo.java161
-rw-r--r--android/view/textservice/TextServicesManager.java236
206 files changed, 130839 insertions, 0 deletions
diff --git a/android/view/AbsSavedState.java b/android/view/AbsSavedState.java
new file mode 100644
index 00000000..6b616c0a
--- /dev/null
+++ b/android/view/AbsSavedState.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2006 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} implementation that should be used by inheritance
+ * hierarchies to ensure the state of all classes along the chain is saved.
+ */
+public abstract class AbsSavedState implements Parcelable {
+ public static final AbsSavedState EMPTY_STATE = new AbsSavedState() {};
+
+ private final Parcelable mSuperState;
+
+ /**
+ * Constructor used to make the EMPTY_STATE singleton
+ */
+ private AbsSavedState() {
+ mSuperState = null;
+ }
+
+ /**
+ * Constructor called by derived classes when creating their SavedState objects
+ *
+ * @param superState The state of the superclass of this view
+ */
+ protected AbsSavedState(Parcelable superState) {
+ if (superState == null) {
+ throw new IllegalArgumentException("superState must not be null");
+ }
+ mSuperState = superState != EMPTY_STATE ? superState : null;
+ }
+
+ /**
+ * Constructor used when reading from a parcel. Reads the state of the superclass.
+ *
+ * @param source parcel to read from
+ */
+ protected AbsSavedState(Parcel source) {
+ this(source, null);
+ }
+
+ /**
+ * Constructor used when reading from a parcel using a given class loader.
+ * Reads the state of the superclass.
+ *
+ * @param source parcel to read from
+ * @param loader ClassLoader to use for reading
+ */
+ protected AbsSavedState(Parcel source, ClassLoader loader) {
+ Parcelable superState = source.readParcelable(loader);
+ mSuperState = superState != null ? superState : EMPTY_STATE;
+ }
+
+ final public Parcelable getSuperState() {
+ return mSuperState;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mSuperState, flags);
+ }
+
+ public static final Parcelable.Creator<AbsSavedState> CREATOR
+ = new Parcelable.ClassLoaderCreator<AbsSavedState>() {
+
+ @Override
+ public AbsSavedState createFromParcel(Parcel in) {
+ return createFromParcel(in, null);
+ }
+
+ @Override
+ public AbsSavedState createFromParcel(Parcel in, ClassLoader loader) {
+ Parcelable superState = in.readParcelable(loader);
+ if (superState != null) {
+ throw new IllegalStateException("superState must be null");
+ }
+ return EMPTY_STATE;
+ }
+
+ @Override
+ public AbsSavedState[] newArray(int size) {
+ return new AbsSavedState[size];
+ }
+ };
+}
diff --git a/android/view/AccessibilityInteractionController.java b/android/view/AccessibilityInteractionController.java
new file mode 100644
index 00000000..45fa5614
--- /dev/null
+++ b/android/view/AccessibilityInteractionController.java
@@ -0,0 +1,1390 @@
+/*
+ * Copyright (C) 2012 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 static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.ClickableSpan;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.view.View.AttachInfo;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityRequestPreparer;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.function.Predicate;
+
+/**
+ * Class for managing accessibility interactions initiated from the system
+ * and targeting the view hierarchy. A *ClientThread method is to be
+ * called from the interaction connection ViewAncestor gives the system to
+ * talk to it and a corresponding *UiThread method that is executed on the
+ * UI thread.
+ */
+final class AccessibilityInteractionController {
+
+ private static final String LOG_TAG = "AccessibilityInteractionController";
+
+ // Debugging flag
+ private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
+
+ // Constants for readability
+ private static final boolean IGNORE_REQUEST_PREPARERS = true;
+ private static final boolean CONSIDER_REQUEST_PREPARERS = false;
+
+ // If an app holds off accessibility for longer than this, the hold-off is canceled to prevent
+ // accessibility from hanging
+ private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
+
+ private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ new ArrayList<AccessibilityNodeInfo>();
+
+ private final Object mLock = new Object();
+
+ private final Handler mHandler;
+
+ private final ViewRootImpl mViewRootImpl;
+
+ private final AccessibilityNodePrefetcher mPrefetcher;
+
+ private final long mMyLooperThreadId;
+
+ private final int mMyProcessId;
+
+ private final AccessibilityManager mA11yManager;
+
+ private final ArrayList<View> mTempArrayList = new ArrayList<View>();
+
+ private final Point mTempPoint = new Point();
+ private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private AddNodeInfosForViewId mAddNodeInfosForViewId;
+
+ @GuardedBy("mLock")
+ private int mNumActiveRequestPreparers;
+ @GuardedBy("mLock")
+ private List<MessageHolder> mMessagesWaitingForRequestPreparer;
+ @GuardedBy("mLock")
+ private int mActiveRequestPreparerId;
+
+ public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
+ Looper looper = viewRootImpl.mHandler.getLooper();
+ mMyLooperThreadId = looper.getThread().getId();
+ mMyProcessId = Process.myPid();
+ mHandler = new PrivateHandler(looper);
+ mViewRootImpl = viewRootImpl;
+ mPrefetcher = new AccessibilityNodePrefetcher();
+ mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
+ }
+
+ private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
+ boolean ignoreRequestPreparers) {
+ if (ignoreRequestPreparers
+ || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+ }
+
+ private boolean isShown(View view) {
+ // The first two checks are made also made by isShown() which
+ // however traverses the tree up to the parent to catch that.
+ // Therefore, we do some fail fast check to minimize the up
+ // tree traversal.
+ return (view.mAttachInfo != null
+ && view.mAttachInfo.mWindowVisibility == View.VISIBLE
+ && view.isShown());
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
+ long accessibilityNodeId, Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid, MagnificationSpec spec, Bundle arguments) {
+ final Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+ message.arg1 = flags;
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ args.arg2 = spec;
+ args.arg3 = interactiveRegion;
+ args.arg4 = arguments;
+ message.obj = args;
+
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+ }
+
+ /**
+ * Check if this message needs to be held off while the app prepares to meet either this
+ * request, or a request ahead of it.
+ *
+ * @param originalMessage The message to be processed
+ * @param callingPid The calling process id
+ * @param callingTid The calling thread id
+ *
+ * @return {@code true} if the message is held off and will be processed later, {@code false} if
+ * the message should be posted.
+ */
+ private boolean holdOffMessageIfNeeded(
+ Message originalMessage, int callingPid, long callingTid) {
+ synchronized (mLock) {
+ // If a request is already pending, queue this request for when it's finished
+ if (mNumActiveRequestPreparers != 0) {
+ queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
+ return true;
+ }
+
+ // Currently the only message that can hold things off is findByA11yId with extra data.
+ if (originalMessage.what
+ != PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID) {
+ return false;
+ }
+ SomeArgs originalMessageArgs = (SomeArgs) originalMessage.obj;
+ Bundle requestArguments = (Bundle) originalMessageArgs.arg4;
+ if (requestArguments == null) {
+ return false;
+ }
+
+ // If nothing it registered for this view, nothing to do
+ int accessibilityViewId = originalMessageArgs.argi1;
+ final List<AccessibilityRequestPreparer> preparers =
+ mA11yManager.getRequestPreparersForAccessibilityId(accessibilityViewId);
+ if (preparers == null) {
+ return false;
+ }
+
+ // If the bundle doesn't request the extra data, nothing to do
+ final String extraDataKey = requestArguments.getString(EXTRA_DATA_REQUESTED_KEY);
+ if (extraDataKey == null) {
+ return false;
+ }
+
+ // Send the request to the AccessibilityRequestPreparers on the UI thread
+ mNumActiveRequestPreparers = preparers.size();
+ for (int i = 0; i < preparers.size(); i++) {
+ final Message requestPreparerMessage = mHandler.obtainMessage(
+ PrivateHandler.MSG_PREPARE_FOR_EXTRA_DATA_REQUEST);
+ final SomeArgs requestPreparerArgs = SomeArgs.obtain();
+ // virtualDescendentId
+ requestPreparerArgs.argi1 =
+ (originalMessageArgs.argi2 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
+ ? AccessibilityNodeProvider.HOST_VIEW_ID : originalMessageArgs.argi2;
+ requestPreparerArgs.arg1 = preparers.get(i);
+ requestPreparerArgs.arg2 = extraDataKey;
+ requestPreparerArgs.arg3 = requestArguments;
+ Message preparationFinishedMessage = mHandler.obtainMessage(
+ PrivateHandler.MSG_APP_PREPARATION_FINISHED);
+ preparationFinishedMessage.arg1 = ++mActiveRequestPreparerId;
+ requestPreparerArgs.arg4 = preparationFinishedMessage;
+
+ requestPreparerMessage.obj = requestPreparerArgs;
+ scheduleMessage(requestPreparerMessage, callingPid, callingTid,
+ IGNORE_REQUEST_PREPARERS);
+ mHandler.obtainMessage(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT,
+ REQUEST_PREPARER_TIMEOUT_MS);
+ }
+
+ // Set the initial request aside
+ queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
+ return true;
+ }
+ }
+
+ private void prepareForExtraDataRequestUiThread(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ final int virtualDescendantId = args.argi1;
+ final AccessibilityRequestPreparer preparer = (AccessibilityRequestPreparer) args.arg1;
+ final String extraDataKey = (String) args.arg2;
+ final Bundle requestArguments = (Bundle) args.arg3;
+ final Message preparationFinishedMessage = (Message) args.arg4;
+
+ preparer.onPrepareExtraData(virtualDescendantId, extraDataKey,
+ requestArguments, preparationFinishedMessage);
+ }
+
+ private void queueMessageToHandleOncePrepared(Message message, int interrogatingPid,
+ long interrogatingTid) {
+ if (mMessagesWaitingForRequestPreparer == null) {
+ mMessagesWaitingForRequestPreparer = new ArrayList<>(1);
+ }
+ MessageHolder messageHolder =
+ new MessageHolder(message, interrogatingPid, interrogatingTid);
+ mMessagesWaitingForRequestPreparer.add(messageHolder);
+ }
+
+ private void requestPreparerDoneUiThread(Message message) {
+ synchronized (mLock) {
+ if (message.arg1 != mActiveRequestPreparerId) {
+ Slog.e(LOG_TAG, "Surprising AccessibilityRequestPreparer callback (likely late)");
+ return;
+ }
+ mNumActiveRequestPreparers--;
+ if (mNumActiveRequestPreparers <= 0) {
+ mHandler.removeMessages(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
+ scheduleAllMessagesWaitingForRequestPreparerLocked();
+ }
+ }
+ }
+
+ private void requestPreparerTimeoutUiThread() {
+ synchronized (mLock) {
+ Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out");
+ scheduleAllMessagesWaitingForRequestPreparerLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void scheduleAllMessagesWaitingForRequestPreparerLocked() {
+ int numMessages = mMessagesWaitingForRequestPreparer.size();
+ for (int i = 0; i < numMessages; i++) {
+ MessageHolder request = mMessagesWaitingForRequestPreparer.get(i);
+ scheduleMessage(request.mMessage, request.mInterrogatingPid,
+ request.mInterrogatingTid,
+ (i == 0) /* the app is ready for the first request */);
+ }
+ mMessagesWaitingForRequestPreparer.clear();
+ mNumActiveRequestPreparers = 0; // Just to be safe - should be unnecessary
+ mActiveRequestPreparerId = -1;
+ }
+
+ private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
+ final int flags = message.arg1;
+
+ SomeArgs args = (SomeArgs) message.obj;
+ final int accessibilityViewId = args.argi1;
+ final int virtualDescendantId = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final Region interactiveRegion = (Region) args.arg3;
+ final Bundle arguments = (Bundle) args.arg4;
+
+ args.recycle();
+
+ List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ View root = null;
+ if (accessibilityViewId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
+ root = mViewRootImpl.mView;
+ } else {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ }
+ if (root != null && isShown(root)) {
+ mPrefetcher.prefetchAccessibilityNodeInfos(
+ root, virtualDescendantId, flags, infos, arguments);
+ }
+ } finally {
+ updateInfosForViewportAndReturnFindNodeResult(
+ infos, callback, interactionId, spec, interactiveRegion);
+ }
+ }
+
+ public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
+ String viewId, Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid, MagnificationSpec spec) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = interactionId;
+ args.arg1 = callback;
+ args.arg2 = spec;
+ args.arg3 = viewId;
+ args.arg4 = interactiveRegion;
+ message.obj = args;
+
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+ }
+
+ private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+
+ SomeArgs args = (SomeArgs) message.obj;
+ final int interactionId = args.argi1;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final String viewId = (String) args.arg3;
+ final Region interactiveRegion = (Region) args.arg4;
+ args.recycle();
+
+ final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null) {
+ final int resolvedViewId = root.getContext().getResources()
+ .getIdentifier(viewId, null, null);
+ if (resolvedViewId <= 0) {
+ return;
+ }
+ if (mAddNodeInfosForViewId == null) {
+ mAddNodeInfosForViewId = new AddNodeInfosForViewId();
+ }
+ mAddNodeInfosForViewId.init(resolvedViewId, infos);
+ root.findViewByPredicate(mAddNodeInfosForViewId);
+ mAddNodeInfosForViewId.reset();
+ }
+ } finally {
+ updateInfosForViewportAndReturnFindNodeResult(
+ infos, callback, interactionId, spec, interactiveRegion);
+ }
+ }
+
+ public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
+ String text, Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid, MagnificationSpec spec) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;
+ message.arg1 = flags;
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = text;
+ args.arg2 = callback;
+ args.arg3 = spec;
+ args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi3 = interactionId;
+ args.arg4 = interactiveRegion;
+ message.obj = args;
+
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+ }
+
+ private void findAccessibilityNodeInfosByTextUiThread(Message message) {
+ final int flags = message.arg1;
+
+ SomeArgs args = (SomeArgs) message.obj;
+ final String text = (String) args.arg1;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg2;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg3;
+ final int accessibilityViewId = args.argi1;
+ final int virtualDescendantId = args.argi2;
+ final int interactionId = args.argi3;
+ final Region interactiveRegion = (Region) args.arg4;
+ args.recycle();
+
+ List<AccessibilityNodeInfo> infos = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isShown(root)) {
+ AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
+ if (provider != null) {
+ infos = provider.findAccessibilityNodeInfosByText(text,
+ virtualDescendantId);
+ } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ ArrayList<View> foundViews = mTempArrayList;
+ foundViews.clear();
+ root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
+ | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
+ | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
+ if (!foundViews.isEmpty()) {
+ infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+ final int viewCount = foundViews.size();
+ for (int i = 0; i < viewCount; i++) {
+ View foundView = foundViews.get(i);
+ if (isShown(foundView)) {
+ provider = foundView.getAccessibilityNodeProvider();
+ if (provider != null) {
+ List<AccessibilityNodeInfo> infosFromProvider =
+ provider.findAccessibilityNodeInfosByText(text,
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+ if (infosFromProvider != null) {
+ infos.addAll(infosFromProvider);
+ }
+ } else {
+ infos.add(foundView.createAccessibilityNodeInfo());
+ }
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ updateInfosForViewportAndReturnFindNodeResult(
+ infos, callback, interactionId, spec, interactiveRegion);
+ }
+ }
+
+ public void findFocusClientThread(long accessibilityNodeId, int focusType,
+ Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid, MagnificationSpec spec) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_FOCUS;
+ message.arg1 = flags;
+ message.arg2 = focusType;
+
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = interactionId;
+ args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.arg1 = callback;
+ args.arg2 = spec;
+ args.arg3 = interactiveRegion;
+
+ message.obj = args;
+
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+ }
+
+ private void findFocusUiThread(Message message) {
+ final int flags = message.arg1;
+ final int focusType = message.arg2;
+
+ SomeArgs args = (SomeArgs) message.obj;
+ final int interactionId = args.argi1;
+ final int accessibilityViewId = args.argi2;
+ final int virtualDescendantId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final Region interactiveRegion = (Region) args.arg3;
+ args.recycle();
+
+ AccessibilityNodeInfo focused = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isShown(root)) {
+ switch (focusType) {
+ case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
+ View host = mViewRootImpl.mAccessibilityFocusedHost;
+ // If there is no accessibility focus host or it is not a descendant
+ // of the root from which to start the search, then the search failed.
+ if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
+ break;
+ }
+ // The focused view not shown, we failed.
+ if (!isShown(host)) {
+ break;
+ }
+ // If the host has a provider ask this provider to search for the
+ // focus instead fetching all provider nodes to do the search here.
+ AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+ if (provider != null) {
+ if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
+ focused = AccessibilityNodeInfo.obtain(
+ mViewRootImpl.mAccessibilityFocusedVirtualView);
+ }
+ } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ focused = host.createAccessibilityNodeInfo();
+ }
+ } break;
+ case AccessibilityNodeInfo.FOCUS_INPUT: {
+ View target = root.findFocus();
+ if (target == null || !isShown(target)) {
+ break;
+ }
+ AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+ if (provider != null) {
+ focused = provider.findFocus(focusType);
+ }
+ if (focused == null) {
+ focused = target.createAccessibilityNodeInfo();
+ }
+ } break;
+ default:
+ throw new IllegalArgumentException("Unknown focus type: " + focusType);
+ }
+ }
+ } finally {
+ updateInfoForViewportAndReturnFindNodeResult(
+ focused, callback, interactionId, spec, interactiveRegion);
+ }
+ }
+
+ public void focusSearchClientThread(long accessibilityNodeId, int direction,
+ Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid, MagnificationSpec spec) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FOCUS_SEARCH;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+
+ SomeArgs args = SomeArgs.obtain();
+ args.argi2 = direction;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ args.arg2 = spec;
+ args.arg3 = interactiveRegion;
+
+ message.obj = args;
+
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+ }
+
+ private void focusSearchUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+
+ SomeArgs args = (SomeArgs) message.obj;
+ final int direction = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final Region interactiveRegion = (Region) args.arg3;
+
+ args.recycle();
+
+ AccessibilityNodeInfo next = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isShown(root)) {
+ View nextView = root.focusSearch(direction);
+ if (nextView != null) {
+ next = nextView.createAccessibilityNodeInfo();
+ }
+ }
+ } finally {
+ updateInfoForViewportAndReturnFindNodeResult(
+ next, callback, interactionId, spec, interactiveRegion);
+ }
+ }
+
+ public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
+ Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi2 = action;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ args.arg2 = arguments;
+
+ message.obj = args;
+
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+ }
+
+ private void performAccessibilityActionUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+
+ SomeArgs args = (SomeArgs) message.obj;
+ final int virtualDescendantId = args.argi1;
+ final int action = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ Bundle arguments = (Bundle) args.arg2;
+
+ args.recycle();
+
+ boolean succeeded = false;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null ||
+ mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ View target = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
+ target = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ target = mViewRootImpl.mView;
+ }
+ if (target != null && isShown(target)) {
+ if (action == R.id.accessibilityActionClickOnClickableSpan) {
+ // Handle this hidden action separately
+ succeeded = handleClickableSpanActionUiThread(
+ target, virtualDescendantId, arguments);
+ } else {
+ AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+ if (provider != null) {
+ succeeded = provider.performAction(virtualDescendantId, action,
+ arguments);
+ } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ succeeded = target.performAccessibilityAction(action, arguments);
+ }
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ callback.setPerformAccessibilityActionResult(succeeded, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ private View findViewByAccessibilityId(int accessibilityId) {
+ View root = mViewRootImpl.mView;
+ if (root == null) {
+ return null;
+ }
+ View foundView = root.findViewByAccessibilityId(accessibilityId);
+ if (foundView != null && !isShown(foundView)) {
+ return null;
+ }
+ return foundView;
+ }
+
+ private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
+ MagnificationSpec spec) {
+ if (infos == null) {
+ return;
+ }
+ final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+ if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityNodeInfo info = infos.get(i);
+ applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+ }
+ }
+ }
+
+ private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
+ Region interactiveRegion) {
+ if (interactiveRegion == null || infos == null) {
+ return;
+ }
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityNodeInfo info = infos.get(i);
+ adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+ }
+ }
+
+ private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
+ Region interactiveRegion) {
+ if (interactiveRegion == null || info == null) {
+ return;
+ }
+ Rect boundsInScreen = mTempRect;
+ info.getBoundsInScreen(boundsInScreen);
+ if (interactiveRegion.quickReject(boundsInScreen)) {
+ info.setVisibleToUser(false);
+ }
+ }
+
+ private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
+ MagnificationSpec spec) {
+ if (info == null) {
+ return;
+ }
+
+ final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+ if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+ return;
+ }
+
+ Rect boundsInParent = mTempRect;
+ Rect boundsInScreen = mTempRect1;
+
+ info.getBoundsInParent(boundsInParent);
+ info.getBoundsInScreen(boundsInScreen);
+ if (applicationScale != 1.0f) {
+ boundsInParent.scale(applicationScale);
+ boundsInScreen.scale(applicationScale);
+ }
+ if (spec != null) {
+ boundsInParent.scale(spec.scale);
+ // boundsInParent must not be offset.
+ boundsInScreen.scale(spec.scale);
+ boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
+ }
+ info.setBoundsInParent(boundsInParent);
+ info.setBoundsInScreen(boundsInScreen);
+
+ // Scale text locations if they are present
+ if (info.hasExtras()) {
+ Bundle extras = info.getExtras();
+ Parcelable[] textLocations =
+ extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+ if (textLocations != null) {
+ for (int i = 0; i < textLocations.length; i++) {
+ // Unchecked cast - an app that puts other objects in this bundle with this
+ // key will crash.
+ RectF textLocation = ((RectF) textLocations[i]);
+ textLocation.scale(applicationScale);
+ if (spec != null) {
+ textLocation.scale(spec.scale);
+ textLocation.offset(spec.offsetX, spec.offsetY);
+ }
+ }
+ }
+ }
+
+ if (spec != null) {
+ AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
+ if (attachInfo.mDisplay == null) {
+ return;
+ }
+
+ final float scale = attachInfo.mApplicationScale * spec.scale;
+
+ Rect visibleWinFrame = mTempRect1;
+ visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
+ visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
+ visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
+ visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
+
+ attachInfo.mDisplay.getRealSize(mTempPoint);
+ final int displayWidth = mTempPoint.x;
+ final int displayHeight = mTempPoint.y;
+
+ Rect visibleDisplayFrame = mTempRect2;
+ visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
+
+ if (!visibleWinFrame.intersect(visibleDisplayFrame)) {
+ // If there's no intersection with display, set visibleWinFrame empty.
+ visibleDisplayFrame.setEmpty();
+ }
+
+ if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
+ boundsInScreen.right, boundsInScreen.bottom)) {
+ info.setVisibleToUser(false);
+ }
+ }
+ }
+
+ private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
+ MagnificationSpec spec) {
+ return (appScale != 1.0f || (spec != null && !spec.isNop()));
+ }
+
+ private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
+ IAccessibilityInteractionConnectionCallback callback, int interactionId,
+ MagnificationSpec spec, Region interactiveRegion) {
+ try {
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+ adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ if (infos != null) {
+ infos.clear();
+ }
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ } finally {
+ recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
+ }
+ }
+
+ private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
+ IAccessibilityInteractionConnectionCallback callback, int interactionId,
+ MagnificationSpec spec, Region interactiveRegion) {
+ try {
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+ adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ } finally {
+ recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
+ }
+ }
+
+ private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) {
+ if (android.os.Process.myPid() != Binder.getCallingPid()) {
+ // Specs are cached in the system process and obtained from a pool when read from
+ // a parcel, so only recycle the spec if called from another process.
+ if (spec != null) {
+ spec.recycle();
+ }
+ } else {
+ // Regions are obtained in the system process and instantiated when read from
+ // a parcel, so only recycle the region if caled from the same process.
+ if (region != null) {
+ region.recycle();
+ }
+ }
+ }
+
+ private boolean handleClickableSpanActionUiThread(
+ View view, int virtualDescendantId, Bundle arguments) {
+ Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
+ if (!(span instanceof AccessibilityClickableSpan)) {
+ return false;
+ }
+
+ // Find the original ClickableSpan if it's still on the screen
+ AccessibilityNodeInfo infoWithSpan = null;
+ AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+ if (provider != null) {
+ infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
+ } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ infoWithSpan = view.createAccessibilityNodeInfo();
+ }
+ if (infoWithSpan == null) {
+ return false;
+ }
+
+ // Click on the corresponding span
+ ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
+ infoWithSpan.getOriginalText());
+ if (clickableSpan != null) {
+ clickableSpan.onClick(view);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This class encapsulates a prefetching strategy for the accessibility APIs for
+ * querying window content. It is responsible to prefetch a batch of
+ * AccessibilityNodeInfos in addition to the one for a requested node.
+ */
+ private class AccessibilityNodePrefetcher {
+
+ private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
+
+ private final ArrayList<View> mTempViewList = new ArrayList<View>();
+
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
+ List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
+ AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+ // Determine if we'll be populating extra data
+ final String extraDataRequested = (arguments == null) ? null
+ : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
+ if (provider == null) {
+ AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
+ if (root != null) {
+ if (extraDataRequested != null) {
+ view.addExtraDataToAccessibilityNodeInfo(
+ root, extraDataRequested, arguments);
+ }
+ outInfos.add(root);
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfRealNode(view, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfRealNode(view, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfRealNode(view, outInfos);
+ }
+ }
+ } else {
+ final AccessibilityNodeInfo root =
+ provider.createAccessibilityNodeInfo(virtualViewId);
+ if (root != null) {
+ if (extraDataRequested != null) {
+ provider.addExtraDataToAccessibilityNodeInfo(
+ virtualViewId, root, extraDataRequested, arguments);
+ }
+ outInfos.add(root);
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+ }
+ }
+ }
+ if (ENFORCE_NODE_TREE_CONSISTENT) {
+ enforceNodeTreeConsistent(outInfos);
+ }
+ }
+
+ private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
+ LongSparseArray<AccessibilityNodeInfo> nodeMap =
+ new LongSparseArray<AccessibilityNodeInfo>();
+ final int nodeCount = nodes.size();
+ for (int i = 0; i < nodeCount; i++) {
+ AccessibilityNodeInfo node = nodes.get(i);
+ nodeMap.put(node.getSourceNodeId(), node);
+ }
+
+ // If the nodes are a tree it does not matter from
+ // which node we start to search for the root.
+ AccessibilityNodeInfo root = nodeMap.valueAt(0);
+ AccessibilityNodeInfo parent = root;
+ while (parent != null) {
+ root = parent;
+ parent = nodeMap.get(parent.getParentNodeId());
+ }
+
+ // Traverse the tree and do some checks.
+ AccessibilityNodeInfo accessFocus = null;
+ AccessibilityNodeInfo inputFocus = null;
+ HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+ fringe.add(root);
+
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+
+ // Check for duplicates
+ if (!seen.add(current)) {
+ throw new IllegalStateException("Duplicate node: "
+ + current + " in window:"
+ + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+ }
+
+ // Check for one accessibility focus.
+ if (current.isAccessibilityFocused()) {
+ if (accessFocus != null) {
+ throw new IllegalStateException("Duplicate accessibility focus:"
+ + current
+ + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+ } else {
+ accessFocus = current;
+ }
+ }
+
+ // Check for one input focus.
+ if (current.isFocused()) {
+ if (inputFocus != null) {
+ throw new IllegalStateException("Duplicate input focus: "
+ + current + " in window:"
+ + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+ } else {
+ inputFocus = current;
+ }
+ }
+
+ final int childCount = current.getChildCount();
+ for (int j = 0; j < childCount; j++) {
+ final long childId = current.getChildId(j);
+ final AccessibilityNodeInfo child = nodeMap.get(childId);
+ if (child != null) {
+ fringe.add(child);
+ }
+ }
+ }
+
+ // Check for disconnected nodes.
+ for (int j = nodeMap.size() - 1; j >= 0; j--) {
+ AccessibilityNodeInfo info = nodeMap.valueAt(j);
+ if (!seen.contains(info)) {
+ throw new IllegalStateException("Disconnected node: " + info);
+ }
+ }
+ }
+
+ private void prefetchPredecessorsOfRealNode(View view,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent parent = view.getParentForAccessibility();
+ while (parent instanceof View
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ View parentView = (View) parent;
+ AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ }
+ parent = parent.getParentForAccessibility();
+ }
+ }
+
+ private void prefetchSiblingsOfRealNode(View current,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent parent = current.getParentForAccessibility();
+ if (parent instanceof ViewGroup) {
+ ViewGroup parentGroup = (ViewGroup) parent;
+ ArrayList<View> children = mTempViewList;
+ children.clear();
+ try {
+ parentGroup.addChildrenForAccessibility(children);
+ final int childCount = children.size();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ View child = children.get(i);
+ if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
+ && isShown(child)) {
+ AccessibilityNodeInfo info = null;
+ AccessibilityNodeProvider provider =
+ child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ info = child.createAccessibilityNodeInfo();
+ } else {
+ info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+ if (info != null) {
+ outInfos.add(info);
+ }
+ }
+ }
+ } finally {
+ children.clear();
+ }
+ }
+ }
+
+ private void prefetchDescendantsOfRealNode(View root,
+ List<AccessibilityNodeInfo> outInfos) {
+ if (!(root instanceof ViewGroup)) {
+ return;
+ }
+ HashMap<View, AccessibilityNodeInfo> addedChildren =
+ new HashMap<View, AccessibilityNodeInfo>();
+ ArrayList<View> children = mTempViewList;
+ children.clear();
+ try {
+ root.addChildrenForAccessibility(children);
+ final int childCount = children.size();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ View child = children.get(i);
+ if (isShown(child)) {
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, null);
+ }
+ } else {
+ AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, info);
+ }
+ }
+ }
+ }
+ } finally {
+ children.clear();
+ }
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
+ View addedChild = entry.getKey();
+ AccessibilityNodeInfo virtualRoot = entry.getValue();
+ if (virtualRoot == null) {
+ prefetchDescendantsOfRealNode(addedChild, outInfos);
+ } else {
+ AccessibilityNodeProvider provider =
+ addedChild.getAccessibilityNodeProvider();
+ prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
+ View providerHost, AccessibilityNodeProvider provider,
+ List<AccessibilityNodeInfo> outInfos) {
+ final int initialResultSize = outInfos.size();
+ long parentNodeId = root.getParentNodeId();
+ int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final int virtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
+ || accessibilityViewId == providerHost.getAccessibilityViewId()) {
+ final AccessibilityNodeInfo parent;
+ parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
+ if (parent == null) {
+ // Going up the parent relation we found a null predecessor,
+ // so remove these disconnected nodes form the result.
+ final int currentResultSize = outInfos.size();
+ for (int i = currentResultSize - 1; i >= initialResultSize; i--) {
+ outInfos.remove(i);
+ }
+ // Couldn't obtain the parent, which means we have a
+ // disconnected sub-tree. Abort prefetch immediately.
+ return;
+ }
+ outInfos.add(parent);
+ parentNodeId = parent.getParentNodeId();
+ accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+ parentNodeId);
+ } else {
+ prefetchPredecessorsOfRealNode(providerHost, outInfos);
+ return;
+ }
+ }
+ }
+
+ private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ final long parentNodeId = current.getParentNodeId();
+ final int parentAccessibilityViewId =
+ AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ final int parentVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
+ || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
+ final AccessibilityNodeInfo parent =
+ provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
+ if (parent != null) {
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final long childNodeId = parent.getChildId(i);
+ if (childNodeId != current.getSourceNodeId()) {
+ final int childVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ childVirtualDescendantId);
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
+ }
+ }
+ } else {
+ prefetchSiblingsOfRealNode(providerHost, outInfos);
+ }
+ }
+
+ private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ final int initialOutInfosSize = outInfos.size();
+ final int childCount = root.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final long childNodeId = root.getChildId(i);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ final int addedChildCount = outInfos.size() - initialOutInfosSize;
+ for (int i = 0; i < addedChildCount; i++) {
+ AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
+ prefetchDescendantsOfVirtualNode(child, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private class PrivateHandler extends Handler {
+ private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
+ private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
+ private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
+ private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
+ private static final int MSG_FIND_FOCUS = 5;
+ private static final int MSG_FOCUS_SEARCH = 6;
+ private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;
+ private static final int MSG_APP_PREPARATION_FINISHED = 8;
+ private static final int MSG_APP_PREPARATION_TIMEOUT = 9;
+
+ public PrivateHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public String getMessageName(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_PERFORM_ACCESSIBILITY_ACTION:
+ return "MSG_PERFORM_ACCESSIBILITY_ACTION";
+ case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
+ return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
+ case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
+ return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
+ case MSG_FIND_FOCUS:
+ return "MSG_FIND_FOCUS";
+ case MSG_FOCUS_SEARCH:
+ return "MSG_FOCUS_SEARCH";
+ case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:
+ return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";
+ case MSG_APP_PREPARATION_FINISHED:
+ return "MSG_APP_PREPARATION_FINISHED";
+ case MSG_APP_PREPARATION_TIMEOUT:
+ return "MSG_APP_PREPARATION_TIMEOUT";
+ default:
+ throw new IllegalArgumentException("Unknown message type: " + type);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
+ findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
+ } break;
+ case MSG_PERFORM_ACCESSIBILITY_ACTION: {
+ performAccessibilityActionUiThread(message);
+ } break;
+ case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
+ findAccessibilityNodeInfosByViewIdUiThread(message);
+ } break;
+ case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
+ findAccessibilityNodeInfosByTextUiThread(message);
+ } break;
+ case MSG_FIND_FOCUS: {
+ findFocusUiThread(message);
+ } break;
+ case MSG_FOCUS_SEARCH: {
+ focusSearchUiThread(message);
+ } break;
+ case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {
+ prepareForExtraDataRequestUiThread(message);
+ } break;
+ case MSG_APP_PREPARATION_FINISHED: {
+ requestPreparerDoneUiThread(message);
+ } break;
+ case MSG_APP_PREPARATION_TIMEOUT: {
+ requestPreparerTimeoutUiThread();
+ } break;
+ default:
+ throw new IllegalArgumentException("Unknown message type: " + type);
+ }
+ }
+ }
+
+ private final class AddNodeInfosForViewId implements Predicate<View> {
+ private int mViewId = View.NO_ID;
+ private List<AccessibilityNodeInfo> mInfos;
+
+ public void init(int viewId, List<AccessibilityNodeInfo> infos) {
+ mViewId = viewId;
+ mInfos = infos;
+ }
+
+ public void reset() {
+ mViewId = View.NO_ID;
+ mInfos = null;
+ }
+
+ @Override
+ public boolean test(View view) {
+ if (view.getId() == mViewId && isShown(view)) {
+ mInfos.add(view.createAccessibilityNodeInfo());
+ }
+ return false;
+ }
+ }
+
+ private static final class MessageHolder {
+ final Message mMessage;
+ final int mInterrogatingPid;
+ final long mInterrogatingTid;
+
+ MessageHolder(Message message, int interrogatingPid, long interrogatingTid) {
+ mMessage = message;
+ mInterrogatingPid = interrogatingPid;
+ mInterrogatingTid = interrogatingTid;
+ }
+ }
+}
diff --git a/android/view/AccessibilityIterators.java b/android/view/AccessibilityIterators.java
new file mode 100644
index 00000000..ca54bef1
--- /dev/null
+++ b/android/view/AccessibilityIterators.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2012 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.content.res.Configuration;
+
+import java.text.BreakIterator;
+import java.util.Locale;
+
+/**
+ * This class contains the implementation of text segment iterators
+ * for accessibility support.
+ *
+ * Note: Such iterators are needed in the view package since we want
+ * to be able to iterator over content description of any view.
+ *
+ * @hide
+ */
+public final class AccessibilityIterators {
+
+ /**
+ * @hide
+ */
+ public static interface TextSegmentIterator {
+ public int[] following(int current);
+ public int[] preceding(int current);
+ }
+
+ /**
+ * @hide
+ */
+ public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator {
+
+ protected String mText;
+
+ private final int[] mSegment = new int[2];
+
+ public void initialize(String text) {
+ mText = text;
+ }
+
+ protected int[] getRange(int start, int end) {
+ if (start < 0 || end < 0 || start == end) {
+ return null;
+ }
+ mSegment[0] = start;
+ mSegment[1] = end;
+ return mSegment;
+ }
+ }
+
+ static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator
+ implements ViewRootImpl.ConfigChangedCallback {
+ private static CharacterTextSegmentIterator sInstance;
+
+ private Locale mLocale;
+
+ protected BreakIterator mImpl;
+
+ public static CharacterTextSegmentIterator getInstance(Locale locale) {
+ if (sInstance == null) {
+ sInstance = new CharacterTextSegmentIterator(locale);
+ }
+ return sInstance;
+ }
+
+ private CharacterTextSegmentIterator(Locale locale) {
+ mLocale = locale;
+ onLocaleChanged(locale);
+ ViewRootImpl.addConfigCallback(this);
+ }
+
+ @Override
+ public void initialize(String text) {
+ super.initialize(text);
+ mImpl.setText(text);
+ }
+
+ @Override
+ public int[] following(int offset) {
+ final int textLegth = mText.length();
+ if (textLegth <= 0) {
+ return null;
+ }
+ if (offset >= textLegth) {
+ return null;
+ }
+ int start = offset;
+ if (start < 0) {
+ start = 0;
+ }
+ while (!mImpl.isBoundary(start)) {
+ start = mImpl.following(start);
+ if (start == BreakIterator.DONE) {
+ return null;
+ }
+ }
+ final int end = mImpl.following(start);
+ if (end == BreakIterator.DONE) {
+ return null;
+ }
+ return getRange(start, end);
+ }
+
+ @Override
+ public int[] preceding(int offset) {
+ final int textLegth = mText.length();
+ if (textLegth <= 0) {
+ return null;
+ }
+ if (offset <= 0) {
+ return null;
+ }
+ int end = offset;
+ if (end > textLegth) {
+ end = textLegth;
+ }
+ while (!mImpl.isBoundary(end)) {
+ end = mImpl.preceding(end);
+ if (end == BreakIterator.DONE) {
+ return null;
+ }
+ }
+ final int start = mImpl.preceding(end);
+ if (start == BreakIterator.DONE) {
+ return null;
+ }
+ return getRange(start, end);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration globalConfig) {
+ final Locale locale = globalConfig.getLocales().get(0);
+ if (!mLocale.equals(locale)) {
+ mLocale = locale;
+ onLocaleChanged(locale);
+ }
+ }
+
+ protected void onLocaleChanged(Locale locale) {
+ mImpl = BreakIterator.getCharacterInstance(locale);
+ }
+ }
+
+ static class WordTextSegmentIterator extends CharacterTextSegmentIterator {
+ private static WordTextSegmentIterator sInstance;
+
+ public static WordTextSegmentIterator getInstance(Locale locale) {
+ if (sInstance == null) {
+ sInstance = new WordTextSegmentIterator(locale);
+ }
+ return sInstance;
+ }
+
+ private WordTextSegmentIterator(Locale locale) {
+ super(locale);
+ }
+
+ @Override
+ protected void onLocaleChanged(Locale locale) {
+ mImpl = BreakIterator.getWordInstance(locale);
+ }
+
+ @Override
+ public int[] following(int offset) {
+ final int textLegth = mText.length();
+ if (textLegth <= 0) {
+ return null;
+ }
+ if (offset >= mText.length()) {
+ return null;
+ }
+ int start = offset;
+ if (start < 0) {
+ start = 0;
+ }
+ while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
+ start = mImpl.following(start);
+ if (start == BreakIterator.DONE) {
+ return null;
+ }
+ }
+ final int end = mImpl.following(start);
+ if (end == BreakIterator.DONE || !isEndBoundary(end)) {
+ return null;
+ }
+ return getRange(start, end);
+ }
+
+ @Override
+ public int[] preceding(int offset) {
+ final int textLegth = mText.length();
+ if (textLegth <= 0) {
+ return null;
+ }
+ if (offset <= 0) {
+ return null;
+ }
+ int end = offset;
+ if (end > textLegth) {
+ end = textLegth;
+ }
+ while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
+ end = mImpl.preceding(end);
+ if (end == BreakIterator.DONE) {
+ return null;
+ }
+ }
+ final int start = mImpl.preceding(end);
+ if (start == BreakIterator.DONE || !isStartBoundary(start)) {
+ return null;
+ }
+ return getRange(start, end);
+ }
+
+ private boolean isStartBoundary(int index) {
+ return isLetterOrDigit(index)
+ && (index == 0 || !isLetterOrDigit(index - 1));
+ }
+
+ private boolean isEndBoundary(int index) {
+ return (index > 0 && isLetterOrDigit(index - 1))
+ && (index == mText.length() || !isLetterOrDigit(index));
+ }
+
+ private boolean isLetterOrDigit(int index) {
+ if (index >= 0 && index < mText.length()) {
+ final int codePoint = mText.codePointAt(index);
+ return Character.isLetterOrDigit(codePoint);
+ }
+ return false;
+ }
+ }
+
+ static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator {
+ private static ParagraphTextSegmentIterator sInstance;
+
+ public static ParagraphTextSegmentIterator getInstance() {
+ if (sInstance == null) {
+ sInstance = new ParagraphTextSegmentIterator();
+ }
+ return sInstance;
+ }
+
+ @Override
+ public int[] following(int offset) {
+ final int textLength = mText.length();
+ if (textLength <= 0) {
+ return null;
+ }
+ if (offset >= textLength) {
+ return null;
+ }
+ int start = offset;
+ if (start < 0) {
+ start = 0;
+ }
+ while (start < textLength && mText.charAt(start) == '\n'
+ && !isStartBoundary(start)) {
+ start++;
+ }
+ if (start >= textLength) {
+ return null;
+ }
+ int end = start + 1;
+ while (end < textLength && !isEndBoundary(end)) {
+ end++;
+ }
+ return getRange(start, end);
+ }
+
+ @Override
+ public int[] preceding(int offset) {
+ final int textLength = mText.length();
+ if (textLength <= 0) {
+ return null;
+ }
+ if (offset <= 0) {
+ return null;
+ }
+ int end = offset;
+ if (end > textLength) {
+ end = textLength;
+ }
+ while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) {
+ end--;
+ }
+ if (end <= 0) {
+ return null;
+ }
+ int start = end - 1;
+ while (start > 0 && !isStartBoundary(start)) {
+ start--;
+ }
+ return getRange(start, end);
+ }
+
+ private boolean isStartBoundary(int index) {
+ return (mText.charAt(index) != '\n'
+ && (index == 0 || mText.charAt(index - 1) == '\n'));
+ }
+
+ private boolean isEndBoundary(int index) {
+ return (index > 0 && mText.charAt(index - 1) != '\n'
+ && (index == mText.length() || mText.charAt(index) == '\n'));
+ }
+ }
+}
diff --git a/android/view/ActionMode.java b/android/view/ActionMode.java
new file mode 100644
index 00000000..05d91675
--- /dev/null
+++ b/android/view/ActionMode.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2010 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.StringRes;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+
+/**
+ * Represents a contextual mode of the user interface. Action modes can be used to provide
+ * alternative interaction modes and replace parts of the normal UI until finished.
+ * Examples of good action modes include text selection and contextual actions.
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to provide contextual actions with {@code ActionMode},
+ * read the <a href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menus</a>
+ * developer guide.</p>
+ * </div>
+ */
+public abstract class ActionMode {
+
+ /**
+ * The action mode is treated as a Primary mode. This is the default.
+ * Use with {@link #setType}.
+ */
+ public static final int TYPE_PRIMARY = 0;
+ /**
+ * The action mode is treated as a Floating Toolbar.
+ * Use with {@link #setType}.
+ */
+ public static final int TYPE_FLOATING = 1;
+
+ /**
+ * Default value to hide the action mode for
+ * {@link ViewConfiguration#getDefaultActionModeHideDuration()}.
+ */
+ public static final int DEFAULT_HIDE_DURATION = -1;
+
+ private Object mTag;
+ private boolean mTitleOptionalHint;
+ private int mType = TYPE_PRIMARY;
+
+ /**
+ * Set a tag object associated with this ActionMode.
+ *
+ * <p>Like the tag available to views, this allows applications to associate arbitrary
+ * data with an ActionMode for later reference.
+ *
+ * @param tag Tag to associate with this ActionMode
+ *
+ * @see #getTag()
+ */
+ public void setTag(Object tag) {
+ mTag = tag;
+ }
+
+ /**
+ * Retrieve the tag object associated with this ActionMode.
+ *
+ * <p>Like the tag available to views, this allows applications to associate arbitrary
+ * data with an ActionMode for later reference.
+ *
+ * @return Tag associated with this ActionMode
+ *
+ * @see #setTag(Object)
+ */
+ public Object getTag() {
+ return mTag;
+ }
+
+ /**
+ * Set the title of the action mode. This method will have no visible effect if
+ * a custom view has been set.
+ *
+ * @param title Title string to set
+ *
+ * @see #setTitle(int)
+ * @see #setCustomView(View)
+ */
+ public abstract void setTitle(CharSequence title);
+
+ /**
+ * Set the title of the action mode. This method will have no visible effect if
+ * a custom view has been set.
+ *
+ * @param resId Resource ID of a string to set as the title
+ *
+ * @see #setTitle(CharSequence)
+ * @see #setCustomView(View)
+ */
+ public abstract void setTitle(@StringRes int resId);
+
+ /**
+ * Set the subtitle of the action mode. This method will have no visible effect if
+ * a custom view has been set.
+ *
+ * @param subtitle Subtitle string to set
+ *
+ * @see #setSubtitle(int)
+ * @see #setCustomView(View)
+ */
+ public abstract void setSubtitle(CharSequence subtitle);
+
+ /**
+ * Set the subtitle of the action mode. This method will have no visible effect if
+ * a custom view has been set.
+ *
+ * @param resId Resource ID of a string to set as the subtitle
+ *
+ * @see #setSubtitle(CharSequence)
+ * @see #setCustomView(View)
+ */
+ public abstract void setSubtitle(@StringRes int resId);
+
+ /**
+ * Set whether or not the title/subtitle display for this action mode
+ * is optional.
+ *
+ * <p>In many cases the supplied title for an action mode is merely
+ * meant to add context and is not strictly required for the action
+ * mode to be useful. If the title is optional, the system may choose
+ * to hide the title entirely rather than truncate it due to a lack
+ * of available space.</p>
+ *
+ * <p>Note that this is merely a hint; the underlying implementation
+ * may choose to ignore this setting under some circumstances.</p>
+ *
+ * @param titleOptional true if the title only presents optional information.
+ */
+ public void setTitleOptionalHint(boolean titleOptional) {
+ mTitleOptionalHint = titleOptional;
+ }
+
+ /**
+ * @return true if this action mode has been given a hint to consider the
+ * title/subtitle display to be optional.
+ *
+ * @see #setTitleOptionalHint(boolean)
+ * @see #isTitleOptional()
+ */
+ public boolean getTitleOptionalHint() {
+ return mTitleOptionalHint;
+ }
+
+ /**
+ * @return true if this action mode considers the title and subtitle fields
+ * as optional. Optional titles may not be displayed to the user.
+ */
+ public boolean isTitleOptional() {
+ return false;
+ }
+
+ /**
+ * Set a custom view for this action mode. The custom view will take the place of
+ * the title and subtitle. Useful for things like search boxes.
+ *
+ * @param view Custom view to use in place of the title/subtitle.
+ *
+ * @see #setTitle(CharSequence)
+ * @see #setSubtitle(CharSequence)
+ */
+ public abstract void setCustomView(View view);
+
+ /**
+ * Set a type for this action mode. This will affect how the system renders the action mode if
+ * it has to.
+ *
+ * @param type One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+ */
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * Returns the type for this action mode.
+ *
+ * @return One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Invalidate the action mode and refresh menu content. The mode's
+ * {@link ActionMode.Callback} will have its
+ * {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
+ * If it returns true the menu will be scanned for updated content and any relevant changes
+ * will be reflected to the user.
+ */
+ public abstract void invalidate();
+
+ /**
+ * Invalidate the content rect associated to this ActionMode. This only makes sense for
+ * action modes that support dynamic positioning on the screen, and provides a more efficient
+ * way to reposition it without invalidating the whole action mode.
+ *
+ * @see Callback2#onGetContentRect(ActionMode, View, Rect) .
+ */
+ public void invalidateContentRect() {}
+
+ /**
+ * Hide the action mode view from obstructing the content below for a short duration.
+ * This only makes sense for action modes that support dynamic positioning on the screen.
+ * If this method is called again before the hide duration expires, the later hide call will
+ * cancel the former and then take effect.
+ * NOTE that there is an internal limit to how long the mode can be hidden for. It's typically
+ * about a few seconds.
+ *
+ * @param duration The number of milliseconds to hide for.
+ * @see #DEFAULT_HIDE_DURATION
+ */
+ public void hide(long duration) {}
+
+ /**
+ * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
+ * have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
+ */
+ public abstract void finish();
+
+ /**
+ * Returns the menu of actions that this action mode presents.
+ * @return The action mode's menu.
+ */
+ public abstract Menu getMenu();
+
+ /**
+ * Returns the current title of this action mode.
+ * @return Title text
+ */
+ public abstract CharSequence getTitle();
+
+ /**
+ * Returns the current subtitle of this action mode.
+ * @return Subtitle text
+ */
+ public abstract CharSequence getSubtitle();
+
+ /**
+ * Returns the current custom view for this action mode.
+ * @return The current custom view
+ */
+ public abstract View getCustomView();
+
+ /**
+ * Returns a {@link MenuInflater} with the ActionMode's context.
+ */
+ public abstract MenuInflater getMenuInflater();
+
+ /**
+ * Called when the window containing the view that started this action mode gains or loses
+ * focus.
+ *
+ * @param hasWindowFocus True if the window containing the view that started this action mode
+ * now has focus, false otherwise.
+ *
+ */
+ public void onWindowFocusChanged(boolean hasWindowFocus) {}
+
+ /**
+ * Returns whether the UI presenting this action mode can take focus or not.
+ * This is used by internal components within the framework that would otherwise
+ * present an action mode UI that requires focus, such as an EditText as a custom view.
+ *
+ * @return true if the UI used to show this action mode can take focus
+ * @hide Internal use only
+ */
+ @TestApi
+ public boolean isUiFocusable() {
+ return true;
+ }
+
+ /**
+ * Callback interface for action modes. Supplied to
+ * {@link View#startActionMode(Callback)}, a Callback
+ * configures and handles events raised by a user's interaction with an action mode.
+ *
+ * <p>An action mode's lifecycle is as follows:
+ * <ul>
+ * <li>{@link Callback#onCreateActionMode(ActionMode, Menu)} once on initial
+ * creation</li>
+ * <li>{@link Callback#onPrepareActionMode(ActionMode, Menu)} after creation
+ * and any time the {@link ActionMode} is invalidated</li>
+ * <li>{@link Callback#onActionItemClicked(ActionMode, MenuItem)} any time a
+ * contextual action button is clicked</li>
+ * <li>{@link Callback#onDestroyActionMode(ActionMode)} when the action mode
+ * is closed</li>
+ * </ul>
+ */
+ public interface Callback {
+ /**
+ * Called when action mode is first created. The menu supplied will be used to
+ * generate action buttons for the action mode.
+ *
+ * @param mode ActionMode being created
+ * @param menu Menu used to populate action buttons
+ * @return true if the action mode should be created, false if entering this
+ * mode should be aborted.
+ */
+ public boolean onCreateActionMode(ActionMode mode, Menu menu);
+
+ /**
+ * Called to refresh an action mode's action menu whenever it is invalidated.
+ *
+ * @param mode ActionMode being prepared
+ * @param menu Menu used to populate action buttons
+ * @return true if the menu or action mode was updated, false otherwise.
+ */
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu);
+
+ /**
+ * Called to report a user click on an action button.
+ *
+ * @param mode The current ActionMode
+ * @param item The item that was clicked
+ * @return true if this callback handled the event, false if the standard MenuItem
+ * invocation should continue.
+ */
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item);
+
+ /**
+ * Called when an action mode is about to be exited and destroyed.
+ *
+ * @param mode The current ActionMode being destroyed
+ */
+ public void onDestroyActionMode(ActionMode mode);
+ }
+
+ /**
+ * Extension of {@link ActionMode.Callback} to provide content rect information. This is
+ * required for ActionModes with dynamic positioning such as the ones with type
+ * {@link ActionMode#TYPE_FLOATING} to ensure the positioning doesn't obscure app content. If
+ * an app fails to provide a subclass of this class, a default implementation will be used.
+ */
+ public static abstract class Callback2 implements ActionMode.Callback {
+
+ /**
+ * Called when an ActionMode needs to be positioned on screen, potentially occluding view
+ * content. Note this may be called on a per-frame basis.
+ *
+ * @param mode The ActionMode that requires positioning.
+ * @param view The View that originated the ActionMode, in whose coordinates the Rect should
+ * be provided.
+ * @param outRect The Rect to be populated with the content position. Use this to specify
+ * where the content in your app lives within the given view. This will be used
+ * to avoid occluding the given content Rect with the created ActionMode.
+ */
+ public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+ if (view != null) {
+ outRect.set(0, 0, view.getWidth(), view.getHeight());
+ } else {
+ outRect.set(0, 0, 0, 0);
+ }
+ }
+
+ }
+}
diff --git a/android/view/ActionProvider.java b/android/view/ActionProvider.java
new file mode 100644
index 00000000..353b4c26
--- /dev/null
+++ b/android/view/ActionProvider.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2011 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.content.Context;
+import android.util.Log;
+
+/**
+ * An ActionProvider defines rich menu interaction in a single component.
+ * ActionProvider can generate action views for use in the action bar,
+ * dynamically populate submenus of a MenuItem, and handle default menu
+ * item invocations.
+ *
+ * <p>An ActionProvider can be optionally specified for a {@link MenuItem} and will be
+ * responsible for creating the action view that appears in the {@link android.app.ActionBar}
+ * in place of a simple button in the bar. When the menu item is presented in a way that
+ * does not allow custom action views, (e.g. in an overflow menu,) the ActionProvider
+ * can perform a default action.</p>
+ *
+ * <p>There are two ways to use an action provider:
+ * <ul>
+ * <li>
+ * Set the action provider on a {@link MenuItem} directly by calling
+ * {@link MenuItem#setActionProvider(ActionProvider)}.
+ * </li>
+ * <li>
+ * Declare the action provider in an XML menu resource. For example:
+ * <pre>
+ * <code>
+ * &lt;item android:id="@+id/my_menu_item"
+ * android:title="Title"
+ * android:icon="@drawable/my_menu_item_icon"
+ * android:showAsAction="ifRoom"
+ * android:actionProviderClass="foo.bar.SomeActionProvider" /&gt;
+ * </code>
+ * </pre>
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @see MenuItem#setActionProvider(ActionProvider)
+ * @see MenuItem#getActionProvider()
+ */
+public abstract class ActionProvider {
+ private static final String TAG = "ActionProvider";
+ private SubUiVisibilityListener mSubUiVisibilityListener;
+ private VisibilityListener mVisibilityListener;
+
+ /**
+ * Creates a new instance. ActionProvider classes should always implement a
+ * constructor that takes a single Context parameter for inflating from menu XML.
+ *
+ * @param context Context for accessing resources.
+ */
+ public ActionProvider(Context context) {
+ }
+
+ /**
+ * Factory method called by the Android framework to create new action views.
+ *
+ * <p>This method has been deprecated in favor of {@link #onCreateActionView(MenuItem)}.
+ * Newer apps that wish to support platform versions prior to API 16 should also
+ * implement this method to return a valid action view.</p>
+ *
+ * @return A new action view.
+ *
+ * @deprecated use {@link #onCreateActionView(MenuItem)}
+ */
+ @Deprecated
+ public abstract View onCreateActionView();
+
+ /**
+ * Factory method called by the Android framework to create new action views.
+ * This method returns a new action view for the given MenuItem.
+ *
+ * <p>If your ActionProvider implementation overrides the deprecated no-argument overload
+ * {@link #onCreateActionView()}, overriding this method for devices running API 16 or later
+ * is recommended but optional. The default implementation calls {@link #onCreateActionView()}
+ * for compatibility with applications written for older platform versions.</p>
+ *
+ * @param forItem MenuItem to create the action view for
+ * @return the new action view
+ */
+ public View onCreateActionView(MenuItem forItem) {
+ return onCreateActionView();
+ }
+
+ /**
+ * The result of this method determines whether or not {@link #isVisible()} will be used
+ * by the {@link MenuItem} this ActionProvider is bound to help determine its visibility.
+ *
+ * @return true if this ActionProvider overrides the visibility of the MenuItem
+ * it is bound to, false otherwise. The default implementation returns false.
+ * @see #isVisible()
+ */
+ public boolean overridesItemVisibility() {
+ return false;
+ }
+
+ /**
+ * If {@link #overridesItemVisibility()} returns true, the return value of this method
+ * will help determine the visibility of the {@link MenuItem} this ActionProvider is bound to.
+ *
+ * <p>If the MenuItem's visibility is explicitly set to false by the application,
+ * the MenuItem will not be shown, even if this method returns true.</p>
+ *
+ * @return true if the MenuItem this ActionProvider is bound to is visible, false if
+ * it is invisible. The default implementation returns true.
+ */
+ public boolean isVisible() {
+ return true;
+ }
+
+ /**
+ * If this ActionProvider is associated with an item in a menu,
+ * refresh the visibility of the item based on {@link #overridesItemVisibility()} and
+ * {@link #isVisible()}. If {@link #overridesItemVisibility()} returns false, this call
+ * will have no effect.
+ */
+ public void refreshVisibility() {
+ if (mVisibilityListener != null && overridesItemVisibility()) {
+ mVisibilityListener.onActionProviderVisibilityChanged(isVisible());
+ }
+ }
+
+ /**
+ * Performs an optional default action.
+ * <p>
+ * For the case of an action provider placed in a menu item not shown as an action this
+ * method is invoked if previous callbacks for processing menu selection has handled
+ * the event.
+ * </p>
+ * <p>
+ * A menu item selection is processed in the following order:
+ * <ul>
+ * <li>
+ * Receiving a call to {@link MenuItem.OnMenuItemClickListener#onMenuItemClick
+ * MenuItem.OnMenuItemClickListener.onMenuItemClick}.
+ * </li>
+ * <li>
+ * Receiving a call to {@link android.app.Activity#onOptionsItemSelected(MenuItem)
+ * Activity.onOptionsItemSelected(MenuItem)}
+ * </li>
+ * <li>
+ * Receiving a call to {@link android.app.Fragment#onOptionsItemSelected(MenuItem)
+ * Fragment.onOptionsItemSelected(MenuItem)}
+ * </li>
+ * <li>
+ * Launching the {@link android.content.Intent} set via
+ * {@link MenuItem#setIntent(android.content.Intent) MenuItem.setIntent(android.content.Intent)}
+ * </li>
+ * <li>
+ * Invoking this method.
+ * </li>
+ * </ul>
+ * </p>
+ * <p>
+ * The default implementation does not perform any action and returns false.
+ * </p>
+ */
+ public boolean onPerformDefaultAction() {
+ return false;
+ }
+
+ /**
+ * Determines if this ActionProvider has a submenu associated with it.
+ *
+ * <p>Associated submenus will be shown when an action view is not. This
+ * provider instance will receive a call to {@link #onPrepareSubMenu(SubMenu)}
+ * after the call to {@link #onPerformDefaultAction()} and before a submenu is
+ * displayed to the user.
+ *
+ * @return true if the item backed by this provider should have an associated submenu
+ */
+ public boolean hasSubMenu() {
+ return false;
+ }
+
+ /**
+ * Called to prepare an associated submenu for the menu item backed by this ActionProvider.
+ *
+ * <p>if {@link #hasSubMenu()} returns true, this method will be called when the
+ * menu item is selected to prepare the submenu for presentation to the user. Apps
+ * may use this to create or alter submenu content right before display.
+ *
+ * @param subMenu Submenu that will be displayed
+ */
+ public void onPrepareSubMenu(SubMenu subMenu) {
+ }
+
+ /**
+ * Notify the system that the visibility of an action view's sub-UI such as
+ * an anchored popup has changed. This will affect how other system
+ * visibility notifications occur.
+ *
+ * @hide Pending future API approval
+ */
+ public void subUiVisibilityChanged(boolean isVisible) {
+ if (mSubUiVisibilityListener != null) {
+ mSubUiVisibilityListener.onSubUiVisibilityChanged(isVisible);
+ }
+ }
+
+ /**
+ * @hide Internal use only
+ */
+ public void setSubUiVisibilityListener(SubUiVisibilityListener listener) {
+ mSubUiVisibilityListener = listener;
+ }
+
+ /**
+ * Set a listener to be notified when this ActionProvider's overridden visibility changes.
+ * This should only be used by MenuItem implementations.
+ *
+ * @param listener listener to set
+ */
+ public void setVisibilityListener(VisibilityListener listener) {
+ if (mVisibilityListener != null) {
+ Log.w(TAG, "setVisibilityListener: Setting a new ActionProvider.VisibilityListener " +
+ "when one is already set. Are you reusing this " + getClass().getSimpleName() +
+ " instance while it is still in use somewhere else?");
+ }
+ mVisibilityListener = listener;
+ }
+
+ /**
+ * @hide
+ */
+ public void reset() {
+ mVisibilityListener = null;
+ mSubUiVisibilityListener = null;
+ }
+
+ /**
+ * @hide Internal use only
+ */
+ public interface SubUiVisibilityListener {
+ public void onSubUiVisibilityChanged(boolean isVisible);
+ }
+
+ /**
+ * Listens to changes in visibility as reported by {@link ActionProvider#refreshVisibility()}.
+ *
+ * @see ActionProvider#overridesItemVisibility()
+ * @see ActionProvider#isVisible()
+ */
+ public interface VisibilityListener {
+ public void onActionProviderVisibilityChanged(boolean isVisible);
+ }
+}
diff --git a/android/view/AppTransitionAnimationSpec.java b/android/view/AppTransitionAnimationSpec.java
new file mode 100644
index 00000000..86a5fb76
--- /dev/null
+++ b/android/view/AppTransitionAnimationSpec.java
@@ -0,0 +1,60 @@
+package android.view;
+
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Holds information about how the next app transition animation should be executed.
+ *
+ * This class is intended to be used with IWindowManager.overridePendingAppTransition* methods when
+ * simple arguments are not enough to describe the animation.
+ *
+ * @hide
+ */
+public class AppTransitionAnimationSpec implements Parcelable {
+ public final int taskId;
+ public final GraphicBuffer buffer;
+ public final Rect rect;
+
+ public AppTransitionAnimationSpec(int taskId, GraphicBuffer buffer, Rect rect) {
+ this.taskId = taskId;
+ this.rect = rect;
+ this.buffer = buffer;
+ }
+
+ public AppTransitionAnimationSpec(Parcel in) {
+ taskId = in.readInt();
+ rect = in.readParcelable(null);
+ buffer = in.readParcelable(null);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(taskId);
+ dest.writeParcelable(rect, 0 /* flags */);
+ dest.writeParcelable(buffer, 0);
+ }
+
+ public static final Parcelable.Creator<AppTransitionAnimationSpec> CREATOR
+ = new Parcelable.Creator<AppTransitionAnimationSpec>() {
+ public AppTransitionAnimationSpec createFromParcel(Parcel in) {
+ return new AppTransitionAnimationSpec(in);
+ }
+
+ public AppTransitionAnimationSpec[] newArray(int size) {
+ return new AppTransitionAnimationSpec[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "{taskId: " + taskId + ", buffer: " + buffer + ", rect: " + rect + "}";
+ }
+}
diff --git a/android/view/AttachInfo_Accessor.java b/android/view/AttachInfo_Accessor.java
new file mode 100644
index 00000000..4445a223
--- /dev/null
+++ b/android/view/AttachInfo_Accessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 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 com.android.layoutlib.bridge.android.BridgeWindow;
+import com.android.layoutlib.bridge.android.BridgeWindowSession;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.View.AttachInfo;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class AttachInfo_Accessor {
+
+ public static void setAttachInfo(View view) {
+ Context context = view.getContext();
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ ViewRootImpl root = new ViewRootImpl(context, display);
+ AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
+ display, root, new Handler(), null, context);
+ info.mHasWindowFocus = true;
+ info.mWindowVisibility = View.VISIBLE;
+ info.mInTouchMode = false; // this is so that we can display selections.
+ info.mHardwareAccelerated = false;
+ view.dispatchAttachedToWindow(info, 0);
+ }
+
+ public static void dispatchOnPreDraw(View view) {
+ view.mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+ }
+
+ public static void detachFromWindow(View view) {
+ if (view != null) {
+ view.dispatchDetachedFromWindow();
+ }
+ }
+
+ public static ViewRootImpl getRootView(View view) {
+ return view.mAttachInfo != null ? view.mAttachInfo.mViewRootImpl : null;
+ }
+}
diff --git a/android/view/BatchedInputEventReceiver.java b/android/view/BatchedInputEventReceiver.java
new file mode 100644
index 00000000..b1d28e00
--- /dev/null
+++ b/android/view/BatchedInputEventReceiver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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.os.Looper;
+
+/**
+ * Similar to {@link InputEventReceiver}, but batches events to vsync boundaries when possible.
+ * @hide
+ */
+public class BatchedInputEventReceiver extends InputEventReceiver {
+ Choreographer mChoreographer;
+ private boolean mBatchedInputScheduled;
+
+ public BatchedInputEventReceiver(
+ InputChannel inputChannel, Looper looper, Choreographer choreographer) {
+ super(inputChannel, looper);
+ mChoreographer = choreographer;
+ }
+
+ @Override
+ public void onBatchedInputEventPending() {
+ scheduleBatchedInput();
+ }
+
+ @Override
+ public void dispose() {
+ unscheduleBatchedInput();
+ super.dispose();
+ }
+
+ void doConsumeBatchedInput(long frameTimeNanos) {
+ if (mBatchedInputScheduled) {
+ mBatchedInputScheduled = false;
+ if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) {
+ // If we consumed a batch here, we want to go ahead and schedule the
+ // consumption of batched input events on the next frame. Otherwise, we would
+ // wait until we have more input events pending and might get starved by other
+ // things occurring in the process. If the frame time is -1, however, then
+ // we're in a non-batching mode, so there's no need to schedule this.
+ scheduleBatchedInput();
+ }
+ }
+ }
+
+ private void scheduleBatchedInput() {
+ if (!mBatchedInputScheduled) {
+ mBatchedInputScheduled = true;
+ mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
+ }
+ }
+
+ private void unscheduleBatchedInput() {
+ if (mBatchedInputScheduled) {
+ mBatchedInputScheduled = false;
+ mChoreographer.removeCallbacks(
+ Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
+ }
+ }
+
+ private final class BatchedInputRunnable implements Runnable {
+ @Override
+ public void run() {
+ doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+ }
+ }
+ private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable();
+}
diff --git a/android/view/BridgeInflater.java b/android/view/BridgeInflater.java
new file mode 100644
index 00000000..58d8c527
--- /dev/null
+++ b/android/view/BridgeInflater.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2008 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 com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.MergeCookie;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.MockView;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
+import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.util.ReflectionUtils;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.NumberPicker;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
+import static com.android.SdkConstants.BUTTON;
+import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
+import static com.android.SdkConstants.CHECK_BOX;
+import static com.android.SdkConstants.EDIT_TEXT;
+import static com.android.SdkConstants.IMAGE_BUTTON;
+import static com.android.SdkConstants.IMAGE_VIEW;
+import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
+import static com.android.SdkConstants.RADIO_BUTTON;
+import static com.android.SdkConstants.SEEK_BAR;
+import static com.android.SdkConstants.SPINNER;
+import static com.android.SdkConstants.TEXT_VIEW;
+import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
+
+/**
+ * Custom implementation of {@link LayoutInflater} to handle custom views.
+ */
+public final class BridgeInflater extends LayoutInflater {
+
+ private final LayoutlibCallback mLayoutlibCallback;
+ /**
+ * If true, the inflater will try to replace the framework widgets with the AppCompat versions.
+ * Ideally, this should be based on the activity being an AppCompat activity but since that is
+ * not trivial to check from layoutlib, we currently base the decision on the current theme
+ * being an AppCompat theme.
+ */
+ private boolean mLoadAppCompatViews;
+ /**
+ * This set contains the framework views that have an AppCompat version but failed to load.
+ * This might happen because not all widgets are contained in all versions of the support
+ * library.
+ * This will help us to avoid trying to load the AppCompat version multiple times if it
+ * doesn't exist.
+ */
+ private Set<String> mFailedAppCompatViews = new HashSet<>();
+ private boolean mIsInMerge = false;
+ private ResourceReference mResourceReference;
+ private Map<View, String> mOpenDrawerLayouts;
+
+ // Keep in sync with the same value in LayoutInflater.
+ private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
+
+ private static final String APPCOMPAT_WIDGET_PREFIX = "android.support.v7.widget.AppCompat";
+ /** List of platform widgets that have an AppCompat version */
+ private static final Set<String> APPCOMPAT_VIEWS = Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(TEXT_VIEW, IMAGE_VIEW, BUTTON, EDIT_TEXT, SPINNER,
+ IMAGE_BUTTON, CHECK_BOX, RADIO_BUTTON, CHECKED_TEXT_VIEW,
+ AUTO_COMPLETE_TEXT_VIEW, MULTI_AUTO_COMPLETE_TEXT_VIEW, "RatingBar",
+ SEEK_BAR)));
+
+ /**
+ * List of class prefixes which are tried first by default.
+ * <p/>
+ * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
+ */
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit.",
+ "android.app."
+ };
+
+ public static String[] getClassPrefixList() {
+ return sClassPrefixList;
+ }
+
+ private BridgeInflater(LayoutInflater original, Context newContext) {
+ super(original, newContext);
+ newContext = getBaseContext(newContext);
+ if (newContext instanceof BridgeContext) {
+ mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
+ mLoadAppCompatViews = ((BridgeContext) newContext).isAppCompatTheme();
+ } else {
+ mLayoutlibCallback = null;
+ mLoadAppCompatViews = false;
+ }
+ }
+
+ /**
+ * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object.
+ *
+ * @param context The Android application context.
+ * @param layoutlibCallback the {@link LayoutlibCallback} object.
+ */
+ public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) {
+ super(context);
+ mLayoutlibCallback = layoutlibCallback;
+ mConstructorArgs[0] = context;
+ mLoadAppCompatViews = context.isAppCompatTheme();
+ }
+
+ @Override
+ public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ View view = null;
+
+ try {
+ if (mLoadAppCompatViews
+ && APPCOMPAT_VIEWS.contains(name)
+ && !mFailedAppCompatViews.contains(name)) {
+ // We are using an AppCompat theme so try to load the appcompat views
+ view = loadCustomView(APPCOMPAT_WIDGET_PREFIX + name, attrs, true);
+
+ if (view == null) {
+ mFailedAppCompatViews.add(name); // Do not try this one anymore
+ }
+ }
+
+ if (view == null) {
+ // First try to find a class using the default Android prefixes
+ for (String prefix : sClassPrefixList) {
+ try {
+ view = createView(name, prefix, attrs);
+ if (view != null) {
+ break;
+ }
+ } catch (ClassNotFoundException e) {
+ // Ignore. We'll try again using the base class below.
+ }
+ }
+
+ // Next try using the parent loader. This will most likely only work for
+ // fully-qualified class names.
+ try {
+ if (view == null) {
+ view = super.onCreateView(name, attrs);
+ }
+ } catch (ClassNotFoundException e) {
+ // Ignore. We'll try again using the custom view loader below.
+ }
+ }
+
+ // Finally try again using the custom view loader
+ if (view == null) {
+ view = loadCustomView(name, attrs);
+ }
+ } catch (InflateException e) {
+ // Don't catch the InflateException below as that results in hiding the real cause.
+ throw e;
+ } catch (Exception e) {
+ // Wrap the real exception in a ClassNotFoundException, so that the calling method
+ // can deal with it.
+ throw new ClassNotFoundException("onCreateView", e);
+ }
+
+ setupViewInContext(view, attrs);
+
+ return view;
+ }
+
+ @Override
+ public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
+ boolean ignoreThemeAttr) {
+ View view = null;
+ if (name.equals("view")) {
+ // This is usually done by the superclass but this allows us catching the error and
+ // reporting something useful.
+ name = attrs.getAttributeValue(null, "class");
+
+ if (name == null) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to inflate view tag without " +
+ "class attribute", null);
+ // We weren't able to resolve the view so we just pass a mock View to be able to
+ // continue rendering.
+ view = new MockView(context, attrs);
+ ((MockView) view).setText("view");
+ }
+ }
+
+ try {
+ if (view == null) {
+ view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr);
+ }
+ } catch (InflateException e) {
+ // Creation of ContextThemeWrapper code is same as in the super method.
+ // Apply a theme wrapper, if allowed and one is specified.
+ if (!ignoreThemeAttr) {
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ if (themeResId != 0) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+ }
+ if (!(e.getCause() instanceof ClassNotFoundException)) {
+ // There is some unknown inflation exception in inflating a View that was found.
+ view = new MockView(context, attrs);
+ ((MockView) view).setText(name);
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null);
+ } else {
+ final Object lastContext = mConstructorArgs[0];
+ mConstructorArgs[0] = context;
+ // try to load the class from using the custom view loader
+ try {
+ view = loadCustomView(name, attrs);
+ } catch (Exception e2) {
+ // Wrap the real exception in an InflateException so that the calling
+ // method can deal with it.
+ InflateException exception = new InflateException();
+ if (!e2.getClass().equals(ClassNotFoundException.class)) {
+ exception.initCause(e2);
+ } else {
+ exception.initCause(e);
+ }
+ throw exception;
+ } finally {
+ mConstructorArgs[0] = lastContext;
+ }
+ }
+ }
+
+ setupViewInContext(view, attrs);
+
+ return view;
+ }
+
+ @Override
+ public View inflate(int resource, ViewGroup root) {
+ Context context = getContext();
+ context = getBaseContext(context);
+ if (context instanceof BridgeContext) {
+ BridgeContext bridgeContext = (BridgeContext)context;
+
+ ResourceValue value = null;
+
+ @SuppressWarnings("deprecation")
+ Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
+ if (layoutInfo != null) {
+ value = bridgeContext.getRenderResources().getFrameworkResource(
+ ResourceType.LAYOUT, layoutInfo.getSecond());
+ } else {
+ layoutInfo = mLayoutlibCallback.resolveResourceId(resource);
+
+ if (layoutInfo != null) {
+ value = bridgeContext.getRenderResources().getProjectResource(
+ ResourceType.LAYOUT, layoutInfo.getSecond());
+ }
+ }
+
+ if (value != null) {
+ File f = new File(value.getValue());
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f, true);
+
+ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
+ parser, bridgeContext, value.isFramework());
+
+ return inflate(bridgeParser, root);
+ } catch (Exception e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + f.getAbsolutePath(), e, null);
+
+ return null;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Instantiates the given view name and returns the instance. If the view doesn't exist, a
+ * MockView or null might be returned.
+ * @param name the custom view name
+ * @param attrs the {@link AttributeSet} to be passed to the view constructor
+ * @param silent if true, errors while loading the view won't be reported and, if the view
+ * doesn't exist, null will be returned.
+ */
+ private View loadCustomView(String name, AttributeSet attrs, boolean silent) throws Exception {
+ if (mLayoutlibCallback != null) {
+ // first get the classname in case it's not the node name
+ if (name.equals("view")) {
+ name = attrs.getAttributeValue(null, "class");
+ if (name == null) {
+ return null;
+ }
+ }
+
+ mConstructorArgs[1] = attrs;
+
+ Object customView = silent ?
+ mLayoutlibCallback.loadClass(name, mConstructorSignature, mConstructorArgs)
+ : mLayoutlibCallback.loadView(name, mConstructorSignature, mConstructorArgs);
+
+ if (customView instanceof View) {
+ return (View)customView;
+ }
+ }
+
+ return null;
+ }
+
+ private View loadCustomView(String name, AttributeSet attrs) throws Exception {
+ return loadCustomView(name, attrs, false);
+ }
+
+ private void setupViewInContext(View view, AttributeSet attrs) {
+ Context context = getContext();
+ context = getBaseContext(context);
+ if (context instanceof BridgeContext) {
+ BridgeContext bc = (BridgeContext) context;
+ // get the view key
+ Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
+ if (viewKey != null) {
+ bc.addViewKey(view, viewKey);
+ }
+ String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
+ if (scrollPosX != null && scrollPosX.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
+ bc.setScrollXPos(view, value);
+ }
+ String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
+ if (scrollPosY != null && scrollPosY.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
+ bc.setScrollYPos(view, value);
+ }
+ if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
+ Integer resourceId = null;
+ String attrListItemValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
+ BridgeConstants.ATTR_LIST_ITEM);
+ int attrItemCountValue = attrs.getAttributeIntValue(BridgeConstants.NS_TOOLS_URI,
+ BridgeConstants.ATTR_ITEM_COUNT, -1);
+ if (attrListItemValue != null && !attrListItemValue.isEmpty()) {
+ ResourceValue resValue = bc.getRenderResources().findResValue(attrListItemValue, false);
+ if (resValue.isFramework()) {
+ resourceId = Bridge.getResourceId(resValue.getResourceType(),
+ resValue.getName());
+ } else {
+ resourceId = mLayoutlibCallback.getResourceId(resValue.getResourceType(),
+ resValue.getName());
+ }
+ }
+ if (resourceId == null) {
+ resourceId = 0;
+ }
+ RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId, attrItemCountValue);
+ } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
+ String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
+ BridgeConstants.ATTR_OPEN_DRAWER);
+ if (attrVal != null) {
+ getDrawerLayoutMap().put(view, attrVal);
+ }
+ }
+ else if (view instanceof NumberPicker) {
+ NumberPicker numberPicker = (NumberPicker) view;
+ String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue");
+ if (minValue != null) {
+ numberPicker.setMinValue(Integer.parseInt(minValue));
+ }
+ String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue");
+ if (maxValue != null) {
+ numberPicker.setMaxValue(Integer.parseInt(maxValue));
+ }
+ }
+ else if (view instanceof ImageView) {
+ ImageView img = (ImageView) view;
+ Drawable drawable = img.getDrawable();
+ if (drawable instanceof Animatable) {
+ if (!((Animatable) drawable).isRunning()) {
+ ((Animatable) drawable).start();
+ }
+ }
+ }
+
+ }
+ }
+
+ public void setIsInMerge(boolean isInMerge) {
+ mIsInMerge = isInMerge;
+ }
+
+ public void setResourceReference(ResourceReference reference) {
+ mResourceReference = reference;
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new BridgeInflater(this, newContext);
+ }
+
+ /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
+ ResourceReference resourceReference, boolean isInMerge) {
+
+ if (!(attrs instanceof BridgeXmlBlockParser)) {
+ return null;
+ }
+ BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
+
+ // get the view key
+ Object viewKey = parser.getViewCookie();
+
+ if (viewKey == null) {
+ int currentDepth = parser.getDepth();
+
+ // test whether we are in an included file or in a adapter binding view.
+ BridgeXmlBlockParser previousParser = bc.getPreviousParser();
+ if (previousParser != null) {
+ // looks like we are inside an embedded layout.
+ // only apply the cookie of the calling node (<include>) if we are at the
+ // top level of the embedded layout. If there is a merge tag, then
+ // skip it and look for the 2nd level
+ int testDepth = isInMerge ? 2 : 1;
+ if (currentDepth == testDepth) {
+ viewKey = previousParser.getViewCookie();
+ // if we are in a merge, wrap the cookie in a MergeCookie.
+ if (viewKey != null && isInMerge) {
+ viewKey = new MergeCookie(viewKey);
+ }
+ }
+ } else if (resourceReference != null && currentDepth == 1) {
+ // else if there's a resource reference, this means we are in an adapter
+ // binding case. Set the resource ref as the view cookie only for the top
+ // level view.
+ viewKey = resourceReference;
+ }
+ }
+
+ return viewKey;
+ }
+
+ public void postInflateProcess(View view) {
+ if (mOpenDrawerLayouts != null) {
+ String gravity = mOpenDrawerLayouts.get(view);
+ if (gravity != null) {
+ DrawerLayoutUtil.openDrawer(view, gravity);
+ }
+ mOpenDrawerLayouts.remove(view);
+ }
+ }
+
+ @NonNull
+ private Map<View, String> getDrawerLayoutMap() {
+ if (mOpenDrawerLayouts == null) {
+ mOpenDrawerLayouts = new HashMap<View, String>(4);
+ }
+ return mOpenDrawerLayouts;
+ }
+
+ public void onDoneInflation() {
+ if (mOpenDrawerLayouts != null) {
+ mOpenDrawerLayouts.clear();
+ }
+ }
+}
diff --git a/android/view/Choreographer.java b/android/view/Choreographer.java
new file mode 100644
index 00000000..2ffa1c5e
--- /dev/null
+++ b/android/view/Choreographer.java
@@ -0,0 +1,988 @@
+/*
+ * Copyright (C) 2011 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 static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
+import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
+
+import android.annotation.TestApi;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.Log;
+import android.util.TimeUtils;
+import android.view.animation.AnimationUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Coordinates the timing of animations, input and drawing.
+ * <p>
+ * The choreographer receives timing pulses (such as vertical synchronization)
+ * from the display subsystem then schedules work to occur as part of rendering
+ * the next display frame.
+ * </p><p>
+ * Applications typically interact with the choreographer indirectly using
+ * higher level abstractions in the animation framework or the view hierarchy.
+ * Here are some examples of things you can do using the higher-level APIs.
+ * </p>
+ * <ul>
+ * <li>To post an animation to be processed on a regular time basis synchronized with
+ * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
+ * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
+ * frame, use {@link View#postOnAnimation}.</li>
+ * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
+ * frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
+ * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
+ * next display frame, use {@link View#postInvalidateOnAnimation()} or
+ * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
+ * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
+ * sync with display frame rendering, do nothing. This already happens automatically.
+ * {@link View#onDraw} will be called at the appropriate time.</li>
+ * </ul>
+ * <p>
+ * However, there are a few cases where you might want to use the functions of the
+ * choreographer directly in your application. Here are some examples.
+ * </p>
+ * <ul>
+ * <li>If your application does its rendering in a different thread, possibly using GL,
+ * or does not use the animation framework or view hierarchy at all
+ * and you want to ensure that it is appropriately synchronized with the display, then use
+ * {@link Choreographer#postFrameCallback}.</li>
+ * <li>... and that's about it.</li>
+ * </ul>
+ * <p>
+ * Each {@link Looper} thread has its own choreographer. Other threads can
+ * post callbacks to run on the choreographer but they will run on the {@link Looper}
+ * to which the choreographer belongs.
+ * </p>
+ */
+public final class Choreographer {
+ private static final String TAG = "Choreographer";
+
+ // Prints debug messages about jank which was detected (low volume).
+ private static final boolean DEBUG_JANK = false;
+
+ // Prints debug messages about every frame and callback registered (high volume).
+ private static final boolean DEBUG_FRAMES = false;
+
+ // The default amount of time in ms between animation frames.
+ // When vsync is not enabled, we want to have some idea of how long we should
+ // wait before posting the next animation message. It is important that the
+ // default value be less than the true inter-frame delay on all devices to avoid
+ // situations where we might skip frames by waiting too long (we must compensate
+ // for jitter and hardware variations). Regardless of this value, the animation
+ // and display loop is ultimately rate-limited by how fast new graphics buffers can
+ // be dequeued.
+ private static final long DEFAULT_FRAME_DELAY = 10;
+
+ // The number of milliseconds between animation frames.
+ private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
+
+ // Thread local storage for the choreographer.
+ private static final ThreadLocal<Choreographer> sThreadInstance =
+ new ThreadLocal<Choreographer>() {
+ @Override
+ protected Choreographer initialValue() {
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalStateException("The current thread must have a looper!");
+ }
+ return new Choreographer(looper, VSYNC_SOURCE_APP);
+ }
+ };
+
+ // Thread local storage for the SF choreographer.
+ private static final ThreadLocal<Choreographer> sSfThreadInstance =
+ new ThreadLocal<Choreographer>() {
+ @Override
+ protected Choreographer initialValue() {
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalStateException("The current thread must have a looper!");
+ }
+ return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
+ }
+ };
+
+ // Enable/disable vsync for animations and drawing.
+ private static final boolean USE_VSYNC = SystemProperties.getBoolean(
+ "debug.choreographer.vsync", true);
+
+ // Enable/disable using the frame time instead of returning now.
+ private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
+ "debug.choreographer.frametime", true);
+
+ // Set a limit to warn about skipped frames.
+ // Skipped frames imply jank.
+ private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
+ "debug.choreographer.skipwarning", 30);
+
+ private static final int MSG_DO_FRAME = 0;
+ private static final int MSG_DO_SCHEDULE_VSYNC = 1;
+ private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
+
+ // All frame callbacks posted by applications have this token.
+ private static final Object FRAME_CALLBACK_TOKEN = new Object() {
+ public String toString() { return "FRAME_CALLBACK_TOKEN"; }
+ };
+
+ private final Object mLock = new Object();
+
+ private final Looper mLooper;
+ private final FrameHandler mHandler;
+
+ // The display event receiver can only be accessed by the looper thread to which
+ // it is attached. We take care to ensure that we post message to the looper
+ // if appropriate when interacting with the display event receiver.
+ private final FrameDisplayEventReceiver mDisplayEventReceiver;
+
+ private CallbackRecord mCallbackPool;
+
+ private final CallbackQueue[] mCallbackQueues;
+
+ private boolean mFrameScheduled;
+ private boolean mCallbacksRunning;
+ private long mLastFrameTimeNanos;
+ private long mFrameIntervalNanos;
+ private boolean mDebugPrintNextFrameTimeDelta;
+
+ /**
+ * Contains information about the current frame for jank-tracking,
+ * mainly timings of key events along with a bit of metadata about
+ * view tree state
+ *
+ * TODO: Is there a better home for this? Currently Choreographer
+ * is the only one with CALLBACK_ANIMATION start time, hence why this
+ * resides here.
+ *
+ * @hide
+ */
+ FrameInfo mFrameInfo = new FrameInfo();
+
+ /**
+ * Must be kept in sync with CALLBACK_* ints below, used to index into this array.
+ * @hide
+ */
+ private static final String[] CALLBACK_TRACE_TITLES = {
+ "input", "animation", "traversal", "commit"
+ };
+
+ /**
+ * Callback type: Input callback. Runs first.
+ * @hide
+ */
+ public static final int CALLBACK_INPUT = 0;
+
+ /**
+ * Callback type: Animation callback. Runs before traversals.
+ * @hide
+ */
+ @TestApi
+ public static final int CALLBACK_ANIMATION = 1;
+
+ /**
+ * Callback type: Traversal callback. Handles layout and draw. Runs
+ * after all other asynchronous messages have been handled.
+ * @hide
+ */
+ public static final int CALLBACK_TRAVERSAL = 2;
+
+ /**
+ * Callback type: Commit callback. Handles post-draw operations for the frame.
+ * Runs after traversal completes. The {@link #getFrameTime() frame time} reported
+ * during this callback may be updated to reflect delays that occurred while
+ * traversals were in progress in case heavy layout operations caused some frames
+ * to be skipped. The frame time reported during this callback provides a better
+ * estimate of the start time of the frame in which animations (and other updates
+ * to the view hierarchy state) actually took effect.
+ * @hide
+ */
+ public static final int CALLBACK_COMMIT = 3;
+
+ private static final int CALLBACK_LAST = CALLBACK_COMMIT;
+
+ private Choreographer(Looper looper, int vsyncSource) {
+ mLooper = looper;
+ mHandler = new FrameHandler(looper);
+ mDisplayEventReceiver = USE_VSYNC
+ ? new FrameDisplayEventReceiver(looper, vsyncSource)
+ : null;
+ mLastFrameTimeNanos = Long.MIN_VALUE;
+
+ mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
+
+ mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
+ for (int i = 0; i <= CALLBACK_LAST; i++) {
+ mCallbackQueues[i] = new CallbackQueue();
+ }
+ }
+
+ private static float getRefreshRate() {
+ DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
+ Display.DEFAULT_DISPLAY);
+ return di.getMode().getRefreshRate();
+ }
+
+ /**
+ * Gets the choreographer for the calling thread. Must be called from
+ * a thread that already has a {@link android.os.Looper} associated with it.
+ *
+ * @return The choreographer for this thread.
+ * @throws IllegalStateException if the thread does not have a looper.
+ */
+ public static Choreographer getInstance() {
+ return sThreadInstance.get();
+ }
+
+ /**
+ * @hide
+ */
+ public static Choreographer getSfInstance() {
+ return sSfThreadInstance.get();
+ }
+
+ /** Destroys the calling thread's choreographer
+ * @hide
+ */
+ public static void releaseInstance() {
+ Choreographer old = sThreadInstance.get();
+ sThreadInstance.remove();
+ old.dispose();
+ }
+
+ private void dispose() {
+ mDisplayEventReceiver.dispose();
+ }
+
+ /**
+ * The amount of time, in milliseconds, between each frame of the animation.
+ * <p>
+ * This is a requested time that the animation will attempt to honor, but the actual delay
+ * between frames may be different, depending on system load and capabilities. This is a static
+ * function because the same delay will be applied to all animations, since they are all
+ * run off of a single timing loop.
+ * </p><p>
+ * The frame delay may be ignored when the animation system uses an external timing
+ * source, such as the display refresh rate (vsync), to govern animations.
+ * </p>
+ *
+ * @return the requested time between frames, in milliseconds
+ * @hide
+ */
+ @TestApi
+ public static long getFrameDelay() {
+ return sFrameDelay;
+ }
+
+ /**
+ * The amount of time, in milliseconds, between each frame of the animation.
+ * <p>
+ * This is a requested time that the animation will attempt to honor, but the actual delay
+ * between frames may be different, depending on system load and capabilities. This is a static
+ * function because the same delay will be applied to all animations, since they are all
+ * run off of a single timing loop.
+ * </p><p>
+ * The frame delay may be ignored when the animation system uses an external timing
+ * source, such as the display refresh rate (vsync), to govern animations.
+ * </p>
+ *
+ * @param frameDelay the requested time between frames, in milliseconds
+ * @hide
+ */
+ @TestApi
+ public static void setFrameDelay(long frameDelay) {
+ sFrameDelay = frameDelay;
+ }
+
+ /**
+ * Subtracts typical frame delay time from a delay interval in milliseconds.
+ * <p>
+ * This method can be used to compensate for animation delay times that have baked
+ * in assumptions about the frame delay. For example, it's quite common for code to
+ * assume a 60Hz frame time and bake in a 16ms delay. When we call
+ * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
+ * posting the animation callback but let the animation timer take care of the remaining
+ * frame delay time.
+ * </p><p>
+ * This method is somewhat conservative about how much of the frame delay it
+ * subtracts. It uses the same value returned by {@link #getFrameDelay} which by
+ * default is 10ms even though many parts of the system assume 16ms. Consequently,
+ * we might still wait 6ms before posting an animation callback that we want to run
+ * on the next frame, but this is much better than waiting a whole 16ms and likely
+ * missing the deadline.
+ * </p>
+ *
+ * @param delayMillis The original delay time including an assumed frame delay.
+ * @return The adjusted delay time with the assumed frame delay subtracted out.
+ * @hide
+ */
+ public static long subtractFrameDelay(long delayMillis) {
+ final long frameDelay = sFrameDelay;
+ return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
+ }
+
+ /**
+ * @return The refresh rate as the nanoseconds between frames
+ * @hide
+ */
+ public long getFrameIntervalNanos() {
+ return mFrameIntervalNanos;
+ }
+
+ void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+ writer.print(prefix); writer.println("Choreographer:");
+ writer.print(innerPrefix); writer.print("mFrameScheduled=");
+ writer.println(mFrameScheduled);
+ writer.print(innerPrefix); writer.print("mLastFrameTime=");
+ writer.println(TimeUtils.formatUptime(mLastFrameTimeNanos / 1000000));
+ }
+
+ /**
+ * Posts a callback to run on the next frame.
+ * <p>
+ * The callback runs once then is automatically removed.
+ * </p>
+ *
+ * @param callbackType The callback type.
+ * @param action The callback action to run during the next frame.
+ * @param token The callback token, or null if none.
+ *
+ * @see #removeCallbacks
+ * @hide
+ */
+ public void postCallback(int callbackType, Runnable action, Object token) {
+ postCallbackDelayed(callbackType, action, token, 0);
+ }
+
+ /**
+ * Posts a callback to run on the next frame after the specified delay.
+ * <p>
+ * The callback runs once then is automatically removed.
+ * </p>
+ *
+ * @param callbackType The callback type.
+ * @param action The callback action to run during the next frame after the specified delay.
+ * @param token The callback token, or null if none.
+ * @param delayMillis The delay time in milliseconds.
+ *
+ * @see #removeCallback
+ * @hide
+ */
+ public void postCallbackDelayed(int callbackType,
+ Runnable action, Object token, long delayMillis) {
+ if (action == null) {
+ throw new IllegalArgumentException("action must not be null");
+ }
+ if (callbackType < 0 || callbackType > CALLBACK_LAST) {
+ throw new IllegalArgumentException("callbackType is invalid");
+ }
+
+ postCallbackDelayedInternal(callbackType, action, token, delayMillis);
+ }
+
+ private void postCallbackDelayedInternal(int callbackType,
+ Object action, Object token, long delayMillis) {
+ if (DEBUG_FRAMES) {
+ Log.d(TAG, "PostCallback: type=" + callbackType
+ + ", action=" + action + ", token=" + token
+ + ", delayMillis=" + delayMillis);
+ }
+
+ synchronized (mLock) {
+ final long now = SystemClock.uptimeMillis();
+ final long dueTime = now + delayMillis;
+ mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
+
+ if (dueTime <= now) {
+ scheduleFrameLocked(now);
+ } else {
+ Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
+ msg.arg1 = callbackType;
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, dueTime);
+ }
+ }
+ }
+
+ /**
+ * Removes callbacks that have the specified action and token.
+ *
+ * @param callbackType The callback type.
+ * @param action The action property of the callbacks to remove, or null to remove
+ * callbacks with any action.
+ * @param token The token property of the callbacks to remove, or null to remove
+ * callbacks with any token.
+ *
+ * @see #postCallback
+ * @see #postCallbackDelayed
+ * @hide
+ */
+ public void removeCallbacks(int callbackType, Runnable action, Object token) {
+ if (callbackType < 0 || callbackType > CALLBACK_LAST) {
+ throw new IllegalArgumentException("callbackType is invalid");
+ }
+
+ removeCallbacksInternal(callbackType, action, token);
+ }
+
+ private void removeCallbacksInternal(int callbackType, Object action, Object token) {
+ if (DEBUG_FRAMES) {
+ Log.d(TAG, "RemoveCallbacks: type=" + callbackType
+ + ", action=" + action + ", token=" + token);
+ }
+
+ synchronized (mLock) {
+ mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
+ if (action != null && token == null) {
+ mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
+ }
+ }
+ }
+
+ /**
+ * Posts a frame callback to run on the next frame.
+ * <p>
+ * The callback runs once then is automatically removed.
+ * </p>
+ *
+ * @param callback The frame callback to run during the next frame.
+ *
+ * @see #postFrameCallbackDelayed
+ * @see #removeFrameCallback
+ */
+ public void postFrameCallback(FrameCallback callback) {
+ postFrameCallbackDelayed(callback, 0);
+ }
+
+ /**
+ * Posts a frame callback to run on the next frame after the specified delay.
+ * <p>
+ * The callback runs once then is automatically removed.
+ * </p>
+ *
+ * @param callback The frame callback to run during the next frame.
+ * @param delayMillis The delay time in milliseconds.
+ *
+ * @see #postFrameCallback
+ * @see #removeFrameCallback
+ */
+ public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+
+ postCallbackDelayedInternal(CALLBACK_ANIMATION,
+ callback, FRAME_CALLBACK_TOKEN, delayMillis);
+ }
+
+ /**
+ * Removes a previously posted frame callback.
+ *
+ * @param callback The frame callback to remove.
+ *
+ * @see #postFrameCallback
+ * @see #postFrameCallbackDelayed
+ */
+ public void removeFrameCallback(FrameCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+
+ removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
+ }
+
+ /**
+ * Gets the time when the current frame started.
+ * <p>
+ * This method provides the time in milliseconds when the frame started being rendered.
+ * The frame time provides a stable time base for synchronizing animations
+ * and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
+ * or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
+ * time helps to reduce inter-frame jitter because the frame time is fixed at the time
+ * the frame was scheduled to start, regardless of when the animations or drawing
+ * callback actually runs. All callbacks that run as part of rendering a frame will
+ * observe the same frame time so using the frame time also helps to synchronize effects
+ * that are performed by different callbacks.
+ * </p><p>
+ * Please note that the framework already takes care to process animations and
+ * drawing using the frame time as a stable time base. Most applications should
+ * not need to use the frame time information directly.
+ * </p><p>
+ * This method should only be called from within a callback.
+ * </p>
+ *
+ * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
+ *
+ * @throws IllegalStateException if no frame is in progress.
+ * @hide
+ */
+ public long getFrameTime() {
+ return getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
+ }
+
+ /**
+ * Same as {@link #getFrameTime()} but with nanosecond precision.
+ *
+ * @return The frame start time, in the {@link System#nanoTime()} time base.
+ *
+ * @throws IllegalStateException if no frame is in progress.
+ * @hide
+ */
+ public long getFrameTimeNanos() {
+ synchronized (mLock) {
+ if (!mCallbacksRunning) {
+ throw new IllegalStateException("This method must only be called as "
+ + "part of a callback while a frame is in progress.");
+ }
+ return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
+ }
+ }
+
+ /**
+ * Like {@link #getLastFrameTimeNanos}, but always returns the last frame time, not matter
+ * whether callbacks are currently running.
+ * @return The frame start time of the last frame, in the {@link System#nanoTime()} time base.
+ * @hide
+ */
+ public long getLastFrameTimeNanos() {
+ synchronized (mLock) {
+ return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
+ }
+ }
+
+ private void scheduleFrameLocked(long now) {
+ if (!mFrameScheduled) {
+ mFrameScheduled = true;
+ if (USE_VSYNC) {
+ if (DEBUG_FRAMES) {
+ Log.d(TAG, "Scheduling next frame on vsync.");
+ }
+
+ // If running on the Looper thread, then schedule the vsync immediately,
+ // otherwise post a message to schedule the vsync from the UI thread
+ // as soon as possible.
+ if (isRunningOnLooperThreadLocked()) {
+ scheduleVsyncLocked();
+ } else {
+ Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtFrontOfQueue(msg);
+ }
+ } else {
+ final long nextFrameTime = Math.max(
+ mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
+ if (DEBUG_FRAMES) {
+ Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
+ }
+ Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, nextFrameTime);
+ }
+ }
+ }
+
+ void doFrame(long frameTimeNanos, int frame) {
+ final long startNanos;
+ synchronized (mLock) {
+ if (!mFrameScheduled) {
+ return; // no work to do
+ }
+
+ if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+ mDebugPrintNextFrameTimeDelta = false;
+ Log.d(TAG, "Frame time delta: "
+ + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+ }
+
+ long intendedFrameTimeNanos = frameTimeNanos;
+ startNanos = System.nanoTime();
+ final long jitterNanos = startNanos - frameTimeNanos;
+ if (jitterNanos >= mFrameIntervalNanos) {
+ final long skippedFrames = jitterNanos / mFrameIntervalNanos;
+ if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
+ Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ + "The application may be doing too much work on its main thread.");
+ }
+ final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ + "which is more than the frame interval of "
+ + (mFrameIntervalNanos * 0.000001f) + " ms! "
+ + "Skipping " + skippedFrames + " frames and setting frame "
+ + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
+ }
+ frameTimeNanos = startNanos - lastFrameOffset;
+ }
+
+ if (frameTimeNanos < mLastFrameTimeNanos) {
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ + "previously skipped frame. Waiting for next vsync.");
+ }
+ scheduleVsyncLocked();
+ return;
+ }
+
+ mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
+ mFrameScheduled = false;
+ mLastFrameTimeNanos = frameTimeNanos;
+ }
+
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
+ AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
+
+ mFrameInfo.markInputHandlingStart();
+ doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+ mFrameInfo.markAnimationsStart();
+ doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+ mFrameInfo.markPerformTraversalsStart();
+ doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+
+ doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+ } finally {
+ AnimationUtils.unlockAnimationClock();
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ if (DEBUG_FRAMES) {
+ final long endNanos = System.nanoTime();
+ Log.d(TAG, "Frame " + frame + ": Finished, took "
+ + (endNanos - startNanos) * 0.000001f + " ms, latency "
+ + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
+ }
+ }
+
+ void doCallbacks(int callbackType, long frameTimeNanos) {
+ CallbackRecord callbacks;
+ synchronized (mLock) {
+ // We use "now" to determine when callbacks become due because it's possible
+ // for earlier processing phases in a frame to post callbacks that should run
+ // in a following phase, such as an input event that causes an animation to start.
+ final long now = System.nanoTime();
+ callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
+ now / TimeUtils.NANOS_PER_MS);
+ if (callbacks == null) {
+ return;
+ }
+ mCallbacksRunning = true;
+
+ // Update the frame time if necessary when committing the frame.
+ // We only update the frame time if we are more than 2 frames late reaching
+ // the commit phase. This ensures that the frame time which is observed by the
+ // callbacks will always increase from one frame to the next and never repeat.
+ // We never want the next frame's starting frame time to end up being less than
+ // or equal to the previous frame's commit frame time. Keep in mind that the
+ // next frame has most likely already been scheduled by now so we play it
+ // safe by ensuring the commit time is always at least one frame behind.
+ if (callbackType == Choreographer.CALLBACK_COMMIT) {
+ final long jitterNanos = now - frameTimeNanos;
+ Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
+ if (jitterNanos >= 2 * mFrameIntervalNanos) {
+ final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ + mFrameIntervalNanos;
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ + " ms which is more than twice the frame interval of "
+ + (mFrameIntervalNanos * 0.000001f) + " ms! "
+ + "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ + " ms in the past.");
+ mDebugPrintNextFrameTimeDelta = true;
+ }
+ frameTimeNanos = now - lastFrameOffset;
+ mLastFrameTimeNanos = frameTimeNanos;
+ }
+ }
+ }
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
+ for (CallbackRecord c = callbacks; c != null; c = c.next) {
+ if (DEBUG_FRAMES) {
+ Log.d(TAG, "RunCallback: type=" + callbackType
+ + ", action=" + c.action + ", token=" + c.token
+ + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
+ }
+ c.run(frameTimeNanos);
+ }
+ } finally {
+ synchronized (mLock) {
+ mCallbacksRunning = false;
+ do {
+ final CallbackRecord next = callbacks.next;
+ recycleCallbackLocked(callbacks);
+ callbacks = next;
+ } while (callbacks != null);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ void doScheduleVsync() {
+ synchronized (mLock) {
+ if (mFrameScheduled) {
+ scheduleVsyncLocked();
+ }
+ }
+ }
+
+ void doScheduleCallback(int callbackType) {
+ synchronized (mLock) {
+ if (!mFrameScheduled) {
+ final long now = SystemClock.uptimeMillis();
+ if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
+ scheduleFrameLocked(now);
+ }
+ }
+ }
+ }
+
+ private void scheduleVsyncLocked() {
+ mDisplayEventReceiver.scheduleVsync();
+ }
+
+ private boolean isRunningOnLooperThreadLocked() {
+ return Looper.myLooper() == mLooper;
+ }
+
+ private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
+ CallbackRecord callback = mCallbackPool;
+ if (callback == null) {
+ callback = new CallbackRecord();
+ } else {
+ mCallbackPool = callback.next;
+ callback.next = null;
+ }
+ callback.dueTime = dueTime;
+ callback.action = action;
+ callback.token = token;
+ return callback;
+ }
+
+ private void recycleCallbackLocked(CallbackRecord callback) {
+ callback.action = null;
+ callback.token = null;
+ callback.next = mCallbackPool;
+ mCallbackPool = callback;
+ }
+
+ /**
+ * Implement this interface to receive a callback when a new display frame is
+ * being rendered. The callback is invoked on the {@link Looper} thread to
+ * which the {@link Choreographer} is attached.
+ */
+ public interface FrameCallback {
+ /**
+ * Called when a new display frame is being rendered.
+ * <p>
+ * This method provides the time in nanoseconds when the frame started being rendered.
+ * The frame time provides a stable time base for synchronizing animations
+ * and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
+ * or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
+ * time helps to reduce inter-frame jitter because the frame time is fixed at the time
+ * the frame was scheduled to start, regardless of when the animations or drawing
+ * callback actually runs. All callbacks that run as part of rendering a frame will
+ * observe the same frame time so using the frame time also helps to synchronize effects
+ * that are performed by different callbacks.
+ * </p><p>
+ * Please note that the framework already takes care to process animations and
+ * drawing using the frame time as a stable time base. Most applications should
+ * not need to use the frame time information directly.
+ * </p>
+ *
+ * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
+ * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
+ * to convert it to the {@link SystemClock#uptimeMillis()} time base.
+ */
+ public void doFrame(long frameTimeNanos);
+ }
+
+ private final class FrameHandler extends Handler {
+ public FrameHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DO_FRAME:
+ doFrame(System.nanoTime(), 0);
+ break;
+ case MSG_DO_SCHEDULE_VSYNC:
+ doScheduleVsync();
+ break;
+ case MSG_DO_SCHEDULE_CALLBACK:
+ doScheduleCallback(msg.arg1);
+ break;
+ }
+ }
+ }
+
+ private final class FrameDisplayEventReceiver extends DisplayEventReceiver
+ implements Runnable {
+ private boolean mHavePendingVsync;
+ private long mTimestampNanos;
+ private int mFrame;
+
+ public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
+ super(looper, vsyncSource);
+ }
+
+ @Override
+ public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
+ // Ignore vsync from secondary display.
+ // This can be problematic because the call to scheduleVsync() is a one-shot.
+ // We need to ensure that we will still receive the vsync from the primary
+ // display which is the one we really care about. Ideally we should schedule
+ // vsync for a particular display.
+ // At this time Surface Flinger won't send us vsyncs for secondary displays
+ // but that could change in the future so let's log a message to help us remember
+ // that we need to fix this.
+ if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
+ Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ + "this case yet. Choreographer needs a way to explicitly request "
+ + "vsync for a specific display to ensure it doesn't lose track "
+ + "of its scheduled vsync.");
+ scheduleVsync();
+ return;
+ }
+
+ // Post the vsync event to the Handler.
+ // The idea is to prevent incoming vsync events from completely starving
+ // the message queue. If there are no messages in the queue with timestamps
+ // earlier than the frame time, then the vsync event will be processed immediately.
+ // Otherwise, messages that predate the vsync event will be handled first.
+ long now = System.nanoTime();
+ if (timestampNanos > now) {
+ Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ + " ms in the future! Check that graphics HAL is generating vsync "
+ + "timestamps using the correct timebase.");
+ timestampNanos = now;
+ }
+
+ if (mHavePendingVsync) {
+ Log.w(TAG, "Already have a pending vsync event. There should only be "
+ + "one at a time.");
+ } else {
+ mHavePendingVsync = true;
+ }
+
+ mTimestampNanos = timestampNanos;
+ mFrame = frame;
+ Message msg = Message.obtain(mHandler, this);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+ }
+
+ @Override
+ public void run() {
+ mHavePendingVsync = false;
+ doFrame(mTimestampNanos, mFrame);
+ }
+ }
+
+ private static final class CallbackRecord {
+ public CallbackRecord next;
+ public long dueTime;
+ public Object action; // Runnable or FrameCallback
+ public Object token;
+
+ public void run(long frameTimeNanos) {
+ if (token == FRAME_CALLBACK_TOKEN) {
+ ((FrameCallback)action).doFrame(frameTimeNanos);
+ } else {
+ ((Runnable)action).run();
+ }
+ }
+ }
+
+ private final class CallbackQueue {
+ private CallbackRecord mHead;
+
+ public boolean hasDueCallbacksLocked(long now) {
+ return mHead != null && mHead.dueTime <= now;
+ }
+
+ public CallbackRecord extractDueCallbacksLocked(long now) {
+ CallbackRecord callbacks = mHead;
+ if (callbacks == null || callbacks.dueTime > now) {
+ return null;
+ }
+
+ CallbackRecord last = callbacks;
+ CallbackRecord next = last.next;
+ while (next != null) {
+ if (next.dueTime > now) {
+ last.next = null;
+ break;
+ }
+ last = next;
+ next = next.next;
+ }
+ mHead = next;
+ return callbacks;
+ }
+
+ public void addCallbackLocked(long dueTime, Object action, Object token) {
+ CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
+ CallbackRecord entry = mHead;
+ if (entry == null) {
+ mHead = callback;
+ return;
+ }
+ if (dueTime < entry.dueTime) {
+ callback.next = entry;
+ mHead = callback;
+ return;
+ }
+ while (entry.next != null) {
+ if (dueTime < entry.next.dueTime) {
+ callback.next = entry.next;
+ break;
+ }
+ entry = entry.next;
+ }
+ entry.next = callback;
+ }
+
+ public void removeCallbacksLocked(Object action, Object token) {
+ CallbackRecord predecessor = null;
+ for (CallbackRecord callback = mHead; callback != null;) {
+ final CallbackRecord next = callback.next;
+ if ((action == null || callback.action == action)
+ && (token == null || callback.token == token)) {
+ if (predecessor != null) {
+ predecessor.next = next;
+ } else {
+ mHead = next;
+ }
+ recycleCallbackLocked(callback);
+ } else {
+ predecessor = callback;
+ }
+ callback = next;
+ }
+ }
+ }
+}
diff --git a/android/view/Choreographer_Delegate.java b/android/view/Choreographer_Delegate.java
new file mode 100644
index 00000000..1dc77788
--- /dev/null
+++ b/android/view/Choreographer_Delegate.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.animation.AnimationHandler;
+import android.util.TimeUtils;
+import android.view.animation.AnimationUtils;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Choreographer}
+ *
+ * Through the layoutlib_create tool, the original methods of Choreographer have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Choreographer_Delegate {
+ private static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
+
+ @LayoutlibDelegate
+ public static Choreographer getInstance() {
+ if (mInstance.get() == null) {
+ mInstance.compareAndSet(null, Choreographer.getInstance_Original());
+ }
+
+ return mInstance.get();
+ }
+
+ @LayoutlibDelegate
+ public static float getRefreshRate() {
+ return 60.f;
+ }
+
+ @LayoutlibDelegate
+ static void scheduleVsyncLocked(Choreographer thisChoreographer) {
+ // do nothing
+ }
+
+ public static void doFrame(long frameTimeNanos) {
+ Choreographer thisChoreographer = Choreographer.getInstance();
+
+ AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
+
+ try {
+ thisChoreographer.mLastFrameTimeNanos = frameTimeNanos - thisChoreographer.getFrameIntervalNanos();
+ thisChoreographer.mFrameInfo.markInputHandlingStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markAnimationsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markPerformTraversalsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+ } finally {
+ AnimationUtils.unlockAnimationClock();
+ }
+ }
+
+ public static void clearFrames() {
+ Choreographer thisChoreographer = Choreographer.getInstance();
+
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_INPUT, null, null);
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, null, null);
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, null, null);
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_COMMIT, null, null);
+
+ // Release animation handler instance since it holds references to the callbacks
+ AnimationHandler.sAnimatorHandler.set(null);
+ }
+
+ public static void dispose() {
+ clearFrames();
+ Choreographer.releaseInstance();
+ }
+}
diff --git a/android/view/CollapsibleActionView.java b/android/view/CollapsibleActionView.java
new file mode 100644
index 00000000..ab2365eb
--- /dev/null
+++ b/android/view/CollapsibleActionView.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.view.MenuItem.OnActionExpandListener;
+
+/**
+ * When a {@link View} implements this interface it will receive callbacks
+ * when expanded or collapsed as an action view alongside the optional,
+ * app-specified callbacks to {@link OnActionExpandListener}.
+ *
+ * <p>See {@link MenuItem} for more information about action views.
+ * See {@link android.app.ActionBar} for more information about the action bar.
+ */
+public interface CollapsibleActionView {
+ /**
+ * Called when this view is expanded as an action view.
+ * See {@link MenuItem#expandActionView()}.
+ */
+ public void onActionViewExpanded();
+
+ /**
+ * Called when this view is collapsed as an action view.
+ * See {@link MenuItem#collapseActionView()}.
+ */
+ public void onActionViewCollapsed();
+}
diff --git a/android/view/ContextMenu.java b/android/view/ContextMenu.java
new file mode 100644
index 00000000..85fe4218
--- /dev/null
+++ b/android/view/ContextMenu.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2007 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.DrawableRes;
+import android.annotation.StringRes;
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.widget.AdapterView;
+
+/**
+ * Extension of {@link Menu} for context menus providing functionality to modify
+ * the header of the context menu.
+ * <p>
+ * Context menus do not support item shortcuts and item icons.
+ * <p>
+ * To show a context menu on long click, most clients will want to call
+ * {@link Activity#registerForContextMenu} and override
+ * {@link Activity#onCreateContextMenu}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+public interface ContextMenu extends Menu {
+ /**
+ * Sets the context menu header's title to the title given in <var>titleRes</var>
+ * resource identifier.
+ *
+ * @param titleRes The string resource identifier used for the title.
+ * @return This ContextMenu so additional setters can be called.
+ */
+ public ContextMenu setHeaderTitle(@StringRes int titleRes);
+
+ /**
+ * Sets the context menu header's title to the title given in <var>title</var>.
+ *
+ * @param title The character sequence used for the title.
+ * @return This ContextMenu so additional setters can be called.
+ */
+ public ContextMenu setHeaderTitle(CharSequence title);
+
+ /**
+ * Sets the context menu header's icon to the icon given in <var>iconRes</var>
+ * resource id.
+ *
+ * @param iconRes The resource identifier used for the icon.
+ * @return This ContextMenu so additional setters can be called.
+ */
+ public ContextMenu setHeaderIcon(@DrawableRes int iconRes);
+
+ /**
+ * Sets the context menu header's icon to the icon given in <var>icon</var>
+ * {@link Drawable}.
+ *
+ * @param icon The {@link Drawable} used for the icon.
+ * @return This ContextMenu so additional setters can be called.
+ */
+ public ContextMenu setHeaderIcon(Drawable icon);
+
+ /**
+ * Sets the header of the context menu to the {@link View} given in
+ * <var>view</var>. This replaces the header title and icon (and those
+ * replace this).
+ *
+ * @param view The {@link View} used for the header.
+ * @return This ContextMenu so additional setters can be called.
+ */
+ public ContextMenu setHeaderView(View view);
+
+ /**
+ * Clears the header of the context menu.
+ */
+ public void clearHeader();
+
+ /**
+ * Additional information regarding the creation of the context menu. For example,
+ * {@link AdapterView}s use this to pass the exact item position within the adapter
+ * that initiated the context menu.
+ */
+ public interface ContextMenuInfo {
+ }
+}
diff --git a/android/view/ContextThemeWrapper.java b/android/view/ContextThemeWrapper.java
new file mode 100644
index 00000000..d3cc175d
--- /dev/null
+++ b/android/view/ContextThemeWrapper.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2006 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.StyleRes;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+/**
+ * A context wrapper that allows you to modify or replace the theme of the
+ * wrapped context.
+ */
+public class ContextThemeWrapper extends ContextWrapper {
+ private int mThemeResource;
+ private Resources.Theme mTheme;
+ private LayoutInflater mInflater;
+ private Configuration mOverrideConfiguration;
+ private Resources mResources;
+
+ /**
+ * Creates a new context wrapper with no theme and no base context.
+ * <p class="note">
+ * <strong>Note:</strong> A base context <strong>must</strong> be attached
+ * using {@link #attachBaseContext(Context)} before calling any other
+ * method on the newly constructed context wrapper.
+ */
+ public ContextThemeWrapper() {
+ super(null);
+ }
+
+ /**
+ * Creates a new context wrapper with the specified theme.
+ * <p>
+ * The specified theme will be applied on top of the base context's theme.
+ * Any attributes not explicitly defined in the theme identified by
+ * <var>themeResId</var> will retain their original values.
+ *
+ * @param base the base context
+ * @param themeResId the resource ID of the theme to be applied on top of
+ * the base context's theme
+ */
+ public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
+ super(base);
+ mThemeResource = themeResId;
+ }
+
+ /**
+ * Creates a new context wrapper with the specified theme.
+ * <p>
+ * Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to
+ * this constructor will completely replace the base context's theme.
+ *
+ * @param base the base context
+ * @param theme the theme against which resources should be inflated
+ */
+ public ContextThemeWrapper(Context base, Resources.Theme theme) {
+ super(base);
+ mTheme = theme;
+ }
+
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+ }
+
+ /**
+ * Call to set an "override configuration" on this context -- this is
+ * a configuration that replies one or more values of the standard
+ * configuration that is applied to the context. See
+ * {@link Context#createConfigurationContext(Configuration)} for more
+ * information.
+ *
+ * <p>This method can only be called once, and must be called before any
+ * calls to {@link #getResources()} or {@link #getAssets()} are made.
+ */
+ public void applyOverrideConfiguration(Configuration overrideConfiguration) {
+ if (mResources != null) {
+ throw new IllegalStateException(
+ "getResources() or getAssets() has already been called");
+ }
+ if (mOverrideConfiguration != null) {
+ throw new IllegalStateException("Override configuration has already been set");
+ }
+ mOverrideConfiguration = new Configuration(overrideConfiguration);
+ }
+
+ /**
+ * Used by ActivityThread to apply the overridden configuration to onConfigurationChange
+ * callbacks.
+ * @hide
+ */
+ public Configuration getOverrideConfiguration() {
+ return mOverrideConfiguration;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ // Ensure we're returning assets with the correct configuration.
+ return getResourcesInternal().getAssets();
+ }
+
+ @Override
+ public Resources getResources() {
+ return getResourcesInternal();
+ }
+
+ private Resources getResourcesInternal() {
+ if (mResources == null) {
+ if (mOverrideConfiguration == null) {
+ mResources = super.getResources();
+ } else {
+ final Context resContext = createConfigurationContext(mOverrideConfiguration);
+ mResources = resContext.getResources();
+ }
+ }
+ return mResources;
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ if (mThemeResource != resid) {
+ mThemeResource = resid;
+ initializeTheme();
+ }
+ }
+
+ /** @hide */
+ @Override
+ public int getThemeResId() {
+ return mThemeResource;
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ if (mTheme != null) {
+ return mTheme;
+ }
+
+ mThemeResource = Resources.selectDefaultTheme(mThemeResource,
+ getApplicationInfo().targetSdkVersion);
+ initializeTheme();
+
+ return mTheme;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+ }
+ return mInflater;
+ }
+ return getBaseContext().getSystemService(name);
+ }
+
+ /**
+ * Called by {@link #setTheme} and {@link #getTheme} to apply a theme
+ * resource to the current Theme object. May be overridden to change the
+ * default (simple) behavior. This method will not be called in multiple
+ * threads simultaneously.
+ *
+ * @param theme the theme being modified
+ * @param resId the style resource being applied to <var>theme</var>
+ * @param first {@code true} if this is the first time a style is being
+ * applied to <var>theme</var>
+ */
+ protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
+ theme.applyStyle(resId, true);
+ }
+
+ private void initializeTheme() {
+ final boolean first = mTheme == null;
+ if (first) {
+ mTheme = getResources().newTheme();
+ final Resources.Theme theme = getBaseContext().getTheme();
+ if (theme != null) {
+ mTheme.setTo(theme);
+ }
+ }
+ onApplyThemeResource(mTheme, mThemeResource, first);
+ }
+}
+
diff --git a/android/view/Display.java b/android/view/Display.java
new file mode 100644
index 00000000..e7c3f92d
--- /dev/null
+++ b/android/view/Display.java
@@ -0,0 +1,1441 @@
+/*
+ * Copyright (C) 2006 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 static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
+
+import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Provides information about the size and density of a logical display.
+ * <p>
+ * The display area is described in two different ways.
+ * <ul>
+ * <li>The application display area specifies the part of the display that may contain
+ * an application window, excluding the system decorations. The application display area may
+ * be smaller than the real display area because the system subtracts the space needed
+ * for decor elements such as the status bar. Use the following methods to query the
+ * application display area: {@link #getSize}, {@link #getRectSize} and {@link #getMetrics}.</li>
+ * <li>The real display area specifies the part of the display that contains content
+ * including the system decorations. Even so, the real display area may be smaller than the
+ * physical size of the display if the window manager is emulating a smaller display
+ * using (adb shell wm size). Use the following methods to query the
+ * real display area: {@link #getRealSize}, {@link #getRealMetrics}.</li>
+ * </ul>
+ * </p><p>
+ * A logical display does not necessarily represent a particular physical display device
+ * such as the built-in screen or an external monitor. The contents of a logical
+ * display may be presented on one or more physical displays according to the devices
+ * that are currently attached and whether mirroring has been enabled.
+ * </p>
+ */
+public final class Display {
+ private static final String TAG = "Display";
+ private static final boolean DEBUG = false;
+
+ private final DisplayManagerGlobal mGlobal;
+ private final int mDisplayId;
+ private final int mLayerStack;
+ private final int mFlags;
+ private final int mType;
+ private final String mAddress;
+ private final int mOwnerUid;
+ private final String mOwnerPackageName;
+ private final Resources mResources;
+ private DisplayAdjustments mDisplayAdjustments;
+
+ private DisplayInfo mDisplayInfo; // never null
+ private boolean mIsValid;
+
+ // Temporary display metrics structure used for compatibility mode.
+ private final DisplayMetrics mTempMetrics = new DisplayMetrics();
+
+ // We cache the app width and height properties briefly between calls
+ // to getHeight() and getWidth() to ensure that applications perceive
+ // consistent results when the size changes (most of the time).
+ // Applications should now be using getSize() instead.
+ private static final int CACHED_APP_SIZE_DURATION_MILLIS = 20;
+ private long mLastCachedAppSizeUpdate;
+ private int mCachedAppWidthCompat;
+ private int mCachedAppHeightCompat;
+
+ /**
+ * The default Display id, which is the id of the built-in primary display
+ * assuming there is one.
+ */
+ public static final int DEFAULT_DISPLAY = 0;
+
+ /**
+ * Invalid display id.
+ */
+ public static final int INVALID_DISPLAY = -1;
+
+ /**
+ * Display flag: Indicates that the display supports compositing content
+ * that is stored in protected graphics buffers.
+ * <p>
+ * If this flag is set then the display device supports compositing protected buffers.
+ * </p><p>
+ * If this flag is not set then the display device may not support compositing
+ * protected buffers; the user may see a blank region on the screen instead of
+ * the protected content.
+ * </p><p>
+ * Secure (DRM) video decoders may allocate protected graphics buffers to request that
+ * a hardware-protected path be provided between the video decoder and the external
+ * display sink. If a hardware-protected path is not available, then content stored
+ * in protected graphics buffers may not be composited.
+ * </p><p>
+ * An application can use the absence of this flag as a hint that it should not use protected
+ * buffers for this display because the content may not be visible. For example,
+ * if the flag is not set then the application may choose not to show content on this
+ * display, show an informative error message, select an alternate content stream
+ * or adopt a different strategy for decoding content that does not rely on
+ * protected buffers.
+ * </p>
+ *
+ * @see #getFlags
+ */
+ public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1 << 0;
+
+ /**
+ * Display flag: Indicates that the display has a secure video output and
+ * supports compositing secure surfaces.
+ * <p>
+ * If this flag is set then the display device has a secure video output
+ * and is capable of showing secure surfaces. It may also be capable of
+ * showing {@link #FLAG_SUPPORTS_PROTECTED_BUFFERS protected buffers}.
+ * </p><p>
+ * If this flag is not set then the display device may not have a secure video
+ * output; the user may see a blank region on the screen instead of
+ * the contents of secure surfaces or protected buffers.
+ * </p><p>
+ * Secure surfaces are used to prevent content rendered into those surfaces
+ * by applications from appearing in screenshots or from being viewed
+ * on non-secure displays. Protected buffers are used by secure video decoders
+ * for a similar purpose.
+ * </p><p>
+ * An application creates a window with a secure surface by specifying the
+ * {@link WindowManager.LayoutParams#FLAG_SECURE} window flag.
+ * Likewise, an application creates a {@link SurfaceView} with a secure surface
+ * by calling {@link SurfaceView#setSecure} before attaching the secure view to
+ * its containing window.
+ * </p><p>
+ * An application can use the absence of this flag as a hint that it should not create
+ * secure surfaces or protected buffers on this display because the content may
+ * not be visible. For example, if the flag is not set then the application may
+ * choose not to show content on this display, show an informative error message,
+ * select an alternate content stream or adopt a different strategy for decoding
+ * content that does not rely on secure surfaces or protected buffers.
+ * </p>
+ *
+ * @see #getFlags
+ */
+ public static final int FLAG_SECURE = 1 << 1;
+
+ /**
+ * Display flag: Indicates that the display is private. Only the application that
+ * owns the display and apps that are already on the display can create windows on it.
+ *
+ * @see #getFlags
+ */
+ public static final int FLAG_PRIVATE = 1 << 2;
+
+ /**
+ * Display flag: Indicates that the display is a presentation display.
+ * <p>
+ * This flag identifies secondary displays that are suitable for
+ * use as presentation displays such as HDMI or Wireless displays. Applications
+ * may automatically project their content to presentation displays to provide
+ * richer second screen experiences.
+ * </p>
+ *
+ * @see #getFlags
+ */
+ public static final int FLAG_PRESENTATION = 1 << 3;
+
+ /**
+ * Display flag: Indicates that the display has a round shape.
+ * <p>
+ * This flag identifies displays that are circular, elliptical or otherwise
+ * do not permit the user to see all the way to the logical corners of the display.
+ * </p>
+ *
+ * @see #getFlags
+ */
+ public static final int FLAG_ROUND = 1 << 4;
+
+ /**
+ * Display flag: Indicates that the display can show its content when non-secure keyguard is
+ * shown.
+ * <p>
+ * This flag identifies secondary displays that will continue showing content if keyguard can be
+ * dismissed without entering credentials.
+ * </p><p>
+ * An example of usage is a virtual display which content is displayed on external hardware
+ * display that is not visible to the system directly.
+ * </p>
+ *
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
+ * @see WindowManagerPolicy#isKeyguardSecure(int)
+ * @see WindowManagerPolicy#isKeyguardTrustedLw()
+ * @see #getFlags
+ * @hide
+ */
+ public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;
+
+ /**
+ * Display flag: Indicates that the contents of the display should not be scaled
+ * to fit the physical screen dimensions. Used for development only to emulate
+ * devices with smaller physicals screens while preserving density.
+ *
+ * @hide
+ */
+ public static final int FLAG_SCALING_DISABLED = 1 << 30;
+
+ /**
+ * Display type: Unknown display type.
+ * @hide
+ */
+ public static final int TYPE_UNKNOWN = 0;
+
+ /**
+ * Display type: Built-in display.
+ * @hide
+ */
+ public static final int TYPE_BUILT_IN = 1;
+
+ /**
+ * Display type: HDMI display.
+ * @hide
+ */
+ public static final int TYPE_HDMI = 2;
+
+ /**
+ * Display type: WiFi display.
+ * @hide
+ */
+ public static final int TYPE_WIFI = 3;
+
+ /**
+ * Display type: Overlay display.
+ * @hide
+ */
+ public static final int TYPE_OVERLAY = 4;
+
+ /**
+ * Display type: Virtual display.
+ * @hide
+ */
+ public static final int TYPE_VIRTUAL = 5;
+
+ /**
+ * Display state: The display state is unknown.
+ *
+ * @see #getState
+ */
+ public static final int STATE_UNKNOWN = 0;
+
+ /**
+ * Display state: The display is off.
+ *
+ * @see #getState
+ */
+ public static final int STATE_OFF = 1;
+
+ /**
+ * Display state: The display is on.
+ *
+ * @see #getState
+ */
+ public static final int STATE_ON = 2;
+
+ /**
+ * Display state: The display is dozing in a low power state; it is still
+ * on but is optimized for showing system-provided content while the
+ * device is non-interactive.
+ *
+ * @see #getState
+ * @see android.os.PowerManager#isInteractive
+ */
+ public static final int STATE_DOZE = 3;
+
+ /**
+ * Display state: The display is dozing in a suspended low power state; it is still
+ * on but is optimized for showing static system-provided content while the device
+ * is non-interactive. This mode may be used to conserve even more power by allowing
+ * the hardware to stop applying frame buffer updates from the graphics subsystem or
+ * to take over the display and manage it autonomously to implement low power always-on
+ * display functionality.
+ *
+ * @see #getState
+ * @see android.os.PowerManager#isInteractive
+ */
+ public static final int STATE_DOZE_SUSPEND = 4;
+
+ /**
+ * Display state: The display is on and optimized for VR mode.
+ *
+ * @see #getState
+ * @see android.os.PowerManager#isInteractive
+ */
+ public static final int STATE_VR = 5;
+
+ /* The color mode constants defined below must be kept in sync with the ones in
+ * system/core/include/system/graphics-base.h */
+
+ /**
+ * Display color mode: The current color mode is unknown or invalid.
+ * @hide
+ */
+ public static final int COLOR_MODE_INVALID = -1;
+
+ /**
+ * Display color mode: The default or native gamut of the display.
+ * @hide
+ */
+ public static final int COLOR_MODE_DEFAULT = 0;
+
+ /** @hide */
+ public static final int COLOR_MODE_BT601_625 = 1;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_625_UNADJUSTED = 2;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_525 = 3;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_525_UNADJUSTED = 4;
+ /** @hide */
+ public static final int COLOR_MODE_BT709 = 5;
+ /** @hide */
+ public static final int COLOR_MODE_DCI_P3 = 6;
+ /** @hide */
+ public static final int COLOR_MODE_SRGB = 7;
+ /** @hide */
+ public static final int COLOR_MODE_ADOBE_RGB = 8;
+ /** @hide */
+ public static final int COLOR_MODE_DISPLAY_P3 = 9;
+
+ /**
+ * Indicates that when display is removed, all its activities will be moved to the primary
+ * display and the topmost activity should become focused.
+ *
+ * @hide
+ */
+ public static final int REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY = 0;
+ /**
+ * Indicates that when display is removed, all its stacks and tasks will be removed, all
+ * activities will be destroyed according to the usual lifecycle.
+ *
+ * @hide
+ */
+ public static final int REMOVE_MODE_DESTROY_CONTENT = 1;
+
+ /**
+ * Internal method to create a display.
+ * The display created with this method will have a static {@link DisplayAdjustments} applied.
+ * Applications should use {@link android.view.WindowManager#getDefaultDisplay()}
+ * or {@link android.hardware.display.DisplayManager#getDisplay}
+ * to get a display object.
+ *
+ * @hide
+ */
+ public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo,
+ DisplayAdjustments daj) {
+ this(global, displayId, displayInfo, daj, null /*res*/);
+ }
+
+ /**
+ * Internal method to create a display.
+ * The display created with this method will be adjusted based on the adjustments in the
+ * supplied {@link Resources}.
+ *
+ * @hide
+ */
+ public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo,
+ Resources res) {
+ this(global, displayId, displayInfo, null /*daj*/, res);
+ }
+
+ private Display(DisplayManagerGlobal global, int displayId,
+ /*@NotNull*/ DisplayInfo displayInfo, DisplayAdjustments daj, Resources res) {
+ mGlobal = global;
+ mDisplayId = displayId;
+ mDisplayInfo = displayInfo;
+ mResources = res;
+ mDisplayAdjustments = mResources != null
+ ? new DisplayAdjustments(mResources.getConfiguration())
+ : daj != null ? new DisplayAdjustments(daj) : null;
+ mIsValid = true;
+
+ // Cache properties that cannot change as long as the display is valid.
+ mLayerStack = displayInfo.layerStack;
+ mFlags = displayInfo.flags;
+ mType = displayInfo.type;
+ mAddress = displayInfo.address;
+ mOwnerUid = displayInfo.ownerUid;
+ mOwnerPackageName = displayInfo.ownerPackageName;
+ }
+
+ /**
+ * Gets the display id.
+ * <p>
+ * Each logical display has a unique id.
+ * The default display has id {@link #DEFAULT_DISPLAY}.
+ * </p>
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * Returns true if this display is still valid, false if the display has been removed.
+ *
+ * If the display is invalid, then the methods of this class will
+ * continue to report the most recently observed display information.
+ * However, it is unwise (and rather fruitless) to continue using a
+ * {@link Display} object after the display's demise.
+ *
+ * It's possible for a display that was previously invalid to become
+ * valid again if a display with the same id is reconnected.
+ *
+ * @return True if the display is still valid.
+ */
+ public boolean isValid() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mIsValid;
+ }
+ }
+
+ /**
+ * Gets a full copy of the display information.
+ *
+ * @param outDisplayInfo The object to receive the copy of the display information.
+ * @return True if the display is still valid.
+ * @hide
+ */
+ public boolean getDisplayInfo(DisplayInfo outDisplayInfo) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outDisplayInfo.copyFrom(mDisplayInfo);
+ return mIsValid;
+ }
+ }
+
+ /**
+ * Gets the display's layer stack.
+ *
+ * Each display has its own independent layer stack upon which surfaces
+ * are placed to be managed by surface flinger.
+ *
+ * @return The display's layer stack number.
+ * @hide
+ */
+ public int getLayerStack() {
+ return mLayerStack;
+ }
+
+ /**
+ * Returns a combination of flags that describe the capabilities of the display.
+ *
+ * @return The display flags.
+ *
+ * @see #FLAG_SUPPORTS_PROTECTED_BUFFERS
+ * @see #FLAG_SECURE
+ * @see #FLAG_PRIVATE
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Gets the display type.
+ *
+ * @return The display type.
+ *
+ * @see #TYPE_UNKNOWN
+ * @see #TYPE_BUILT_IN
+ * @see #TYPE_HDMI
+ * @see #TYPE_WIFI
+ * @see #TYPE_OVERLAY
+ * @see #TYPE_VIRTUAL
+ * @hide
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets the display address, or null if none.
+ * Interpretation varies by display type.
+ *
+ * @return The display address.
+ * @hide
+ */
+ public String getAddress() {
+ return mAddress;
+ }
+
+ /**
+ * Gets the UID of the application that owns this display, or zero if it is
+ * owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ *
+ * @hide
+ */
+ public int getOwnerUid() {
+ return mOwnerUid;
+ }
+
+ /**
+ * Gets the package name of the application that owns this display, or null if it is
+ * owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ *
+ * @hide
+ */
+ public String getOwnerPackageName() {
+ return mOwnerPackageName;
+ }
+
+ /**
+ * Gets the compatibility info used by this display instance.
+ *
+ * @return The display adjustments holder, or null if none is required.
+ * @hide
+ */
+ public DisplayAdjustments getDisplayAdjustments() {
+ if (mResources != null) {
+ final DisplayAdjustments currentAdjustements = mResources.getDisplayAdjustments();
+ if (!mDisplayAdjustments.equals(currentAdjustements)) {
+ mDisplayAdjustments = new DisplayAdjustments(currentAdjustements);
+ }
+ }
+
+ return mDisplayAdjustments;
+ }
+
+ /**
+ * Gets the name of the display.
+ * <p>
+ * Note that some displays may be renamed by the user.
+ * </p>
+ *
+ * @return The display's name.
+ */
+ public String getName() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.name;
+ }
+ }
+
+ /**
+ * Gets the size of the display, in pixels.
+ * Value returned by this method does not necessarily represent the actual raw size
+ * (native resolution) of the display.
+ * <p>
+ * 1. The returned size may be adjusted to exclude certain system decor elements
+ * that are always visible.
+ * </p><p>
+ * 2. It may be scaled to provide compatibility with older applications that
+ * were originally designed for smaller displays.
+ * </p><p>
+ * 3. It can be different depending on the WindowManager to which the display belongs.
+ * </p><p>
+ * - If requested from non-Activity context (e.g. Application context via
+ * {@code (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE)})
+ * it will report the size of the entire display based on current rotation and with subtracted
+ * system decoration areas.
+ * </p><p>
+ * - If requested from activity (either using {@code getWindowManager()} or
+ * {@code (WindowManager) getSystemService(Context.WINDOW_SERVICE)}) resulting size will
+ * correspond to current app window size. In this case it can be smaller than physical size in
+ * multi-window mode.
+ * </p><p>
+ * Typically for the purposes of layout apps should make a request from activity context
+ * to obtain size available for the app content.
+ * </p>
+ *
+ * @param outSize A {@link Point} object to receive the size information.
+ */
+ public void getSize(Point outSize) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+ outSize.x = mTempMetrics.widthPixels;
+ outSize.y = mTempMetrics.heightPixels;
+ }
+ }
+
+ /**
+ * Gets the size of the display as a rectangle, in pixels.
+ *
+ * @param outSize A {@link Rect} object to receive the size information.
+ * @see #getSize(Point)
+ */
+ public void getRectSize(Rect outSize) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+ outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels);
+ }
+ }
+
+ /**
+ * Return the range of display sizes an application can expect to encounter
+ * under normal operation, as long as there is no physical change in screen
+ * size. This is basically the sizes you will see as the orientation
+ * changes, taking into account whatever screen decoration there is in
+ * each rotation. For example, the status bar is always at the top of the
+ * screen, so it will reduce the height both in landscape and portrait, and
+ * the smallest height returned here will be the smaller of the two.
+ *
+ * This is intended for applications to get an idea of the range of sizes
+ * they will encounter while going through device rotations, to provide a
+ * stable UI through rotation. The sizes here take into account all standard
+ * system decorations that reduce the size actually available to the
+ * application: the status bar, navigation bar, system bar, etc. It does
+ * <em>not</em> take into account more transient elements like an IME
+ * soft keyboard.
+ *
+ * @param outSmallestSize Filled in with the smallest width and height
+ * that the application will encounter, in pixels (not dp units). The x
+ * (width) dimension here directly corresponds to
+ * {@link android.content.res.Configuration#smallestScreenWidthDp
+ * Configuration.smallestScreenWidthDp}, except the value here is in raw
+ * screen pixels rather than dp units. Your application may of course
+ * still get smaller space yet if, for example, a soft keyboard is
+ * being displayed.
+ * @param outLargestSize Filled in with the largest width and height
+ * that the application will encounter, in pixels (not dp units). Your
+ * application may of course still get larger space than this if,
+ * for example, screen decorations like the status bar are being hidden.
+ */
+ public void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outSmallestSize.x = mDisplayInfo.smallestNominalAppWidth;
+ outSmallestSize.y = mDisplayInfo.smallestNominalAppHeight;
+ outLargestSize.x = mDisplayInfo.largestNominalAppWidth;
+ outLargestSize.y = mDisplayInfo.largestNominalAppHeight;
+ }
+ }
+
+ /**
+ * Return the maximum screen size dimension that will happen. This is
+ * mostly for wallpapers.
+ * @hide
+ */
+ public int getMaximumSizeDimension() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return Math.max(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #getSize(Point)} instead.
+ */
+ @Deprecated
+ public int getWidth() {
+ synchronized (this) {
+ updateCachedAppSizeIfNeededLocked();
+ return mCachedAppWidthCompat;
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #getSize(Point)} instead.
+ */
+ @Deprecated
+ public int getHeight() {
+ synchronized (this) {
+ updateCachedAppSizeIfNeededLocked();
+ return mCachedAppHeightCompat;
+ }
+ }
+
+ /**
+ * @hide
+ * Return a rectangle defining the insets of the overscan region of the display.
+ * Each field of the rectangle is the number of pixels the overscan area extends
+ * into the display on that side.
+ */
+ public void getOverscanInsets(Rect outRect) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outRect.set(mDisplayInfo.overscanLeft, mDisplayInfo.overscanTop,
+ mDisplayInfo.overscanRight, mDisplayInfo.overscanBottom);
+ }
+ }
+
+ /**
+ * Returns the rotation of the screen from its "natural" orientation.
+ * The returned value may be {@link Surface#ROTATION_0 Surface.ROTATION_0}
+ * (no rotation), {@link Surface#ROTATION_90 Surface.ROTATION_90},
+ * {@link Surface#ROTATION_180 Surface.ROTATION_180}, or
+ * {@link Surface#ROTATION_270 Surface.ROTATION_270}. For
+ * example, if a device has a naturally tall screen, and the user has
+ * turned it on its side to go into a landscape orientation, the value
+ * returned here may be either {@link Surface#ROTATION_90 Surface.ROTATION_90}
+ * or {@link Surface#ROTATION_270 Surface.ROTATION_270} depending on
+ * the direction it was turned. The angle is the rotation of the drawn
+ * graphics on the screen, which is the opposite direction of the physical
+ * rotation of the device. For example, if the device is rotated 90
+ * degrees counter-clockwise, to compensate rendering will be rotated by
+ * 90 degrees clockwise and thus the returned value here will be
+ * {@link Surface#ROTATION_90 Surface.ROTATION_90}.
+ */
+ @Surface.Rotation
+ public int getRotation() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.rotation;
+ }
+ }
+
+ /**
+ * @deprecated use {@link #getRotation}
+ * @return orientation of this display.
+ */
+ @Deprecated
+ @Surface.Rotation
+ public int getOrientation() {
+ return getRotation();
+ }
+
+ /**
+ * Gets the pixel format of the display.
+ * @return One of the constants defined in {@link android.graphics.PixelFormat}.
+ *
+ * @deprecated This method is no longer supported.
+ * The result is always {@link PixelFormat#RGBA_8888}.
+ */
+ @Deprecated
+ public int getPixelFormat() {
+ return PixelFormat.RGBA_8888;
+ }
+
+ /**
+ * Gets the refresh rate of this display in frames per second.
+ */
+ public float getRefreshRate() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.getMode().getRefreshRate();
+ }
+ }
+
+ /**
+ * Get the supported refresh rates of this display in frames per second.
+ * <p>
+ * This method only returns refresh rates for the display's default modes. For more options, use
+ * {@link #getSupportedModes()}.
+ *
+ * @deprecated use {@link #getSupportedModes()} instead
+ */
+ @Deprecated
+ public float[] getSupportedRefreshRates() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.getDefaultRefreshRates();
+ }
+ }
+
+ /**
+ * Returns the active mode of the display.
+ */
+ public Mode getMode() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.getMode();
+ }
+ }
+
+ /**
+ * Gets the supported modes of this display.
+ */
+ public Mode[] getSupportedModes() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ final Display.Mode[] modes = mDisplayInfo.supportedModes;
+ return Arrays.copyOf(modes, modes.length);
+ }
+ }
+
+ /**
+ * Request the display applies a color mode.
+ * @hide
+ */
+ @RequiresPermission(CONFIGURE_DISPLAY_COLOR_MODE)
+ public void requestColorMode(int colorMode) {
+ mGlobal.requestColorMode(mDisplayId, colorMode);
+ }
+
+ /**
+ * Returns the active color mode of this display
+ * @hide
+ */
+ public int getColorMode() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.colorMode;
+ }
+ }
+
+ /**
+ * @hide
+ * Get current remove mode of the display - what actions should be performed with the display's
+ * content when it is removed. Default behavior for public displays in this case is to move all
+ * activities to the primary display and make it focused. For private display - destroy all
+ * activities.
+ *
+ * @see #REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY
+ * @see #REMOVE_MODE_DESTROY_CONTENT
+ */
+ public int getRemoveMode() {
+ return mDisplayInfo.removeMode;
+ }
+
+ /**
+ * Returns the display's HDR capabilities.
+ *
+ * @see #isHdr()
+ */
+ public HdrCapabilities getHdrCapabilities() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.hdrCapabilities;
+ }
+ }
+
+ /**
+ * Returns whether this display supports any HDR type.
+ *
+ * @see #getHdrCapabilities()
+ * @see HdrCapabilities#getSupportedHdrTypes()
+ */
+ public boolean isHdr() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.isHdr();
+ }
+ }
+
+ /**
+ * Returns whether this display can be used to display wide color gamut content.
+ * This does not necessarily mean the device itself can render wide color gamut
+ * content. To ensure wide color gamut content can be produced, refer to
+ * {@link Configuration#isScreenWideColorGamut()}.
+ */
+ public boolean isWideColorGamut() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.isWideColorGamut();
+ }
+ }
+
+ /**
+ * Gets the supported color modes of this device.
+ * @hide
+ */
+ public int[] getSupportedColorModes() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ int[] colorModes = mDisplayInfo.supportedColorModes;
+ return Arrays.copyOf(colorModes, colorModes.length);
+ }
+ }
+
+ /**
+ * Gets the app VSYNC offset, in nanoseconds. This is a positive value indicating
+ * the phase offset of the VSYNC events provided by Choreographer relative to the
+ * display refresh. For example, if Choreographer reports that the refresh occurred
+ * at time N, it actually occurred at (N - appVsyncOffset).
+ * <p>
+ * Apps generally do not need to be aware of this. It's only useful for fine-grained
+ * A/V synchronization.
+ */
+ public long getAppVsyncOffsetNanos() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.appVsyncOffsetNanos;
+ }
+ }
+
+ /**
+ * This is how far in advance a buffer must be queued for presentation at
+ * a given time. If you want a buffer to appear on the screen at
+ * time N, you must submit the buffer before (N - presentationDeadline).
+ * <p>
+ * The desired presentation time for GLES rendering may be set with
+ * {@link android.opengl.EGLExt#eglPresentationTimeANDROID}. For video decoding, use
+ * {@link android.media.MediaCodec#releaseOutputBuffer(int, long)}. Times are
+ * expressed in nanoseconds, using the system monotonic clock
+ * ({@link System#nanoTime}).
+ */
+ public long getPresentationDeadlineNanos() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.presentationDeadlineNanos;
+ }
+ }
+
+ /**
+ * Gets display metrics that describe the size and density of this display.
+ * The size returned by this method does not necessarily represent the
+ * actual raw size (native resolution) of the display.
+ * <p>
+ * 1. The returned size may be adjusted to exclude certain system decor elements
+ * that are always visible.
+ * </p><p>
+ * 2. It may be scaled to provide compatibility with older applications that
+ * were originally designed for smaller displays.
+ * </p><p>
+ * 3. It can be different depending on the WindowManager to which the display belongs.
+ * </p><p>
+ * - If requested from non-Activity context (e.g. Application context via
+ * {@code (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE)})
+ * metrics will report the size of the entire display based on current rotation and with
+ * subtracted system decoration areas.
+ * </p><p>
+ * - If requested from activity (either using {@code getWindowManager()} or
+ * {@code (WindowManager) getSystemService(Context.WINDOW_SERVICE)}) resulting metrics will
+ * correspond to current app window metrics. In this case the size can be smaller than physical
+ * size in multi-window mode.
+ * </p>
+ *
+ * @param outMetrics A {@link DisplayMetrics} object to receive the metrics.
+ */
+ public void getMetrics(DisplayMetrics outMetrics) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(outMetrics, getDisplayAdjustments());
+ }
+ }
+
+ /**
+ * Gets the real size of the display without subtracting any window decor or
+ * applying any compatibility scale factors.
+ * <p>
+ * The size is adjusted based on the current rotation of the display.
+ * </p><p>
+ * The real size may be smaller than the physical size of the screen when the
+ * window manager is emulating a smaller display (using adb shell wm size).
+ * </p>
+ *
+ * @param outSize Set to the real size of the display.
+ */
+ public void getRealSize(Point outSize) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outSize.x = mDisplayInfo.logicalWidth;
+ outSize.y = mDisplayInfo.logicalHeight;
+ }
+ }
+
+ /**
+ * Gets display metrics based on the real size of this display.
+ * <p>
+ * The size is adjusted based on the current rotation of the display.
+ * </p><p>
+ * The real size may be smaller than the physical size of the screen when the
+ * window manager is emulating a smaller display (using adb shell wm size).
+ * </p>
+ *
+ * @param outMetrics A {@link DisplayMetrics} object to receive the metrics.
+ */
+ public void getRealMetrics(DisplayMetrics outMetrics) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getLogicalMetrics(outMetrics,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ }
+ }
+
+ /**
+ * Gets the state of the display, such as whether it is on or off.
+ *
+ * @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
+ * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, or
+ * {@link #STATE_UNKNOWN}.
+ */
+ public int getState() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mIsValid ? mDisplayInfo.state : STATE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns true if the specified UID has access to this display.
+ * @hide
+ */
+ public boolean hasAccess(int uid) {
+ return Display.hasAccess(uid, mFlags, mOwnerUid);
+ }
+
+ /** @hide */
+ public static boolean hasAccess(int uid, int flags, int ownerUid) {
+ return (flags & Display.FLAG_PRIVATE) == 0
+ || uid == ownerUid
+ || uid == Process.SYSTEM_UID
+ || uid == 0;
+ }
+
+ /**
+ * Returns true if the display is a public presentation display.
+ * @hide
+ */
+ public boolean isPublicPresentation() {
+ return (mFlags & (Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION)) ==
+ Display.FLAG_PRESENTATION;
+ }
+
+ private void updateDisplayInfoLocked() {
+ // Note: The display manager caches display info objects on our behalf.
+ DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
+ if (newInfo == null) {
+ // Preserve the old mDisplayInfo after the display is removed.
+ if (mIsValid) {
+ mIsValid = false;
+ if (DEBUG) {
+ Log.d(TAG, "Logical display " + mDisplayId + " was removed.");
+ }
+ }
+ } else {
+ // Use the new display info. (It might be the same object if nothing changed.)
+ mDisplayInfo = newInfo;
+ if (!mIsValid) {
+ mIsValid = true;
+ if (DEBUG) {
+ Log.d(TAG, "Logical display " + mDisplayId + " was recreated.");
+ }
+ }
+ }
+ }
+
+ private void updateCachedAppSizeIfNeededLocked() {
+ long now = SystemClock.uptimeMillis();
+ if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+ mCachedAppWidthCompat = mTempMetrics.widthPixels;
+ mCachedAppHeightCompat = mTempMetrics.heightPixels;
+ mLastCachedAppSizeUpdate = now;
+ }
+ }
+
+ // For debugging purposes
+ @Override
+ public String toString() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+ return "Display id " + mDisplayId + ": " + mDisplayInfo
+ + ", " + mTempMetrics + ", isValid=" + mIsValid;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static String typeToString(int type) {
+ switch (type) {
+ case TYPE_UNKNOWN:
+ return "UNKNOWN";
+ case TYPE_BUILT_IN:
+ return "BUILT_IN";
+ case TYPE_HDMI:
+ return "HDMI";
+ case TYPE_WIFI:
+ return "WIFI";
+ case TYPE_OVERLAY:
+ return "OVERLAY";
+ case TYPE_VIRTUAL:
+ return "VIRTUAL";
+ default:
+ return Integer.toString(type);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static String stateToString(int state) {
+ switch (state) {
+ case STATE_UNKNOWN:
+ return "UNKNOWN";
+ case STATE_OFF:
+ return "OFF";
+ case STATE_ON:
+ return "ON";
+ case STATE_DOZE:
+ return "DOZE";
+ case STATE_DOZE_SUSPEND:
+ return "DOZE_SUSPEND";
+ case STATE_VR:
+ return "VR";
+ default:
+ return Integer.toString(state);
+ }
+ }
+
+ /**
+ * Returns true if display updates may be suspended while in the specified
+ * display power state.
+ * @hide
+ */
+ public static boolean isSuspendedState(int state) {
+ return state == STATE_OFF || state == STATE_DOZE_SUSPEND;
+ }
+
+ /**
+ * Returns true if the display may be in a reduced operating mode while in the
+ * specified display power state.
+ * @hide
+ */
+ public static boolean isDozeState(int state) {
+ return state == STATE_DOZE || state == STATE_DOZE_SUSPEND;
+ }
+
+ /**
+ * A mode supported by a given display.
+ *
+ * @see Display#getSupportedModes()
+ */
+ public static final class Mode implements Parcelable {
+ /**
+ * @hide
+ */
+ public static final Mode[] EMPTY_ARRAY = new Mode[0];
+
+ private final int mModeId;
+ private final int mWidth;
+ private final int mHeight;
+ private final float mRefreshRate;
+
+ /**
+ * @hide
+ */
+ public Mode(int modeId, int width, int height, float refreshRate) {
+ mModeId = modeId;
+ mWidth = width;
+ mHeight = height;
+ mRefreshRate = refreshRate;
+ }
+
+ /**
+ * Returns this mode's id.
+ */
+ public int getModeId() {
+ return mModeId;
+ }
+
+ /**
+ * Returns the physical width of the display in pixels when configured in this mode's
+ * resolution.
+ * <p>
+ * Note that due to application UI scaling, the number of pixels made available to
+ * applications when the mode is active (as reported by {@link Display#getWidth()} may
+ * differ from the mode's actual resolution (as reported by this function).
+ * <p>
+ * For example, applications running on a 4K display may have their UI laid out and rendered
+ * in 1080p and then scaled up. Applications can take advantage of the extra resolution by
+ * rendering content through a {@link android.view.SurfaceView} using full size buffers.
+ */
+ public int getPhysicalWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Returns the physical height of the display in pixels when configured in this mode's
+ * resolution.
+ * <p>
+ * Note that due to application UI scaling, the number of pixels made available to
+ * applications when the mode is active (as reported by {@link Display#getHeight()} may
+ * differ from the mode's actual resolution (as reported by this function).
+ * <p>
+ * For example, applications running on a 4K display may have their UI laid out and rendered
+ * in 1080p and then scaled up. Applications can take advantage of the extra resolution by
+ * rendering content through a {@link android.view.SurfaceView} using full size buffers.
+ */
+ public int getPhysicalHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Returns the refresh rate in frames per second.
+ */
+ public float getRefreshRate() {
+ return mRefreshRate;
+ }
+
+ /**
+ * Returns {@code true} if this mode matches the given parameters.
+ *
+ * @hide
+ */
+ public boolean matches(int width, int height, float refreshRate) {
+ return mWidth == width &&
+ mHeight == height &&
+ Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Mode)) {
+ return false;
+ }
+ Mode that = (Mode) other;
+ return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 1;
+ hash = hash * 17 + mModeId;
+ hash = hash * 17 + mWidth;
+ hash = hash * 17 + mHeight;
+ hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("{")
+ .append("id=").append(mModeId)
+ .append(", width=").append(mWidth)
+ .append(", height=").append(mHeight)
+ .append(", fps=").append(mRefreshRate)
+ .append("}")
+ .toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private Mode(Parcel in) {
+ this(in.readInt(), in.readInt(), in.readInt(), in.readFloat());
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int parcelableFlags) {
+ out.writeInt(mModeId);
+ out.writeInt(mWidth);
+ out.writeInt(mHeight);
+ out.writeFloat(mRefreshRate);
+ }
+
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<Mode> CREATOR
+ = new Parcelable.Creator<Mode>() {
+ @Override
+ public Mode createFromParcel(Parcel in) {
+ return new Mode(in);
+ }
+
+ @Override
+ public Mode[] newArray(int size) {
+ return new Mode[size];
+ }
+ };
+ }
+
+ /**
+ * Encapsulates the HDR capabilities of a given display.
+ * For example, what HDR types it supports and details about the desired luminance data.
+ * <p>You can get an instance for a given {@link Display} object with
+ * {@link Display#getHdrCapabilities getHdrCapabilities()}.
+ */
+ public static final class HdrCapabilities implements Parcelable {
+ /**
+ * Invalid luminance value.
+ */
+ public static final float INVALID_LUMINANCE = -1;
+ /**
+ * Dolby Vision high dynamic range (HDR) display.
+ */
+ public static final int HDR_TYPE_DOLBY_VISION = 1;
+ /**
+ * HDR10 display.
+ */
+ public static final int HDR_TYPE_HDR10 = 2;
+ /**
+ * Hybrid Log-Gamma HDR display.
+ */
+ public static final int HDR_TYPE_HLG = 3;
+
+ /** @hide */
+ @IntDef({
+ HDR_TYPE_DOLBY_VISION,
+ HDR_TYPE_HDR10,
+ HDR_TYPE_HLG,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HdrType {}
+
+ private @HdrType int[] mSupportedHdrTypes = new int[0];
+ private float mMaxLuminance = INVALID_LUMINANCE;
+ private float mMaxAverageLuminance = INVALID_LUMINANCE;
+ private float mMinLuminance = INVALID_LUMINANCE;
+
+ /**
+ * @hide
+ */
+ public HdrCapabilities() {
+ }
+
+ /**
+ * @hide
+ */
+ public HdrCapabilities(int[] supportedHdrTypes, float maxLuminance,
+ float maxAverageLuminance, float minLuminance) {
+ mSupportedHdrTypes = supportedHdrTypes;
+ mMaxLuminance = maxLuminance;
+ mMaxAverageLuminance = maxAverageLuminance;
+ mMinLuminance = minLuminance;
+ }
+
+ /**
+ * Gets the supported HDR types of this display.
+ * Returns empty array if HDR is not supported by the display.
+ */
+ public @HdrType int[] getSupportedHdrTypes() {
+ return mSupportedHdrTypes;
+ }
+ /**
+ * Returns the desired content max luminance data in cd/m2 for this display.
+ */
+ public float getDesiredMaxLuminance() {
+ return mMaxLuminance;
+ }
+ /**
+ * Returns the desired content max frame-average luminance data in cd/m2 for this display.
+ */
+ public float getDesiredMaxAverageLuminance() {
+ return mMaxAverageLuminance;
+ }
+ /**
+ * Returns the desired content min luminance data in cd/m2 for this display.
+ */
+ public float getDesiredMinLuminance() {
+ return mMinLuminance;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof HdrCapabilities)) {
+ return false;
+ }
+ HdrCapabilities that = (HdrCapabilities) other;
+
+ return Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes)
+ && mMaxLuminance == that.mMaxLuminance
+ && mMaxAverageLuminance == that.mMaxAverageLuminance
+ && mMinLuminance == that.mMinLuminance;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 23;
+ hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
+ hash = hash * 17 + Float.floatToIntBits(mMaxLuminance);
+ hash = hash * 17 + Float.floatToIntBits(mMaxAverageLuminance);
+ hash = hash * 17 + Float.floatToIntBits(mMinLuminance);
+ return hash;
+ }
+
+ public static final Creator<HdrCapabilities> CREATOR = new Creator<HdrCapabilities>() {
+ @Override
+ public HdrCapabilities createFromParcel(Parcel source) {
+ return new HdrCapabilities(source);
+ }
+
+ @Override
+ public HdrCapabilities[] newArray(int size) {
+ return new HdrCapabilities[size];
+ }
+ };
+
+ private HdrCapabilities(Parcel source) {
+ readFromParcel(source);
+ }
+
+ /**
+ * @hide
+ */
+ public void readFromParcel(Parcel source) {
+ int types = source.readInt();
+ mSupportedHdrTypes = new int[types];
+ for (int i = 0; i < types; ++i) {
+ mSupportedHdrTypes[i] = source.readInt();
+ }
+ mMaxLuminance = source.readFloat();
+ mMaxAverageLuminance = source.readFloat();
+ mMinLuminance = source.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSupportedHdrTypes.length);
+ for (int i = 0; i < mSupportedHdrTypes.length; ++i) {
+ dest.writeInt(mSupportedHdrTypes[i]);
+ }
+ dest.writeFloat(mMaxLuminance);
+ dest.writeFloat(mMaxAverageLuminance);
+ dest.writeFloat(mMinLuminance);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ }
+}
diff --git a/android/view/DisplayAdjustments.java b/android/view/DisplayAdjustments.java
new file mode 100644
index 00000000..790029b9
--- /dev/null
+++ b/android/view/DisplayAdjustments.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 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.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+
+import java.util.Objects;
+
+/** @hide */
+public class DisplayAdjustments {
+ public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = new DisplayAdjustments();
+
+ private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ private Configuration mConfiguration;
+
+ public DisplayAdjustments() {
+ }
+
+ public DisplayAdjustments(Configuration configuration) {
+ mConfiguration = new Configuration(configuration != null
+ ? configuration : Configuration.EMPTY);
+ }
+
+ public DisplayAdjustments(DisplayAdjustments daj) {
+ setCompatibilityInfo(daj.mCompatInfo);
+ mConfiguration = new Configuration(daj.mConfiguration != null
+ ? daj.mConfiguration : Configuration.EMPTY);
+ }
+
+ public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
+ if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+ throw new IllegalArgumentException(
+ "setCompatbilityInfo: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+ }
+ if (compatInfo != null && (compatInfo.isScalingRequired()
+ || !compatInfo.supportsScreen())) {
+ mCompatInfo = compatInfo;
+ } else {
+ mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ }
+ }
+
+ public CompatibilityInfo getCompatibilityInfo() {
+ return mCompatInfo;
+ }
+
+ public void setConfiguration(Configuration configuration) {
+ if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+ throw new IllegalArgumentException(
+ "setConfiguration: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+ }
+ mConfiguration.setTo(configuration != null ? configuration : Configuration.EMPTY);
+ }
+
+ public Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = hash * 31 + Objects.hashCode(mCompatInfo);
+ hash = hash * 31 + Objects.hashCode(mConfiguration);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof DisplayAdjustments)) {
+ return false;
+ }
+ DisplayAdjustments daj = (DisplayAdjustments)o;
+ return Objects.equals(daj.mCompatInfo, mCompatInfo) &&
+ Objects.equals(daj.mConfiguration, mConfiguration);
+ }
+}
diff --git a/android/view/DisplayEventReceiver.java b/android/view/DisplayEventReceiver.java
new file mode 100644
index 00000000..cb98c881
--- /dev/null
+++ b/android/view/DisplayEventReceiver.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2011 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.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+import dalvik.annotation.optimization.FastNative;
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a low-level mechanism for an application to receive display events
+ * such as vertical sync.
+ *
+ * The display event receive is NOT thread safe. Moreover, its methods must only
+ * be called on the Looper thread to which it is attached.
+ *
+ * @hide
+ */
+public abstract class DisplayEventReceiver {
+
+ /**
+ * When retrieving vsync events, this specifies that the vsync event should happen at the normal
+ * vsync-app tick.
+ * <p>
+ * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+ */
+ public static final int VSYNC_SOURCE_APP = 0;
+
+ /**
+ * When retrieving vsync events, this specifies that the vsync event should happen whenever
+ * Surface Flinger is processing a frame.
+ * <p>
+ * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+ */
+ public static final int VSYNC_SOURCE_SURFACE_FLINGER = 1;
+
+ private static final String TAG = "DisplayEventReceiver";
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private long mReceiverPtr;
+
+ // We keep a reference message queue object here so that it is not
+ // GC'd while the native peer of the receiver is using them.
+ private MessageQueue mMessageQueue;
+
+ private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
+ MessageQueue messageQueue, int vsyncSource);
+ private static native void nativeDispose(long receiverPtr);
+ @FastNative
+ private static native void nativeScheduleVsync(long receiverPtr);
+
+ /**
+ * Creates a display event receiver.
+ *
+ * @param looper The looper to use when invoking callbacks.
+ */
+ public DisplayEventReceiver(Looper looper) {
+ this(looper, VSYNC_SOURCE_APP);
+ }
+
+ /**
+ * Creates a display event receiver.
+ *
+ * @param looper The looper to use when invoking callbacks.
+ * @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
+ */
+ public DisplayEventReceiver(Looper looper, int vsyncSource) {
+ if (looper == null) {
+ throw new IllegalArgumentException("looper must not be null");
+ }
+
+ mMessageQueue = looper.getQueue();
+ mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
+ vsyncSource);
+
+ mCloseGuard.open("dispose");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Disposes the receiver.
+ */
+ public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+
+ if (mReceiverPtr != 0) {
+ nativeDispose(mReceiverPtr);
+ mReceiverPtr = 0;
+ }
+ mMessageQueue = null;
+ }
+
+ /**
+ * Called when a vertical sync pulse is received.
+ * The recipient should render a frame and then call {@link #scheduleVsync}
+ * to schedule the next vertical sync pulse.
+ *
+ * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
+ * timebase.
+ * @param builtInDisplayId The surface flinger built-in display id such as
+ * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
+ * @param frame The frame number. Increases by one for each vertical sync interval.
+ */
+ public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
+ }
+
+ /**
+ * Called when a display hotplug event is received.
+ *
+ * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
+ * timebase.
+ * @param builtInDisplayId The surface flinger built-in display id such as
+ * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_HDMI}.
+ * @param connected True if the display is connected, false if it disconnected.
+ */
+ public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
+ }
+
+ /**
+ * Schedules a single vertical sync pulse to be delivered when the next
+ * display frame begins.
+ */
+ public void scheduleVsync() {
+ if (mReceiverPtr == 0) {
+ Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ + "receiver has already been disposed.");
+ } else {
+ nativeScheduleVsync(mReceiverPtr);
+ }
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
+ onVsync(timestampNanos, builtInDisplayId, frame);
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
+ onHotplug(timestampNanos, builtInDisplayId, connected);
+ }
+}
diff --git a/android/view/DisplayInfo.java b/android/view/DisplayInfo.java
new file mode 100644
index 00000000..b813ddb6
--- /dev/null
+++ b/android/view/DisplayInfo.java
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2012 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 static android.view.DisplayInfoProto.APP_HEIGHT;
+import static android.view.DisplayInfoProto.APP_WIDTH;
+import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
+import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
+
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.proto.ProtoOutputStream;
+
+import libcore.util.Objects;
+
+import java.util.Arrays;
+
+/**
+ * Describes the characteristics of a particular logical display.
+ * @hide
+ */
+public final class DisplayInfo implements Parcelable {
+ /**
+ * The surface flinger layer stack associated with this logical display.
+ */
+ public int layerStack;
+
+ /**
+ * Display flags.
+ */
+ public int flags;
+
+ /**
+ * Display type.
+ */
+ public int type;
+
+ /**
+ * Display address, or null if none.
+ * Interpretation varies by display type.
+ */
+ public String address;
+
+ /**
+ * The human-readable name of the display.
+ */
+ public String name;
+
+ /**
+ * Unique identifier for the display. Shouldn't be displayed to the user.
+ */
+ public String uniqueId;
+
+ /**
+ * The width of the portion of the display that is available to applications, in pixels.
+ * Represents the size of the display minus any system decorations.
+ */
+ public int appWidth;
+
+ /**
+ * The height of the portion of the display that is available to applications, in pixels.
+ * Represents the size of the display minus any system decorations.
+ */
+ public int appHeight;
+
+ /**
+ * The smallest value of {@link #appWidth} that an application is likely to encounter,
+ * in pixels, excepting cases where the width may be even smaller due to the presence
+ * of a soft keyboard, for example.
+ */
+ public int smallestNominalAppWidth;
+
+ /**
+ * The smallest value of {@link #appHeight} that an application is likely to encounter,
+ * in pixels, excepting cases where the height may be even smaller due to the presence
+ * of a soft keyboard, for example.
+ */
+ public int smallestNominalAppHeight;
+
+ /**
+ * The largest value of {@link #appWidth} that an application is likely to encounter,
+ * in pixels, excepting cases where the width may be even larger due to system decorations
+ * such as the status bar being hidden, for example.
+ */
+ public int largestNominalAppWidth;
+
+ /**
+ * The largest value of {@link #appHeight} that an application is likely to encounter,
+ * in pixels, excepting cases where the height may be even larger due to system decorations
+ * such as the status bar being hidden, for example.
+ */
+ public int largestNominalAppHeight;
+
+ /**
+ * The logical width of the display, in pixels.
+ * Represents the usable size of the display which may be smaller than the
+ * physical size when the system is emulating a smaller display.
+ */
+ public int logicalWidth;
+
+ /**
+ * The logical height of the display, in pixels.
+ * Represents the usable size of the display which may be smaller than the
+ * physical size when the system is emulating a smaller display.
+ */
+ public int logicalHeight;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the left side of the display.
+ */
+ public int overscanLeft;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the top side of the display.
+ */
+ public int overscanTop;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the right side of the display.
+ */
+ public int overscanRight;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the bottom side of the display.
+ */
+ public int overscanBottom;
+
+ /**
+ * The rotation of the display relative to its natural orientation.
+ * May be one of {@link android.view.Surface#ROTATION_0},
+ * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180},
+ * {@link android.view.Surface#ROTATION_270}.
+ * <p>
+ * The value of this field is indeterminate if the logical display is presented on
+ * more than one physical display.
+ * </p>
+ */
+ @Surface.Rotation
+ public int rotation;
+
+ /**
+ * The active display mode.
+ */
+ public int modeId;
+
+ /**
+ * The default display mode.
+ */
+ public int defaultModeId;
+
+ /**
+ * The supported modes of this display.
+ */
+ public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY;
+
+ /** The active color mode. */
+ public int colorMode;
+
+ /** The list of supported color modes */
+ public int[] supportedColorModes = { Display.COLOR_MODE_DEFAULT };
+
+ /** The display's HDR capabilities */
+ public Display.HdrCapabilities hdrCapabilities;
+
+ /**
+ * The logical display density which is the basis for density-independent
+ * pixels.
+ */
+ public int logicalDensityDpi;
+
+ /**
+ * The exact physical pixels per inch of the screen in the X dimension.
+ * <p>
+ * The value of this field is indeterminate if the logical display is presented on
+ * more than one physical display.
+ * </p>
+ */
+ public float physicalXDpi;
+
+ /**
+ * The exact physical pixels per inch of the screen in the Y dimension.
+ * <p>
+ * The value of this field is indeterminate if the logical display is presented on
+ * more than one physical display.
+ * </p>
+ */
+ public float physicalYDpi;
+
+ /**
+ * This is a positive value indicating the phase offset of the VSYNC events provided by
+ * Choreographer relative to the display refresh. For example, if Choreographer reports
+ * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos).
+ */
+ public long appVsyncOffsetNanos;
+
+ /**
+ * This is how far in advance a buffer must be queued for presentation at
+ * a given time. If you want a buffer to appear on the screen at
+ * time N, you must submit the buffer before (N - bufferDeadlineNanos).
+ */
+ public long presentationDeadlineNanos;
+
+ /**
+ * The state of the display, such as {@link android.view.Display#STATE_ON}.
+ */
+ public int state;
+
+ /**
+ * The UID of the application that owns this display, or zero if it is owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ */
+ public int ownerUid;
+
+ /**
+ * The package name of the application that owns this display, or null if it is
+ * owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ */
+ public String ownerPackageName;
+
+ /**
+ * @hide
+ * Get current remove mode of the display - what actions should be performed with the display's
+ * content when it is removed.
+ *
+ * @see Display#getRemoveMode()
+ */
+ public int removeMode = Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY;
+
+ public static final Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
+ @Override
+ public DisplayInfo createFromParcel(Parcel source) {
+ return new DisplayInfo(source);
+ }
+
+ @Override
+ public DisplayInfo[] newArray(int size) {
+ return new DisplayInfo[size];
+ }
+ };
+
+ public DisplayInfo() {
+ }
+
+ public DisplayInfo(DisplayInfo other) {
+ copyFrom(other);
+ }
+
+ private DisplayInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof DisplayInfo && equals((DisplayInfo)o);
+ }
+
+ public boolean equals(DisplayInfo other) {
+ return other != null
+ && layerStack == other.layerStack
+ && flags == other.flags
+ && type == other.type
+ && Objects.equal(address, other.address)
+ && Objects.equal(uniqueId, other.uniqueId)
+ && appWidth == other.appWidth
+ && appHeight == other.appHeight
+ && smallestNominalAppWidth == other.smallestNominalAppWidth
+ && smallestNominalAppHeight == other.smallestNominalAppHeight
+ && largestNominalAppWidth == other.largestNominalAppWidth
+ && largestNominalAppHeight == other.largestNominalAppHeight
+ && logicalWidth == other.logicalWidth
+ && logicalHeight == other.logicalHeight
+ && overscanLeft == other.overscanLeft
+ && overscanTop == other.overscanTop
+ && overscanRight == other.overscanRight
+ && overscanBottom == other.overscanBottom
+ && rotation == other.rotation
+ && modeId == other.modeId
+ && defaultModeId == other.defaultModeId
+ && colorMode == other.colorMode
+ && Arrays.equals(supportedColorModes, other.supportedColorModes)
+ && Objects.equal(hdrCapabilities, other.hdrCapabilities)
+ && logicalDensityDpi == other.logicalDensityDpi
+ && physicalXDpi == other.physicalXDpi
+ && physicalYDpi == other.physicalYDpi
+ && appVsyncOffsetNanos == other.appVsyncOffsetNanos
+ && presentationDeadlineNanos == other.presentationDeadlineNanos
+ && state == other.state
+ && ownerUid == other.ownerUid
+ && Objects.equal(ownerPackageName, other.ownerPackageName)
+ && removeMode == other.removeMode;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ public void copyFrom(DisplayInfo other) {
+ layerStack = other.layerStack;
+ flags = other.flags;
+ type = other.type;
+ address = other.address;
+ name = other.name;
+ uniqueId = other.uniqueId;
+ appWidth = other.appWidth;
+ appHeight = other.appHeight;
+ smallestNominalAppWidth = other.smallestNominalAppWidth;
+ smallestNominalAppHeight = other.smallestNominalAppHeight;
+ largestNominalAppWidth = other.largestNominalAppWidth;
+ largestNominalAppHeight = other.largestNominalAppHeight;
+ logicalWidth = other.logicalWidth;
+ logicalHeight = other.logicalHeight;
+ overscanLeft = other.overscanLeft;
+ overscanTop = other.overscanTop;
+ overscanRight = other.overscanRight;
+ overscanBottom = other.overscanBottom;
+ rotation = other.rotation;
+ modeId = other.modeId;
+ defaultModeId = other.defaultModeId;
+ supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
+ colorMode = other.colorMode;
+ supportedColorModes = Arrays.copyOf(
+ other.supportedColorModes, other.supportedColorModes.length);
+ hdrCapabilities = other.hdrCapabilities;
+ logicalDensityDpi = other.logicalDensityDpi;
+ physicalXDpi = other.physicalXDpi;
+ physicalYDpi = other.physicalYDpi;
+ appVsyncOffsetNanos = other.appVsyncOffsetNanos;
+ presentationDeadlineNanos = other.presentationDeadlineNanos;
+ state = other.state;
+ ownerUid = other.ownerUid;
+ ownerPackageName = other.ownerPackageName;
+ removeMode = other.removeMode;
+ }
+
+ public void readFromParcel(Parcel source) {
+ layerStack = source.readInt();
+ flags = source.readInt();
+ type = source.readInt();
+ address = source.readString();
+ name = source.readString();
+ appWidth = source.readInt();
+ appHeight = source.readInt();
+ smallestNominalAppWidth = source.readInt();
+ smallestNominalAppHeight = source.readInt();
+ largestNominalAppWidth = source.readInt();
+ largestNominalAppHeight = source.readInt();
+ logicalWidth = source.readInt();
+ logicalHeight = source.readInt();
+ overscanLeft = source.readInt();
+ overscanTop = source.readInt();
+ overscanRight = source.readInt();
+ overscanBottom = source.readInt();
+ rotation = source.readInt();
+ modeId = source.readInt();
+ defaultModeId = source.readInt();
+ int nModes = source.readInt();
+ supportedModes = new Display.Mode[nModes];
+ for (int i = 0; i < nModes; i++) {
+ supportedModes[i] = Display.Mode.CREATOR.createFromParcel(source);
+ }
+ colorMode = source.readInt();
+ int nColorModes = source.readInt();
+ supportedColorModes = new int[nColorModes];
+ for (int i = 0; i < nColorModes; i++) {
+ supportedColorModes[i] = source.readInt();
+ }
+ hdrCapabilities = source.readParcelable(null);
+ logicalDensityDpi = source.readInt();
+ physicalXDpi = source.readFloat();
+ physicalYDpi = source.readFloat();
+ appVsyncOffsetNanos = source.readLong();
+ presentationDeadlineNanos = source.readLong();
+ state = source.readInt();
+ ownerUid = source.readInt();
+ ownerPackageName = source.readString();
+ uniqueId = source.readString();
+ removeMode = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(layerStack);
+ dest.writeInt(this.flags);
+ dest.writeInt(type);
+ dest.writeString(address);
+ dest.writeString(name);
+ dest.writeInt(appWidth);
+ dest.writeInt(appHeight);
+ dest.writeInt(smallestNominalAppWidth);
+ dest.writeInt(smallestNominalAppHeight);
+ dest.writeInt(largestNominalAppWidth);
+ dest.writeInt(largestNominalAppHeight);
+ dest.writeInt(logicalWidth);
+ dest.writeInt(logicalHeight);
+ dest.writeInt(overscanLeft);
+ dest.writeInt(overscanTop);
+ dest.writeInt(overscanRight);
+ dest.writeInt(overscanBottom);
+ dest.writeInt(rotation);
+ dest.writeInt(modeId);
+ dest.writeInt(defaultModeId);
+ dest.writeInt(supportedModes.length);
+ for (int i = 0; i < supportedModes.length; i++) {
+ supportedModes[i].writeToParcel(dest, flags);
+ }
+ dest.writeInt(colorMode);
+ dest.writeInt(supportedColorModes.length);
+ for (int i = 0; i < supportedColorModes.length; i++) {
+ dest.writeInt(supportedColorModes[i]);
+ }
+ dest.writeParcelable(hdrCapabilities, flags);
+ dest.writeInt(logicalDensityDpi);
+ dest.writeFloat(physicalXDpi);
+ dest.writeFloat(physicalYDpi);
+ dest.writeLong(appVsyncOffsetNanos);
+ dest.writeLong(presentationDeadlineNanos);
+ dest.writeInt(state);
+ dest.writeInt(ownerUid);
+ dest.writeString(ownerPackageName);
+ dest.writeString(uniqueId);
+ dest.writeInt(removeMode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public Display.Mode getMode() {
+ return findMode(modeId);
+ }
+
+ public Display.Mode getDefaultMode() {
+ return findMode(defaultModeId);
+ }
+
+ private Display.Mode findMode(int id) {
+ for (int i = 0; i < supportedModes.length; i++) {
+ if (supportedModes[i].getModeId() == id) {
+ return supportedModes[i];
+ }
+ }
+ throw new IllegalStateException("Unable to locate mode " + id);
+ }
+
+ /**
+ * Returns the id of the "default" mode with the given refresh rate, or {@code 0} if no suitable
+ * mode could be found.
+ */
+ public int findDefaultModeByRefreshRate(float refreshRate) {
+ Display.Mode[] modes = supportedModes;
+ Display.Mode defaultMode = getDefaultMode();
+ for (int i = 0; i < modes.length; i++) {
+ if (modes[i].matches(
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), refreshRate)) {
+ return modes[i].getModeId();
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the list of supported refresh rates in the default mode.
+ */
+ public float[] getDefaultRefreshRates() {
+ Display.Mode[] modes = supportedModes;
+ ArraySet<Float> rates = new ArraySet<>();
+ Display.Mode defaultMode = getDefaultMode();
+ for (int i = 0; i < modes.length; i++) {
+ Display.Mode mode = modes[i];
+ if (mode.getPhysicalWidth() == defaultMode.getPhysicalWidth()
+ && mode.getPhysicalHeight() == defaultMode.getPhysicalHeight()) {
+ rates.add(mode.getRefreshRate());
+ }
+ }
+ float[] result = new float[rates.size()];
+ int i = 0;
+ for (Float rate : rates) {
+ result[i++] = rate;
+ }
+ return result;
+ }
+
+ public void getAppMetrics(DisplayMetrics outMetrics) {
+ getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ }
+
+ public void getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments) {
+ getMetricsWithSize(outMetrics, displayAdjustments.getCompatibilityInfo(),
+ displayAdjustments.getConfiguration(), appWidth, appHeight);
+ }
+
+ public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci,
+ Configuration configuration) {
+ getMetricsWithSize(outMetrics, ci, configuration, appWidth, appHeight);
+ }
+
+ public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+ Configuration configuration) {
+ getMetricsWithSize(outMetrics, compatInfo, configuration, logicalWidth, logicalHeight);
+ }
+
+ public int getNaturalWidth() {
+ return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ?
+ logicalWidth : logicalHeight;
+ }
+
+ public int getNaturalHeight() {
+ return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ?
+ logicalHeight : logicalWidth;
+ }
+
+ public boolean isHdr() {
+ int[] types = hdrCapabilities != null ? hdrCapabilities.getSupportedHdrTypes() : null;
+ return types != null && types.length > 0;
+ }
+
+ public boolean isWideColorGamut() {
+ for (int colorMode : supportedColorModes) {
+ if (colorMode == Display.COLOR_MODE_DCI_P3 || colorMode > Display.COLOR_MODE_SRGB) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the specified UID has access to this display.
+ */
+ public boolean hasAccess(int uid) {
+ return Display.hasAccess(uid, flags, ownerUid);
+ }
+
+ private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+ Configuration configuration, int width, int height) {
+ outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
+ outMetrics.density = outMetrics.noncompatDensity =
+ logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
+ outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
+ outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
+
+ final Rect appBounds = configuration != null
+ ? configuration.windowConfiguration.getAppBounds() : null;
+ width = appBounds != null ? appBounds.width() : width;
+ height = appBounds != null ? appBounds.height() : height;
+
+ outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
+ outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
+
+ if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
+ compatInfo.applyToDisplayMetrics(outMetrics);
+ }
+ }
+
+ // For debugging purposes
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayInfo{\"");
+ sb.append(name);
+ sb.append("\", uniqueId \"");
+ sb.append(uniqueId);
+ sb.append("\", app ");
+ sb.append(appWidth);
+ sb.append(" x ");
+ sb.append(appHeight);
+ sb.append(", real ");
+ sb.append(logicalWidth);
+ sb.append(" x ");
+ sb.append(logicalHeight);
+ if (overscanLeft != 0 || overscanTop != 0 || overscanRight != 0 || overscanBottom != 0) {
+ sb.append(", overscan (");
+ sb.append(overscanLeft);
+ sb.append(",");
+ sb.append(overscanTop);
+ sb.append(",");
+ sb.append(overscanRight);
+ sb.append(",");
+ sb.append(overscanBottom);
+ sb.append(")");
+ }
+ sb.append(", largest app ");
+ sb.append(largestNominalAppWidth);
+ sb.append(" x ");
+ sb.append(largestNominalAppHeight);
+ sb.append(", smallest app ");
+ sb.append(smallestNominalAppWidth);
+ sb.append(" x ");
+ sb.append(smallestNominalAppHeight);
+ sb.append(", mode ");
+ sb.append(modeId);
+ sb.append(", defaultMode ");
+ sb.append(defaultModeId);
+ sb.append(", modes ");
+ sb.append(Arrays.toString(supportedModes));
+ sb.append(", colorMode ");
+ sb.append(colorMode);
+ sb.append(", supportedColorModes ");
+ sb.append(Arrays.toString(supportedColorModes));
+ sb.append(", hdrCapabilities ");
+ sb.append(hdrCapabilities);
+ sb.append(", rotation ");
+ sb.append(rotation);
+ sb.append(", density ");
+ sb.append(logicalDensityDpi);
+ sb.append(" (");
+ sb.append(physicalXDpi);
+ sb.append(" x ");
+ sb.append(physicalYDpi);
+ sb.append(") dpi, layerStack ");
+ sb.append(layerStack);
+ sb.append(", appVsyncOff ");
+ sb.append(appVsyncOffsetNanos);
+ sb.append(", presDeadline ");
+ sb.append(presentationDeadlineNanos);
+ sb.append(", type ");
+ sb.append(Display.typeToString(type));
+ if (address != null) {
+ sb.append(", address ").append(address);
+ }
+ sb.append(", state ");
+ sb.append(Display.stateToString(state));
+ if (ownerUid != 0 || ownerPackageName != null) {
+ sb.append(", owner ").append(ownerPackageName);
+ sb.append(" (uid ").append(ownerUid).append(")");
+ }
+ sb.append(flagsToString(flags));
+ sb.append(", removeMode ");
+ sb.append(removeMode);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.view.DisplayInfoProto}
+ *
+ * @param protoOutputStream Stream to write the Rect object to.
+ * @param fieldId Field Id of the DisplayInfoProto as defined in the parent message
+ */
+ public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ final long token = protoOutputStream.start(fieldId);
+ protoOutputStream.write(LOGICAL_WIDTH, logicalWidth);
+ protoOutputStream.write(LOGICAL_HEIGHT, logicalHeight);
+ protoOutputStream.write(APP_WIDTH, appWidth);
+ protoOutputStream.write(APP_HEIGHT, appHeight);
+ protoOutputStream.end(token);
+ }
+
+ private static String flagsToString(int flags) {
+ StringBuilder result = new StringBuilder();
+ if ((flags & Display.FLAG_SECURE) != 0) {
+ result.append(", FLAG_SECURE");
+ }
+ if ((flags & Display.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) {
+ result.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS");
+ }
+ if ((flags & Display.FLAG_PRIVATE) != 0) {
+ result.append(", FLAG_PRIVATE");
+ }
+ if ((flags & Display.FLAG_PRESENTATION) != 0) {
+ result.append(", FLAG_PRESENTATION");
+ }
+ if ((flags & Display.FLAG_SCALING_DISABLED) != 0) {
+ result.append(", FLAG_SCALING_DISABLED");
+ }
+ if ((flags & Display.FLAG_ROUND) != 0) {
+ result.append(", FLAG_ROUND");
+ }
+ return result.toString();
+ }
+}
diff --git a/android/view/DisplayListCanvas.java b/android/view/DisplayListCanvas.java
new file mode 100644
index 00000000..8f9ae0e3
--- /dev/null
+++ b/android/view/DisplayListCanvas.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2010 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.Bitmap;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.util.Pools.SynchronizedPool;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * A Canvas implementation that records view system drawing operations for deferred rendering.
+ * This is intended for use with a DisplayList. This class keeps a list of all the Paint and
+ * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
+ * the DisplayList is still holding a native reference to the memory.
+ *
+ * @hide
+ */
+public final class DisplayListCanvas extends RecordingCanvas {
+ // The recording canvas pool should be large enough to handle a deeply nested
+ // view hierarchy because display lists are generated recursively.
+ private static final int POOL_LIMIT = 25;
+
+ private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
+
+ private static final SynchronizedPool<DisplayListCanvas> sPool =
+ new SynchronizedPool<>(POOL_LIMIT);
+
+ RenderNode mNode;
+ private int mWidth;
+ private int mHeight;
+
+ static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
+ if (node == null) throw new IllegalArgumentException("node cannot be null");
+ DisplayListCanvas canvas = sPool.acquire();
+ if (canvas == null) {
+ canvas = new DisplayListCanvas(node, width, height);
+ } else {
+ nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
+ width, height);
+ }
+ canvas.mNode = node;
+ canvas.mWidth = width;
+ canvas.mHeight = height;
+ return canvas;
+ }
+
+ void recycle() {
+ mNode = null;
+ sPool.release(this);
+ }
+
+ long finishRecording() {
+ return nFinishRecording(mNativeCanvasWrapper);
+ }
+
+ @Override
+ public boolean isRecordingFor(Object o) {
+ return o == mNode;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Constructors
+ ///////////////////////////////////////////////////////////////////////////
+
+ private DisplayListCanvas(@NonNull RenderNode node, int width, int height) {
+ super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
+ mDensity = 0; // disable bitmap density scaling
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Canvas management
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ @Override
+ public void setDensity(int density) {
+ // drop silently, since DisplayListCanvas doesn't perform density scaling
+ }
+
+ @Override
+ public boolean isHardwareAccelerated() {
+ return true;
+ }
+
+ @Override
+ public void setBitmap(Bitmap bitmap) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return false;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public int getMaximumBitmapWidth() {
+ return nGetMaximumTextureWidth();
+ }
+
+ @Override
+ public int getMaximumBitmapHeight() {
+ return nGetMaximumTextureHeight();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Setup
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void insertReorderBarrier() {
+ nInsertReorderBarrier(mNativeCanvasWrapper, true);
+ }
+
+ @Override
+ public void insertInorderBarrier() {
+ nInsertReorderBarrier(mNativeCanvasWrapper, false);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Functor
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Records the functor specified with the drawGLFunction function pointer. This is
+ * functionality used by webview for calling into their renderer from our display lists.
+ *
+ * @param drawGLFunction A native function pointer
+ */
+ public void callDrawGLFunction2(long drawGLFunction) {
+ nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction, null);
+ }
+
+ /**
+ * Records the functor specified with the drawGLFunction function pointer. This is
+ * functionality used by webview for calling into their renderer from our display lists.
+ *
+ * @param drawGLFunction A native function pointer
+ * @param releasedCallback Called when the display list is destroyed, and thus
+ * the functor is no longer referenced by this canvas's display list.
+ *
+ * NOTE: The callback does *not* necessarily mean that there are no longer
+ * any references to the functor, just that the reference from this specific
+ * canvas's display list has been released.
+ */
+ public void drawGLFunctor2(long drawGLFunctor, @Nullable Runnable releasedCallback) {
+ nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunctor, releasedCallback);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Display list
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Draws the specified display list onto this canvas. The display list can only
+ * be drawn if {@link android.view.RenderNode#isValid()} returns true.
+ *
+ * @param renderNode The RenderNode to draw.
+ */
+ public void drawRenderNode(RenderNode renderNode) {
+ nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList());
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Hardware layer
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Draws the specified layer onto this canvas.
+ *
+ * @param layer The layer to composite on this canvas
+ */
+ void drawHardwareLayer(HardwareLayer layer) {
+ nDrawLayer(mNativeCanvasWrapper, layer.getLayerHandle());
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Drawing
+ ///////////////////////////////////////////////////////////////////////////
+
+ public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
+ CanvasProperty<Float> radius, CanvasProperty<Paint> paint) {
+ nDrawCircle(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
+ radius.getNativeContainer(), paint.getNativeContainer());
+ }
+
+ public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
+ CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
+ CanvasProperty<Float> ry, CanvasProperty<Paint> paint) {
+ nDrawRoundRect(mNativeCanvasWrapper, left.getNativeContainer(), top.getNativeContainer(),
+ right.getNativeContainer(), bottom.getNativeContainer(),
+ rx.getNativeContainer(), ry.getNativeContainer(),
+ paint.getNativeContainer());
+ }
+
+ @Override
+ protected void throwIfCannotDraw(Bitmap bitmap) {
+ super.throwIfCannotDraw(bitmap);
+ int bitmapSize = bitmap.getByteCount();
+ if (bitmapSize > MAX_BITMAP_SIZE) {
+ throw new RuntimeException(
+ "Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
+ }
+ }
+
+
+ // ------------------ Fast JNI ------------------------
+
+ @FastNative
+ private static native void nCallDrawGLFunction(long renderer,
+ long drawGLFunction, Runnable releasedCallback);
+
+
+ // ------------------ Critical JNI ------------------------
+
+ @CriticalNative
+ private static native long nCreateDisplayListCanvas(long node, int width, int height);
+ @CriticalNative
+ private static native void nResetDisplayListCanvas(long canvas, long node,
+ int width, int height);
+ @CriticalNative
+ private static native int nGetMaximumTextureWidth();
+ @CriticalNative
+ private static native int nGetMaximumTextureHeight();
+ @CriticalNative
+ private static native void nInsertReorderBarrier(long renderer, boolean enableReorder);
+ @CriticalNative
+ private static native long nFinishRecording(long renderer);
+ @CriticalNative
+ private static native void nDrawRenderNode(long renderer, long renderNode);
+ @CriticalNative
+ private static native void nDrawLayer(long renderer, long layer);
+ @CriticalNative
+ private static native void nDrawCircle(long renderer, long propCx,
+ long propCy, long propRadius, long propPaint);
+ @CriticalNative
+ private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
+ long propRight, long propBottom, long propRx, long propRy, long propPaint);
+}
diff --git a/android/view/Display_Delegate.java b/android/view/Display_Delegate.java
new file mode 100644
index 00000000..53dc821f
--- /dev/null
+++ b/android/view/Display_Delegate.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Display}
+ *
+ * Through the layoutlib_create tool, the original methods of Display have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Display_Delegate {
+
+ @LayoutlibDelegate
+ static void updateDisplayInfoLocked(Display theDisplay) {
+ // do nothing
+ }
+
+}
diff --git a/android/view/DragAndDropPermissions.java b/android/view/DragAndDropPermissions.java
new file mode 100644
index 00000000..c198e1f3
--- /dev/null
+++ b/android/view/DragAndDropPermissions.java
@@ -0,0 +1,148 @@
+/*
+** Copyright 2015, 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.app.Activity;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.android.internal.view.IDragAndDropPermissions;
+
+/**
+ * {@link DragAndDropPermissions} controls the access permissions for the content URIs associated
+ * with a {@link DragEvent}.
+ * <p>
+ * Permission are granted when this object is created by {@link
+ * android.app.Activity#requestDragAndDropPermissions(DragEvent)
+ * Activity.requestDragAndDropPermissions}.
+ * Which permissions are granted is defined by the set of flags passed to {@link
+ * View#startDragAndDrop(android.content.ClipData, View.DragShadowBuilder, Object, int)
+ * View.startDragAndDrop} by the app that started the drag operation.
+ * </p>
+ * <p>
+ * The life cycle of the permissions is bound to the activity used to call {@link
+ * android.app.Activity#requestDragAndDropPermissions(DragEvent) requestDragAndDropPermissions}. The
+ * permissions are revoked when this activity is destroyed, or when {@link #release()} is called,
+ * whichever occurs first.
+ * </p>
+ * <p>
+ * If you anticipate that your application will receive a large number of drops (e.g. document
+ * editor), you should try to call {@link #release()} on the obtained permissions as soon as they
+ * are no longer required. Permissions can be added to your activity's
+ * {@link Activity#onSaveInstanceState} bundle and later retrieved in order to manually release
+ * the permissions once they are no longer needed.
+ * </p>
+ */
+public final class DragAndDropPermissions implements Parcelable {
+
+ private final IDragAndDropPermissions mDragAndDropPermissions;
+
+ private IBinder mTransientToken;
+
+ /**
+ * Create a new {@link DragAndDropPermissions} object to control the access permissions for
+ * content URIs associated with {@link DragEvent}.
+ * @param dragEvent Drag event
+ * @return {@link DragAndDropPermissions} object or null if there are no content URIs associated
+ * with the {@link DragEvent}.
+ * @hide
+ */
+ public static DragAndDropPermissions obtain(DragEvent dragEvent) {
+ if (dragEvent.getDragAndDropPermissions() == null) {
+ return null;
+ }
+ return new DragAndDropPermissions(dragEvent.getDragAndDropPermissions());
+ }
+
+ /** @hide */
+ private DragAndDropPermissions(IDragAndDropPermissions dragAndDropPermissions) {
+ mDragAndDropPermissions = dragAndDropPermissions;
+ }
+
+ /**
+ * Take the permissions and bind their lifetime to the activity.
+ * @param activityToken Binder pointing to an Activity instance to bind the lifetime to.
+ * @return True if permissions are successfully taken.
+ * @hide
+ */
+ public boolean take(IBinder activityToken) {
+ try {
+ mDragAndDropPermissions.take(activityToken);
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Take the permissions. Must call {@link #release} explicitly.
+ * @return True if permissions are successfully taken.
+ * @hide
+ */
+ public boolean takeTransient() {
+ try {
+ mTransientToken = new Binder();
+ mDragAndDropPermissions.takeTransient(mTransientToken);
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Revoke permissions explicitly.
+ */
+ public void release() {
+ try {
+ mDragAndDropPermissions.release();
+ mTransientToken = null;
+ } catch (RemoteException e) {
+ }
+ }
+
+ public static final Parcelable.Creator<DragAndDropPermissions> CREATOR =
+ new Parcelable.Creator<DragAndDropPermissions> () {
+ @Override
+ public DragAndDropPermissions createFromParcel(Parcel source) {
+ return new DragAndDropPermissions(source);
+ }
+
+ @Override
+ public DragAndDropPermissions[] newArray(int size) {
+ return new DragAndDropPermissions[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel destination, int flags) {
+ destination.writeStrongInterface(mDragAndDropPermissions);
+ destination.writeStrongBinder(mTransientToken);
+ }
+
+ private DragAndDropPermissions(Parcel in) {
+ mDragAndDropPermissions = IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());
+ mTransientToken = in.readStrongBinder();
+ }
+}
diff --git a/android/view/DragEvent.java b/android/view/DragEvent.java
new file mode 100644
index 00000000..16f2d7d0
--- /dev/null
+++ b/android/view/DragEvent.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2010 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.content.ClipData;
+import android.content.ClipDescription;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.view.IDragAndDropPermissions;
+
+//TODO: Improve Javadoc
+/**
+ * Represents an event that is sent out by the system at various times during a drag and drop
+ * operation. It is a data structure that contains several important pieces of data about
+ * the operation and the underlying data.
+ * <p>
+ * View objects that receive a DragEvent call {@link #getAction()}, which returns
+ * an action type that indicates the state of the drag and drop operation. This allows a View
+ * object to react to a change in state by changing its appearance or performing other actions.
+ * For example, a View can react to the {@link #ACTION_DRAG_ENTERED} action type by
+ * by changing one or more colors in its displayed image.
+ * </p>
+ * <p>
+ * During a drag and drop operation, the system displays an image that the user drags. This image
+ * is called a drag shadow. Several action types reflect the position of the drag shadow relative
+ * to the View receiving the event.
+ * </p>
+ * <p>
+ * Most methods return valid data only for certain event actions. This is summarized in the
+ * following table. Each possible {@link #getAction()} value is listed in the first column. The
+ * other columns indicate which method or methods return valid data for that getAction() value:
+ * </p>
+ * <table>
+ * <tr>
+ * <th scope="col">getAction() Value</th>
+ * <th scope="col">getClipDescription()</th>
+ * <th scope="col">getLocalState()</th>
+ * <th scope="col">getX()</th>
+ * <th scope="col">getY()</th>
+ * <th scope="col">getClipData()</th>
+ * <th scope="col">getResult()</th>
+ * </tr>
+ * <tr>
+ * <td>ACTION_DRAG_STARTED</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>ACTION_DRAG_ENTERED</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>ACTION_DRAG_LOCATION</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>ACTION_DRAG_EXITED</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>ACTION_DROP</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">X</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>ACTION_DRAG_ENDED</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">X</td>
+ * </tr>
+ * </table>
+ * <p>
+ * The {@link android.view.DragEvent#getAction()},
+ * {@link android.view.DragEvent#describeContents()},
+ * {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and
+ * {@link android.view.DragEvent#toString()} methods always return valid data.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For a guide to implementing drag and drop features, read the
+ * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+ * </div>
+ */
+public class DragEvent implements Parcelable {
+ private static final boolean TRACK_RECYCLED_LOCATION = false;
+
+ int mAction;
+ float mX, mY;
+ ClipDescription mClipDescription;
+ ClipData mClipData;
+ IDragAndDropPermissions mDragAndDropPermissions;
+
+ Object mLocalState;
+ boolean mDragResult;
+ boolean mEventHandlerWasCalled;
+
+ private DragEvent mNext;
+ private RuntimeException mRecycledLocation;
+ private boolean mRecycled;
+
+ private static final int MAX_RECYCLED = 10;
+ private static final Object gRecyclerLock = new Object();
+ private static int gRecyclerUsed = 0;
+ private static DragEvent gRecyclerTop = null;
+
+ /**
+ * Action constant returned by {@link #getAction()}: Signals the start of a
+ * drag and drop operation. The View should return {@code true} from its
+ * {@link View#onDragEvent(DragEvent) onDragEvent()} handler method or
+ * {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} listener
+ * if it can accept a drop. The onDragEvent() or onDrag() methods usually inspect the metadata
+ * from {@link #getClipDescription()} to determine if they can accept the data contained in
+ * this drag. For an operation that doesn't represent data transfer, these methods may
+ * perform other actions to determine whether or not the View should accept the data.
+ * If the View wants to indicate that it is a valid drop target, it can also react by
+ * changing its appearance.
+ * <p>
+ * Views added or becoming visible for the first time during a drag operation receive this
+ * event when they are added or becoming visible.
+ * </p>
+ * <p>
+ * A View only receives further drag events for the drag operation if it returns {@code true}
+ * in response to ACTION_DRAG_STARTED.
+ * </p>
+ * @see #ACTION_DRAG_ENDED
+ * @see #getX()
+ * @see #getY()
+ */
+ public static final int ACTION_DRAG_STARTED = 1;
+
+ /**
+ * Action constant returned by {@link #getAction()}: Sent to a View after
+ * {@link #ACTION_DRAG_ENTERED} while the drag shadow is still within the View object's bounding
+ * box, but not within a descendant view that can accept the data. The {@link #getX()} and
+ * {@link #getY()} methods supply
+ * the X and Y position of of the drag point within the View object's bounding box.
+ * <p>
+ * A View receives an {@link #ACTION_DRAG_ENTERED} event before receiving any
+ * ACTION_DRAG_LOCATION events.
+ * </p>
+ * <p>
+ * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the
+ * drag shadow out of the View object's bounding box or into a descendant view that can accept
+ * the data. If the user moves the drag shadow back into the View object's bounding box or out
+ * of a descendant view that can accept the data, the View receives an ACTION_DRAG_ENTERED again
+ * before receiving any more ACTION_DRAG_LOCATION events.
+ * </p>
+ * @see #ACTION_DRAG_ENTERED
+ * @see #getX()
+ * @see #getY()
+ */
+ public static final int ACTION_DRAG_LOCATION = 2;
+
+ /**
+ * Action constant returned by {@link #getAction()}: Signals to a View that the user
+ * has released the drag shadow, and the drag point is within the bounding box of the View and
+ * not within a descendant view that can accept the data.
+ * The View should retrieve the data from the DragEvent by calling {@link #getClipData()}.
+ * The methods {@link #getX()} and {@link #getY()} return the X and Y position of the drop point
+ * within the View object's bounding box.
+ * <p>
+ * The View should return {@code true} from its {@link View#onDragEvent(DragEvent)}
+ * handler or {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()}
+ * listener if it accepted the drop, and {@code false} if it ignored the drop.
+ * </p>
+ * <p>
+ * The View can also react to this action by changing its appearance.
+ * </p>
+ * @see #getClipData()
+ * @see #getX()
+ * @see #getY()
+ */
+ public static final int ACTION_DROP = 3;
+
+ /**
+ * Action constant returned by {@link #getAction()}: Signals to a View that the drag and drop
+ * operation has concluded. A View that changed its appearance during the operation should
+ * return to its usual drawing state in response to this event.
+ * <p>
+ * All views with listeners that returned boolean <code>true</code> for the ACTION_DRAG_STARTED
+ * event will receive the ACTION_DRAG_ENDED event even if they are not currently visible when
+ * the drag ends. Views removed during the drag operation won't receive the ACTION_DRAG_ENDED
+ * event.
+ * </p>
+ * <p>
+ * The View object can call {@link #getResult()} to see the result of the operation.
+ * If a View returned {@code true} in response to {@link #ACTION_DROP}, then
+ * getResult() returns {@code true}, otherwise it returns {@code false}.
+ * </p>
+ * @see #ACTION_DRAG_STARTED
+ * @see #getResult()
+ */
+ public static final int ACTION_DRAG_ENDED = 4;
+
+ /**
+ * Action constant returned by {@link #getAction()}: Signals to a View that the drag point has
+ * entered the bounding box of the View.
+ * <p>
+ * If the View can accept a drop, it can react to ACTION_DRAG_ENTERED
+ * by changing its appearance in a way that tells the user that the View is the current
+ * drop target.
+ * </p>
+ * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the
+ * drag shadow out of the View object's bounding box or into a descendant view that can accept
+ * the data. If the user moves the drag shadow back into the View object's bounding box or out
+ * of a descendant view that can accept the data, the View receives an ACTION_DRAG_ENTERED again
+ * before receiving any more ACTION_DRAG_LOCATION events.
+ * </p>
+ * @see #ACTION_DRAG_ENTERED
+ * @see #ACTION_DRAG_LOCATION
+ */
+ public static final int ACTION_DRAG_ENTERED = 5;
+
+ /**
+ * Action constant returned by {@link #getAction()}: Signals that the user has moved the
+ * drag shadow out of the bounding box of the View or into a descendant view that can accept
+ * the data.
+ * The View can react by changing its appearance in a way that tells the user that
+ * View is no longer the immediate drop target.
+ * <p>
+ * After the system sends an ACTION_DRAG_EXITED event to the View, the View receives no more
+ * ACTION_DRAG_LOCATION events until the user drags the drag shadow back over the View.
+ * </p>
+ *
+ */
+ public static final int ACTION_DRAG_EXITED = 6;
+
+ private DragEvent() {
+ }
+
+ private void init(int action, float x, float y, ClipDescription description, ClipData data,
+ IDragAndDropPermissions dragAndDropPermissions, Object localState, boolean result) {
+ mAction = action;
+ mX = x;
+ mY = y;
+ mClipDescription = description;
+ mClipData = data;
+ this.mDragAndDropPermissions = dragAndDropPermissions;
+ mLocalState = localState;
+ mDragResult = result;
+ }
+
+ static DragEvent obtain() {
+ return DragEvent.obtain(0, 0f, 0f, null, null, null, null, false);
+ }
+
+ /** @hide */
+ public static DragEvent obtain(int action, float x, float y, Object localState,
+ ClipDescription description, ClipData data,
+ IDragAndDropPermissions dragAndDropPermissions, boolean result) {
+ final DragEvent ev;
+ synchronized (gRecyclerLock) {
+ if (gRecyclerTop == null) {
+ ev = new DragEvent();
+ ev.init(action, x, y, description, data, dragAndDropPermissions, localState,
+ result);
+ return ev;
+ }
+ ev = gRecyclerTop;
+ gRecyclerTop = ev.mNext;
+ gRecyclerUsed -= 1;
+ }
+ ev.mRecycledLocation = null;
+ ev.mRecycled = false;
+ ev.mNext = null;
+
+ ev.init(action, x, y, description, data, dragAndDropPermissions, localState, result);
+
+ return ev;
+ }
+
+ /** @hide */
+ public static DragEvent obtain(DragEvent source) {
+ return obtain(source.mAction, source.mX, source.mY, source.mLocalState,
+ source.mClipDescription, source.mClipData, source.mDragAndDropPermissions,
+ source.mDragResult);
+ }
+
+ /**
+ * Inspect the action value of this event.
+ * @return One of the following action constants, in the order in which they usually occur
+ * during a drag and drop operation:
+ * <ul>
+ * <li>{@link #ACTION_DRAG_STARTED}</li>
+ * <li>{@link #ACTION_DRAG_ENTERED}</li>
+ * <li>{@link #ACTION_DRAG_LOCATION}</li>
+ * <li>{@link #ACTION_DROP}</li>
+ * <li>{@link #ACTION_DRAG_EXITED}</li>
+ * <li>{@link #ACTION_DRAG_ENDED}</li>
+ * </ul>
+ */
+ public int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Gets the X coordinate of the drag point. The value is only valid if the event action is
+ * {@link #ACTION_DRAG_STARTED}, {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}.
+ * @return The current drag point's X coordinate
+ */
+ public float getX() {
+ return mX;
+ }
+
+ /**
+ * Gets the Y coordinate of the drag point. The value is only valid if the event action is
+ * {@link #ACTION_DRAG_STARTED}, {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}.
+ * @return The current drag point's Y coordinate
+ */
+ public float getY() {
+ return mY;
+ }
+
+ /**
+ * Returns the {@link android.content.ClipData} object sent to the system as part of the call
+ * to
+ * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
+ * This method only returns valid data if the event action is {@link #ACTION_DROP}.
+ * @return The ClipData sent to the system by startDragAndDrop().
+ */
+ public ClipData getClipData() {
+ return mClipData;
+ }
+
+ /**
+ * Returns the {@link android.content.ClipDescription} object contained in the
+ * {@link android.content.ClipData} object sent to the system as part of the call to
+ * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
+ * The drag handler or listener for a View can use the metadata in this object to decide if the
+ * View can accept the dragged View object's data.
+ * <p>
+ * This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
+ * @return The ClipDescription that was part of the ClipData sent to the system by
+ * startDragAndDrop().
+ */
+ public ClipDescription getClipDescription() {
+ return mClipDescription;
+ }
+
+ /** @hide */
+ public IDragAndDropPermissions getDragAndDropPermissions() {
+ return mDragAndDropPermissions;
+ }
+
+ /**
+ * Returns the local state object sent to the system as part of the call to
+ * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
+ * The object is intended to provide local information about the drag and drop operation. For
+ * example, it can indicate whether the drag and drop operation is a copy or a move.
+ * <p>
+ * The local state is available only to views in the activity which has started the drag
+ * operation. In all other activities this method will return null
+ * </p>
+ * <p>
+ * This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
+ * </p>
+ * @return The local state object sent to the system by startDragAndDrop().
+ */
+ public Object getLocalState() {
+ return mLocalState;
+ }
+
+ /**
+ * <p>
+ * Returns an indication of the result of the drag and drop operation.
+ * This method only returns valid data if the action type is {@link #ACTION_DRAG_ENDED}.
+ * The return value depends on what happens after the user releases the drag shadow.
+ * </p>
+ * <p>
+ * If the user releases the drag shadow on a View that can accept a drop, the system sends an
+ * {@link #ACTION_DROP} event to the View object's drag event listener. If the listener
+ * returns {@code true}, then getResult() will return {@code true}.
+ * If the listener returns {@code false}, then getResult() returns {@code false}.
+ * </p>
+ * <p>
+ * Notice that getResult() also returns {@code false} if no {@link #ACTION_DROP} is sent. This
+ * happens, for example, when the user releases the drag shadow over an area outside of the
+ * application. In this case, the system sends out {@link #ACTION_DRAG_ENDED} for the current
+ * operation, but never sends out {@link #ACTION_DROP}.
+ * </p>
+ * @return {@code true} if a drag event listener returned {@code true} in response to
+ * {@link #ACTION_DROP}. If the system did not send {@link #ACTION_DROP} before
+ * {@link #ACTION_DRAG_ENDED}, or if the listener returned {@code false} in response to
+ * {@link #ACTION_DROP}, then {@code false} is returned.
+ */
+ public boolean getResult() {
+ return mDragResult;
+ }
+
+ /**
+ * Recycle the DragEvent, to be re-used by a later caller. After calling
+ * this function you must never touch the event again.
+ *
+ * @hide
+ */
+ public final void recycle() {
+ // Ensure recycle is only called once!
+ if (TRACK_RECYCLED_LOCATION) {
+ if (mRecycledLocation != null) {
+ throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+ }
+ mRecycledLocation = new RuntimeException("Last recycled here");
+ } else {
+ if (mRecycled) {
+ throw new RuntimeException(toString() + " recycled twice!");
+ }
+ mRecycled = true;
+ }
+
+ mClipData = null;
+ mClipDescription = null;
+ mLocalState = null;
+ mEventHandlerWasCalled = false;
+
+ synchronized (gRecyclerLock) {
+ if (gRecyclerUsed < MAX_RECYCLED) {
+ gRecyclerUsed++;
+ mNext = gRecyclerTop;
+ gRecyclerTop = this;
+ }
+ }
+ }
+
+ /**
+ * Returns a string containing a concise, human-readable representation of this DragEvent
+ * object.
+ * @return A string representation of the DragEvent object.
+ */
+ @Override
+ public String toString() {
+ return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
+ + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
+ + " data=" + mClipData + " local=" + mLocalState + " result=" + mDragResult
+ + "}";
+ }
+
+ /* Parcelable interface */
+
+ /**
+ * Returns information about the {@link android.os.Parcel} representation of this DragEvent
+ * object.
+ * @return Information about the {@link android.os.Parcel} representation.
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Creates a {@link android.os.Parcel} object from this DragEvent object.
+ * @param dest A {@link android.os.Parcel} object in which to put the DragEvent object.
+ * @param flags Flags to store in the Parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mAction);
+ dest.writeFloat(mX);
+ dest.writeFloat(mY);
+ dest.writeInt(mDragResult ? 1 : 0);
+ if (mClipData == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ mClipData.writeToParcel(dest, flags);
+ }
+ if (mClipDescription == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ mClipDescription.writeToParcel(dest, flags);
+ }
+ if (mDragAndDropPermissions == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeStrongBinder(mDragAndDropPermissions.asBinder());
+ }
+ }
+
+ /**
+ * A container for creating a DragEvent from a Parcel.
+ */
+ public static final Parcelable.Creator<DragEvent> CREATOR =
+ new Parcelable.Creator<DragEvent>() {
+ public DragEvent createFromParcel(Parcel in) {
+ DragEvent event = DragEvent.obtain();
+ event.mAction = in.readInt();
+ event.mX = in.readFloat();
+ event.mY = in.readFloat();
+ event.mDragResult = (in.readInt() != 0);
+ if (in.readInt() != 0) {
+ event.mClipData = ClipData.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ event.mDragAndDropPermissions =
+ IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());;
+ }
+ return event;
+ }
+
+ public DragEvent[] newArray(int size) {
+ return new DragEvent[size];
+ }
+ };
+}
diff --git a/android/view/FallbackEventHandler.java b/android/view/FallbackEventHandler.java
new file mode 100644
index 00000000..8e00d6da
--- /dev/null
+++ b/android/view/FallbackEventHandler.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 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;
+
+/**
+ * @hide
+ */
+public interface FallbackEventHandler {
+ public void setView(View v);
+ public void preDispatchKeyEvent(KeyEvent event);
+ public boolean dispatchKeyEvent(KeyEvent event);
+}
diff --git a/android/view/FocusFinder.java b/android/view/FocusFinder.java
new file mode 100644
index 00000000..74555de5
--- /dev/null
+++ b/android/view/FocusFinder.java
@@ -0,0 +1,983 @@
+/*
+ * Copyright (C) 2007 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.annotation.TestApi;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * The algorithm used for finding the next focusable view in a given direction
+ * from a view that currently has focus.
+ */
+public class FocusFinder {
+
+ private static final ThreadLocal<FocusFinder> tlFocusFinder =
+ new ThreadLocal<FocusFinder>() {
+ @Override
+ protected FocusFinder initialValue() {
+ return new FocusFinder();
+ }
+ };
+
+ /**
+ * Get the focus finder for this thread.
+ */
+ public static FocusFinder getInstance() {
+ return tlFocusFinder.get();
+ }
+
+ final Rect mFocusedRect = new Rect();
+ final Rect mOtherRect = new Rect();
+ final Rect mBestCandidateRect = new Rect();
+ private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator =
+ new UserSpecifiedFocusComparator((r, v) -> isValidId(v.getNextFocusForwardId())
+ ? v.findUserSetNextFocus(r, View.FOCUS_FORWARD) : null);
+ private final UserSpecifiedFocusComparator mUserSpecifiedClusterComparator =
+ new UserSpecifiedFocusComparator((r, v) -> isValidId(v.getNextClusterForwardId())
+ ? v.findUserSetNextKeyboardNavigationCluster(r, View.FOCUS_FORWARD) : null);
+ private final FocusSorter mFocusSorter = new FocusSorter();
+
+ private final ArrayList<View> mTempList = new ArrayList<View>();
+
+ // enforce thread local access
+ private FocusFinder() {}
+
+ /**
+ * Find the next view to take focus in root's descendants, starting from the view
+ * that currently is focused.
+ * @param root Contains focused. Cannot be null.
+ * @param focused Has focus now.
+ * @param direction Direction to look.
+ * @return The next focusable view, or null if none exists.
+ */
+ public final View findNextFocus(ViewGroup root, View focused, int direction) {
+ return findNextFocus(root, focused, null, direction);
+ }
+
+ /**
+ * Find the next view to take focus in root's descendants, searching from
+ * a particular rectangle in root's coordinates.
+ * @param root Contains focusedRect. Cannot be null.
+ * @param focusedRect The starting point of the search.
+ * @param direction Direction to look.
+ * @return The next focusable view, or null if none exists.
+ */
+ public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
+ mFocusedRect.set(focusedRect);
+ return findNextFocus(root, null, mFocusedRect, direction);
+ }
+
+ private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
+ View next = null;
+ ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
+ if (focused != null) {
+ next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
+ }
+ if (next != null) {
+ return next;
+ }
+ ArrayList<View> focusables = mTempList;
+ try {
+ focusables.clear();
+ effectiveRoot.addFocusables(focusables, direction);
+ if (!focusables.isEmpty()) {
+ next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
+ }
+ } finally {
+ focusables.clear();
+ }
+ return next;
+ }
+
+ /**
+ * Returns the "effective" root of a view. The "effective" root is the closest ancestor
+ * within-which focus should cycle.
+ * <p>
+ * For example: normal focus navigation would stay within a ViewGroup marked as
+ * touchscreenBlocksFocus and keyboardNavigationCluster until a cluster-jump out.
+ * @return the "effective" root of {@param focused}
+ */
+ private ViewGroup getEffectiveRoot(ViewGroup root, View focused) {
+ if (focused == null || focused == root) {
+ return root;
+ }
+ ViewGroup effective = null;
+ ViewParent nextParent = focused.getParent();
+ do {
+ if (nextParent == root) {
+ return effective != null ? effective : root;
+ }
+ ViewGroup vg = (ViewGroup) nextParent;
+ if (vg.getTouchscreenBlocksFocus()
+ && focused.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TOUCHSCREEN)
+ && vg.isKeyboardNavigationCluster()) {
+ // Don't stop and return here because the cluster could be nested and we only
+ // care about the top-most one.
+ effective = vg;
+ }
+ nextParent = nextParent.getParent();
+ } while (nextParent instanceof ViewGroup);
+ return root;
+ }
+
+ /**
+ * Find the root of the next keyboard navigation cluster after the current one.
+ * @param root The view tree to look inside. Cannot be null
+ * @param currentCluster The starting point of the search. Null means the default cluster
+ * @param direction Direction to look
+ * @return The next cluster, or null if none exists
+ */
+ public View findNextKeyboardNavigationCluster(
+ @NonNull View root,
+ @Nullable View currentCluster,
+ @View.FocusDirection int direction) {
+ View next = null;
+ if (currentCluster != null) {
+ next = findNextUserSpecifiedKeyboardNavigationCluster(root, currentCluster, direction);
+ if (next != null) {
+ return next;
+ }
+ }
+
+ final ArrayList<View> clusters = mTempList;
+ try {
+ clusters.clear();
+ root.addKeyboardNavigationClusters(clusters, direction);
+ if (!clusters.isEmpty()) {
+ next = findNextKeyboardNavigationCluster(
+ root, currentCluster, clusters, direction);
+ }
+ } finally {
+ clusters.clear();
+ }
+ return next;
+ }
+
+ private View findNextUserSpecifiedKeyboardNavigationCluster(View root, View currentCluster,
+ int direction) {
+ View userSetNextCluster =
+ currentCluster.findUserSetNextKeyboardNavigationCluster(root, direction);
+ if (userSetNextCluster != null && userSetNextCluster.hasFocusable()) {
+ return userSetNextCluster;
+ }
+ return null;
+ }
+
+ private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
+ // check for user specified next focus
+ View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
+ View cycleCheck = userSetNextFocus;
+ boolean cycleStep = true; // we want the first toggle to yield false
+ while (userSetNextFocus != null) {
+ if (userSetNextFocus.isFocusable()
+ && userSetNextFocus.getVisibility() == View.VISIBLE
+ && (!userSetNextFocus.isInTouchMode()
+ || userSetNextFocus.isFocusableInTouchMode())) {
+ return userSetNextFocus;
+ }
+ userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
+ if (cycleStep = !cycleStep) {
+ cycleCheck = cycleCheck.findUserSetNextFocus(root, direction);
+ if (cycleCheck == userSetNextFocus) {
+ // found a cycle, user-specified focus forms a loop and none of the views
+ // are currently focusable.
+ break;
+ }
+ }
+ }
+ return null;
+ }
+
+ private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
+ int direction, ArrayList<View> focusables) {
+ if (focused != null) {
+ if (focusedRect == null) {
+ focusedRect = mFocusedRect;
+ }
+ // fill in interesting rect from focused
+ focused.getFocusedRect(focusedRect);
+ root.offsetDescendantRectToMyCoords(focused, focusedRect);
+ } else {
+ if (focusedRect == null) {
+ focusedRect = mFocusedRect;
+ // make up a rect at top left or bottom right of root
+ switch (direction) {
+ case View.FOCUS_RIGHT:
+ case View.FOCUS_DOWN:
+ setFocusTopLeft(root, focusedRect);
+ break;
+ case View.FOCUS_FORWARD:
+ if (root.isLayoutRtl()) {
+ setFocusBottomRight(root, focusedRect);
+ } else {
+ setFocusTopLeft(root, focusedRect);
+ }
+ break;
+
+ case View.FOCUS_LEFT:
+ case View.FOCUS_UP:
+ setFocusBottomRight(root, focusedRect);
+ break;
+ case View.FOCUS_BACKWARD:
+ if (root.isLayoutRtl()) {
+ setFocusTopLeft(root, focusedRect);
+ } else {
+ setFocusBottomRight(root, focusedRect);
+ break;
+ }
+ }
+ }
+ }
+
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_BACKWARD:
+ return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
+ direction);
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ return findNextFocusInAbsoluteDirection(focusables, root, focused,
+ focusedRect, direction);
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ }
+
+ private View findNextKeyboardNavigationCluster(
+ View root,
+ View currentCluster,
+ List<View> clusters,
+ @View.FocusDirection int direction) {
+ try {
+ // Note: This sort is stable.
+ mUserSpecifiedClusterComparator.setFocusables(clusters, root);
+ Collections.sort(clusters, mUserSpecifiedClusterComparator);
+ } finally {
+ mUserSpecifiedClusterComparator.recycle();
+ }
+ final int count = clusters.size();
+
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_DOWN:
+ case View.FOCUS_RIGHT:
+ return getNextKeyboardNavigationCluster(root, currentCluster, clusters, count);
+ case View.FOCUS_BACKWARD:
+ case View.FOCUS_UP:
+ case View.FOCUS_LEFT:
+ return getPreviousKeyboardNavigationCluster(root, currentCluster, clusters, count);
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ }
+
+ private View findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
+ View focused, Rect focusedRect, int direction) {
+ try {
+ // Note: This sort is stable.
+ mUserSpecifiedFocusComparator.setFocusables(focusables, root);
+ Collections.sort(focusables, mUserSpecifiedFocusComparator);
+ } finally {
+ mUserSpecifiedFocusComparator.recycle();
+ }
+
+ final int count = focusables.size();
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ return getNextFocusable(focused, focusables, count);
+ case View.FOCUS_BACKWARD:
+ return getPreviousFocusable(focused, focusables, count);
+ }
+ return focusables.get(count - 1);
+ }
+
+ private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
+ final int rootBottom = root.getScrollY() + root.getHeight();
+ final int rootRight = root.getScrollX() + root.getWidth();
+ focusedRect.set(rootRight, rootBottom, rootRight, rootBottom);
+ }
+
+ private void setFocusTopLeft(ViewGroup root, Rect focusedRect) {
+ final int rootTop = root.getScrollY();
+ final int rootLeft = root.getScrollX();
+ focusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+ }
+
+ View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
+ Rect focusedRect, int direction) {
+ // initialize the best candidate to something impossible
+ // (so the first plausible view will become the best choice)
+ mBestCandidateRect.set(focusedRect);
+ switch(direction) {
+ case View.FOCUS_LEFT:
+ mBestCandidateRect.offset(focusedRect.width() + 1, 0);
+ break;
+ case View.FOCUS_RIGHT:
+ mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
+ break;
+ case View.FOCUS_UP:
+ mBestCandidateRect.offset(0, focusedRect.height() + 1);
+ break;
+ case View.FOCUS_DOWN:
+ mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
+ }
+
+ View closest = null;
+
+ int numFocusables = focusables.size();
+ for (int i = 0; i < numFocusables; i++) {
+ View focusable = focusables.get(i);
+
+ // only interested in other non-root views
+ if (focusable == focused || focusable == root) continue;
+
+ // get focus bounds of other view in same coordinate system
+ focusable.getFocusedRect(mOtherRect);
+ root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
+
+ if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
+ mBestCandidateRect.set(mOtherRect);
+ closest = focusable;
+ }
+ }
+ return closest;
+ }
+
+ private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+ if (focused != null) {
+ int position = focusables.lastIndexOf(focused);
+ if (position >= 0 && position + 1 < count) {
+ return focusables.get(position + 1);
+ }
+ }
+ if (!focusables.isEmpty()) {
+ return focusables.get(0);
+ }
+ return null;
+ }
+
+ private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+ if (focused != null) {
+ int position = focusables.indexOf(focused);
+ if (position > 0) {
+ return focusables.get(position - 1);
+ }
+ }
+ if (!focusables.isEmpty()) {
+ return focusables.get(count - 1);
+ }
+ return null;
+ }
+
+ private static View getNextKeyboardNavigationCluster(
+ View root,
+ View currentCluster,
+ List<View> clusters,
+ int count) {
+ if (currentCluster == null) {
+ // The current cluster is the default one.
+ // The next cluster after the default one is the first one.
+ // Note that the caller guarantees that 'clusters' is not empty.
+ return clusters.get(0);
+ }
+
+ final int position = clusters.lastIndexOf(currentCluster);
+ if (position >= 0 && position + 1 < count) {
+ // Return the next non-default cluster if we can find it.
+ return clusters.get(position + 1);
+ }
+
+ // The current cluster is the last one. The next one is the default one, i.e. the
+ // root.
+ return root;
+ }
+
+ private static View getPreviousKeyboardNavigationCluster(
+ View root,
+ View currentCluster,
+ List<View> clusters,
+ int count) {
+ if (currentCluster == null) {
+ // The current cluster is the default one.
+ // The previous cluster before the default one is the last one.
+ // Note that the caller guarantees that 'clusters' is not empty.
+ return clusters.get(count - 1);
+ }
+
+ final int position = clusters.indexOf(currentCluster);
+ if (position > 0) {
+ // Return the previous non-default cluster if we can find it.
+ return clusters.get(position - 1);
+ }
+
+ // The current cluster is the first one. The previous one is the default one, i.e.
+ // the root.
+ return root;
+ }
+
+ /**
+ * Is rect1 a better candidate than rect2 for a focus search in a particular
+ * direction from a source rect? This is the core routine that determines
+ * the order of focus searching.
+ * @param direction the direction (up, down, left, right)
+ * @param source The source we are searching from
+ * @param rect1 The candidate rectangle
+ * @param rect2 The current best candidate.
+ * @return Whether the candidate is the new best.
+ */
+ boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
+
+ // to be a better candidate, need to at least be a candidate in the first
+ // place :)
+ if (!isCandidate(source, rect1, direction)) {
+ return false;
+ }
+
+ // we know that rect1 is a candidate.. if rect2 is not a candidate,
+ // rect1 is better
+ if (!isCandidate(source, rect2, direction)) {
+ return true;
+ }
+
+ // if rect1 is better by beam, it wins
+ if (beamBeats(direction, source, rect1, rect2)) {
+ return true;
+ }
+
+ // if rect2 is better, then rect1 cant' be :)
+ if (beamBeats(direction, source, rect2, rect1)) {
+ return false;
+ }
+
+ // otherwise, do fudge-tastic comparison of the major and minor axis
+ return (getWeightedDistanceFor(
+ majorAxisDistance(direction, source, rect1),
+ minorAxisDistance(direction, source, rect1))
+ < getWeightedDistanceFor(
+ majorAxisDistance(direction, source, rect2),
+ minorAxisDistance(direction, source, rect2)));
+ }
+
+ /**
+ * One rectangle may be another candidate than another by virtue of being
+ * exclusively in the beam of the source rect.
+ * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's
+ * beam
+ */
+ boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
+ final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
+ final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
+
+ // if rect1 isn't exclusively in the src beam, it doesn't win
+ if (rect2InSrcBeam || !rect1InSrcBeam) {
+ return false;
+ }
+
+ // we know rect1 is in the beam, and rect2 is not
+
+ // if rect1 is to the direction of, and rect2 is not, rect1 wins.
+ // for example, for direction left, if rect1 is to the left of the source
+ // and rect2 is below, then we always prefer the in beam rect1, since rect2
+ // could be reached by going down.
+ if (!isToDirectionOf(direction, source, rect2)) {
+ return true;
+ }
+
+ // for horizontal directions, being exclusively in beam always wins
+ if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
+ return true;
+ }
+
+ // for vertical directions, beams only beat up to a point:
+ // now, as long as rect2 isn't completely closer, rect1 wins
+ // e.g for direction down, completely closer means for rect2's top
+ // edge to be closer to the source's top edge than rect1's bottom edge.
+ return (majorAxisDistance(direction, source, rect1)
+ < majorAxisDistanceToFarEdge(direction, source, rect2));
+ }
+
+ /**
+ * Fudge-factor opportunity: how to calculate distance given major and minor
+ * axis distances. Warning: this fudge factor is finely tuned, be sure to
+ * run all focus tests if you dare tweak it.
+ */
+ int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+ return 13 * majorAxisDistance * majorAxisDistance
+ + minorAxisDistance * minorAxisDistance;
+ }
+
+ /**
+ * Is destRect a candidate for the next focus given the direction? This
+ * checks whether the dest is at least partially to the direction of (e.g left of)
+ * from source.
+ *
+ * Includes an edge case for an empty rect (which is used in some cases when
+ * searching from a point on the screen).
+ */
+ boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
+ && srcRect.left > destRect.left;
+ case View.FOCUS_RIGHT:
+ return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
+ && srcRect.right < destRect.right;
+ case View.FOCUS_UP:
+ return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
+ && srcRect.top > destRect.top;
+ case View.FOCUS_DOWN:
+ return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
+ && srcRect.bottom < destRect.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+
+ /**
+ * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
+ * @param direction the direction (up, down, left, right)
+ * @param rect1 The first rectangle
+ * @param rect2 The second rectangle
+ * @return whether the beams overlap
+ */
+ boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ return (rect2.bottom > rect1.top) && (rect2.top < rect1.bottom);
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ return (rect2.right > rect1.left) && (rect2.left < rect1.right);
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * e.g for left, is 'to left of'
+ */
+ boolean isToDirectionOf(int direction, Rect src, Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return src.left >= dest.right;
+ case View.FOCUS_RIGHT:
+ return src.right <= dest.left;
+ case View.FOCUS_UP:
+ return src.top >= dest.bottom;
+ case View.FOCUS_DOWN:
+ return src.bottom <= dest.top;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * @return The distance from the edge furthest in the given direction
+ * of source to the edge nearest in the given direction of dest. If the
+ * dest is not in the direction from source, return 0.
+ */
+ static int majorAxisDistance(int direction, Rect source, Rect dest) {
+ return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
+ }
+
+ static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return source.left - dest.right;
+ case View.FOCUS_RIGHT:
+ return dest.left - source.right;
+ case View.FOCUS_UP:
+ return source.top - dest.bottom;
+ case View.FOCUS_DOWN:
+ return dest.top - source.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * @return The distance along the major axis w.r.t the direction from the
+ * edge of source to the far edge of dest. If the
+ * dest is not in the direction from source, return 1 (to break ties with
+ * {@link #majorAxisDistance}).
+ */
+ static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
+ return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
+ }
+
+ static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return source.left - dest.left;
+ case View.FOCUS_RIGHT:
+ return dest.right - source.right;
+ case View.FOCUS_UP:
+ return source.top - dest.top;
+ case View.FOCUS_DOWN:
+ return dest.bottom - source.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * Find the distance on the minor axis w.r.t the direction to the nearest
+ * edge of the destination rectangle.
+ * @param direction the direction (up, down, left, right)
+ * @param source The source rect.
+ * @param dest The destination rect.
+ * @return The distance.
+ */
+ static int minorAxisDistance(int direction, Rect source, Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ // the distance between the center verticals
+ return Math.abs(
+ ((source.top + source.height() / 2) -
+ ((dest.top + dest.height() / 2))));
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ // the distance between the center horizontals
+ return Math.abs(
+ ((source.left + source.width() / 2) -
+ ((dest.left + dest.width() / 2))));
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * Find the nearest touchable view to the specified view.
+ *
+ * @param root The root of the tree in which to search
+ * @param x X coordinate from which to start the search
+ * @param y Y coordinate from which to start the search
+ * @param direction Direction to look
+ * @param deltas Offset from the <x, y> to the edge of the nearest view. Note that this array
+ * may already be populated with values.
+ * @return The nearest touchable view, or null if none exists.
+ */
+ public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) {
+ ArrayList<View> touchables = root.getTouchables();
+ int minDistance = Integer.MAX_VALUE;
+ View closest = null;
+
+ int numTouchables = touchables.size();
+
+ int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop();
+
+ Rect closestBounds = new Rect();
+ Rect touchableBounds = mOtherRect;
+
+ for (int i = 0; i < numTouchables; i++) {
+ View touchable = touchables.get(i);
+
+ // get visible bounds of other view in same coordinate system
+ touchable.getDrawingRect(touchableBounds);
+
+ root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true);
+
+ if (!isTouchCandidate(x, y, touchableBounds, direction)) {
+ continue;
+ }
+
+ int distance = Integer.MAX_VALUE;
+
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ distance = x - touchableBounds.right + 1;
+ break;
+ case View.FOCUS_RIGHT:
+ distance = touchableBounds.left;
+ break;
+ case View.FOCUS_UP:
+ distance = y - touchableBounds.bottom + 1;
+ break;
+ case View.FOCUS_DOWN:
+ distance = touchableBounds.top;
+ break;
+ }
+
+ if (distance < edgeSlop) {
+ // Give preference to innermost views
+ if (closest == null ||
+ closestBounds.contains(touchableBounds) ||
+ (!touchableBounds.contains(closestBounds) && distance < minDistance)) {
+ minDistance = distance;
+ closest = touchable;
+ closestBounds.set(touchableBounds);
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ deltas[0] = -distance;
+ break;
+ case View.FOCUS_RIGHT:
+ deltas[0] = distance;
+ break;
+ case View.FOCUS_UP:
+ deltas[1] = -distance;
+ break;
+ case View.FOCUS_DOWN:
+ deltas[1] = distance;
+ break;
+ }
+ }
+ }
+ }
+ return closest;
+ }
+
+
+ /**
+ * Is destRect a candidate for the next touch given the direction?
+ */
+ private boolean isTouchCandidate(int x, int y, Rect destRect, int direction) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return destRect.left <= x && destRect.top <= y && y <= destRect.bottom;
+ case View.FOCUS_RIGHT:
+ return destRect.left >= x && destRect.top <= y && y <= destRect.bottom;
+ case View.FOCUS_UP:
+ return destRect.top <= y && destRect.left <= x && x <= destRect.right;
+ case View.FOCUS_DOWN:
+ return destRect.top >= y && destRect.left <= x && x <= destRect.right;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ private static final boolean isValidId(final int id) {
+ return id != 0 && id != View.NO_ID;
+ }
+
+ static final class FocusSorter {
+ private ArrayList<Rect> mRectPool = new ArrayList<>();
+ private int mLastPoolRect;
+ private int mRtlMult;
+ private HashMap<View, Rect> mRectByView = null;
+
+ private Comparator<View> mTopsComparator = (first, second) -> {
+ if (first == second) {
+ return 0;
+ }
+
+ Rect firstRect = mRectByView.get(first);
+ Rect secondRect = mRectByView.get(second);
+
+ int result = firstRect.top - secondRect.top;
+ if (result == 0) {
+ return firstRect.bottom - secondRect.bottom;
+ }
+ return result;
+ };
+
+ private Comparator<View> mSidesComparator = (first, second) -> {
+ if (first == second) {
+ return 0;
+ }
+
+ Rect firstRect = mRectByView.get(first);
+ Rect secondRect = mRectByView.get(second);
+
+ int result = firstRect.left - secondRect.left;
+ if (result == 0) {
+ return firstRect.right - secondRect.right;
+ }
+ return mRtlMult * result;
+ };
+
+ public void sort(View[] views, int start, int end, ViewGroup root, boolean isRtl) {
+ int count = end - start;
+ if (count < 2) {
+ return;
+ }
+ if (mRectByView == null) {
+ mRectByView = new HashMap<>();
+ }
+ mRtlMult = isRtl ? -1 : 1;
+ for (int i = mRectPool.size(); i < count; ++i) {
+ mRectPool.add(new Rect());
+ }
+ for (int i = start; i < end; ++i) {
+ Rect next = mRectPool.get(mLastPoolRect++);
+ views[i].getDrawingRect(next);
+ root.offsetDescendantRectToMyCoords(views[i], next);
+ mRectByView.put(views[i], next);
+ }
+
+ // Sort top-to-bottom
+ Arrays.sort(views, start, count, mTopsComparator);
+ // Sweep top-to-bottom to identify rows
+ int sweepBottom = mRectByView.get(views[start]).bottom;
+ int rowStart = start;
+ int sweepIdx = start + 1;
+ for (; sweepIdx < end; ++sweepIdx) {
+ Rect currRect = mRectByView.get(views[sweepIdx]);
+ if (currRect.top >= sweepBottom) {
+ // Next view is on a new row, sort the row we've just finished left-to-right.
+ if ((sweepIdx - rowStart) > 1) {
+ Arrays.sort(views, rowStart, sweepIdx, mSidesComparator);
+ }
+ sweepBottom = currRect.bottom;
+ rowStart = sweepIdx;
+ } else {
+ // Next view vertically overlaps, we need to extend our "row height"
+ sweepBottom = Math.max(sweepBottom, currRect.bottom);
+ }
+ }
+ // Sort whatever's left (final row) left-to-right
+ if ((sweepIdx - rowStart) > 1) {
+ Arrays.sort(views, rowStart, sweepIdx, mSidesComparator);
+ }
+
+ mLastPoolRect = 0;
+ mRectByView.clear();
+ }
+ }
+
+ /**
+ * Public for testing.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void sort(View[] views, int start, int end, ViewGroup root, boolean isRtl) {
+ getInstance().mFocusSorter.sort(views, start, end, root, isRtl);
+ }
+
+ /**
+ * Sorts views according to any explicitly-specified focus-chains. If there are no explicitly
+ * specified focus chains (eg. no nextFocusForward attributes defined), this should be a no-op.
+ */
+ private static final class UserSpecifiedFocusComparator implements Comparator<View> {
+ private final ArrayMap<View, View> mNextFoci = new ArrayMap<>();
+ private final ArraySet<View> mIsConnectedTo = new ArraySet<>();
+ private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>();
+ private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>();
+ private final NextFocusGetter mNextFocusGetter;
+ private View mRoot;
+
+ public interface NextFocusGetter {
+ View get(View root, View view);
+ }
+
+ UserSpecifiedFocusComparator(NextFocusGetter nextFocusGetter) {
+ mNextFocusGetter = nextFocusGetter;
+ }
+
+ public void recycle() {
+ mRoot = null;
+ mHeadsOfChains.clear();
+ mIsConnectedTo.clear();
+ mOriginalOrdinal.clear();
+ mNextFoci.clear();
+ }
+
+ public void setFocusables(List<View> focusables, View root) {
+ mRoot = root;
+ for (int i = 0; i < focusables.size(); ++i) {
+ mOriginalOrdinal.put(focusables.get(i), i);
+ }
+
+ for (int i = focusables.size() - 1; i >= 0; i--) {
+ final View view = focusables.get(i);
+ final View next = mNextFocusGetter.get(mRoot, view);
+ if (next != null && mOriginalOrdinal.containsKey(next)) {
+ mNextFoci.put(view, next);
+ mIsConnectedTo.add(next);
+ }
+ }
+
+ for (int i = focusables.size() - 1; i >= 0; i--) {
+ final View view = focusables.get(i);
+ final View next = mNextFoci.get(view);
+ if (next != null && !mIsConnectedTo.contains(view)) {
+ setHeadOfChain(view);
+ }
+ }
+ }
+
+ private void setHeadOfChain(View head) {
+ for (View view = head; view != null; view = mNextFoci.get(view)) {
+ final View otherHead = mHeadsOfChains.get(view);
+ if (otherHead != null) {
+ if (otherHead == head) {
+ return; // This view has already had its head set properly
+ }
+ // A hydra -- multi-headed focus chain (e.g. A->C and B->C)
+ // Use the one we've already chosen instead and reset this chain.
+ view = head;
+ head = otherHead;
+ }
+ mHeadsOfChains.put(view, head);
+ }
+ }
+
+ public int compare(View first, View second) {
+ if (first == second) {
+ return 0;
+ }
+ // Order between views within a chain is immaterial -- next/previous is
+ // within a chain is handled elsewhere.
+ View firstHead = mHeadsOfChains.get(first);
+ View secondHead = mHeadsOfChains.get(second);
+ if (firstHead == secondHead && firstHead != null) {
+ if (first == firstHead) {
+ return -1; // first is the head, it should be first
+ } else if (second == firstHead) {
+ return 1; // second is the head, it should be first
+ } else if (mNextFoci.get(first) != null) {
+ return -1; // first is not the end of the chain
+ } else {
+ return 1; // first is end of chain
+ }
+ }
+ boolean involvesChain = false;
+ if (firstHead != null) {
+ first = firstHead;
+ involvesChain = true;
+ }
+ if (secondHead != null) {
+ second = secondHead;
+ involvesChain = true;
+ }
+
+ if (involvesChain) {
+ // keep original order between chains
+ return mOriginalOrdinal.get(first) < mOriginalOrdinal.get(second) ? -1 : 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+}
diff --git a/android/view/FocusFinderHelper.java b/android/view/FocusFinderHelper.java
new file mode 100644
index 00000000..69dc0560
--- /dev/null
+++ b/android/view/FocusFinderHelper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008 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;
+
+/**
+ * A helper class that allows unit tests to access FocusFinder methods.
+ * @hide
+ */
+public class FocusFinderHelper {
+
+ private FocusFinder mFocusFinder;
+
+ /**
+ * Wrap the FocusFinder object
+ */
+ public FocusFinderHelper(FocusFinder focusFinder) {
+ mFocusFinder = focusFinder;
+ }
+
+ public boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
+ return mFocusFinder.isBetterCandidate(direction, source, rect1, rect2);
+ }
+
+ public boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
+ return mFocusFinder.beamBeats(direction, source, rect1, rect2);
+ }
+
+ public boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
+ return mFocusFinder.isCandidate(srcRect, destRect, direction);
+ }
+
+ public boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
+ return mFocusFinder.beamsOverlap(direction, rect1, rect2);
+ }
+
+ public static int majorAxisDistance(int direction, Rect source, Rect dest) {
+ return FocusFinder.majorAxisDistance(direction, source, dest);
+ }
+
+ public static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
+ return FocusFinder.majorAxisDistanceToFarEdge(direction, source, dest);
+ }
+}
diff --git a/android/view/FrameInfo.java b/android/view/FrameInfo.java
new file mode 100644
index 00000000..c79547c8
--- /dev/null
+++ b/android/view/FrameInfo.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that contains all the timing information for the current frame. This
+ * is used in conjunction with the hardware renderer to provide
+ * continous-monitoring jank events
+ *
+ * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
+ *
+ * To minimize overhead from System.nanoTime() calls we infer durations of
+ * things by knowing the ordering of the events. For example, to know how
+ * long layout & measure took it's displayListRecordStart - performTraversalsStart.
+ *
+ * These constants must be kept in sync with FrameInfo.h in libhwui and are
+ * used for indexing into AttachInfo's mFrameInfo long[], which is intended
+ * to be quick to pass down to native via JNI, hence a pre-packed format
+ *
+ * @hide
+ */
+final class FrameInfo {
+
+ long[] mFrameInfo = new long[9];
+
+ // Various flags set to provide extra metadata about the current frame
+ private static final int FLAGS = 0;
+
+ // Is this the first-draw following a window layout?
+ public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
+
+ @IntDef(flag = true, value = {
+ FLAG_WINDOW_LAYOUT_CHANGED })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrameInfoFlags {}
+
+ // The intended vsync time, unadjusted by jitter
+ private static final int INTENDED_VSYNC = 1;
+
+ // Jitter-adjusted vsync time, this is what was used as input into the
+ // animation & drawing system
+ private static final int VSYNC = 2;
+
+ // The time of the oldest input event
+ private static final int OLDEST_INPUT_EVENT = 3;
+
+ // The time of the newest input event
+ private static final int NEWEST_INPUT_EVENT = 4;
+
+ // When input event handling started
+ private static final int HANDLE_INPUT_START = 5;
+
+ // When animation evaluations started
+ private static final int ANIMATION_START = 6;
+
+ // When ViewRootImpl#performTraversals() started
+ private static final int PERFORM_TRAVERSALS_START = 7;
+
+ // When View:draw() started
+ private static final int DRAW_START = 8;
+
+ public void setVsync(long intendedVsync, long usedVsync) {
+ mFrameInfo[INTENDED_VSYNC] = intendedVsync;
+ mFrameInfo[VSYNC] = usedVsync;
+ mFrameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
+ mFrameInfo[NEWEST_INPUT_EVENT] = 0;
+ mFrameInfo[FLAGS] = 0;
+ }
+
+ public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) {
+ if (inputEventOldestTime < mFrameInfo[OLDEST_INPUT_EVENT]) {
+ mFrameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime;
+ }
+ if (inputEventTime > mFrameInfo[NEWEST_INPUT_EVENT]) {
+ mFrameInfo[NEWEST_INPUT_EVENT] = inputEventTime;
+ }
+ }
+
+ public void markInputHandlingStart() {
+ mFrameInfo[HANDLE_INPUT_START] = System.nanoTime();
+ }
+
+ public void markAnimationsStart() {
+ mFrameInfo[ANIMATION_START] = System.nanoTime();
+ }
+
+ public void markPerformTraversalsStart() {
+ mFrameInfo[PERFORM_TRAVERSALS_START] = System.nanoTime();
+ }
+
+ public void markDrawStart() {
+ mFrameInfo[DRAW_START] = System.nanoTime();
+ }
+
+ public void addFlags(@FrameInfoFlags long flags) {
+ mFrameInfo[FLAGS] |= flags;
+ }
+
+}
diff --git a/android/view/FrameMetrics.java b/android/view/FrameMetrics.java
new file mode 100644
index 00000000..358a2d1e
--- /dev/null
+++ b/android/view/FrameMetrics.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2016 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.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class containing timing data for various milestones in a frame
+ * lifecycle reported by the rendering subsystem.
+ * <p>
+ * Supported metrics can be queried via their corresponding identifier.
+ * </p>
+ */
+public final class FrameMetrics {
+
+ /**
+ * Metric identifier for unknown delay.
+ * <p>
+ * Represents the number of nanoseconds elapsed waiting for the
+ * UI thread to become responsive and process the frame. This
+ * should be 0 most of the time.
+ * </p>
+ */
+ public static final int UNKNOWN_DELAY_DURATION = 0;
+
+ /**
+ * Metric identifier for input handling duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed issuing
+ * input handling callbacks.
+ * </p>
+ */
+ public static final int INPUT_HANDLING_DURATION = 1;
+
+ /**
+ * Metric identifier for animation callback duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed issuing
+ * animation callbacks.
+ * </p>
+ */
+ public static final int ANIMATION_DURATION = 2;
+
+ /**
+ * Metric identifier for layout/measure duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed measuring
+ * and laying out the invalidated pieces of the view hierarchy.
+ * </p>
+ */
+ public static final int LAYOUT_MEASURE_DURATION = 3;
+ /**
+ * Metric identifier for draw duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed computing
+ * DisplayLists for transformations applied to the view
+ * hierarchy.
+ * </p>
+ */
+ public static final int DRAW_DURATION = 4;
+
+ /**
+ * Metric identifier for sync duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed
+ * synchronizing the computed display lists with the render
+ * thread.
+ * </p>
+ */
+ public static final int SYNC_DURATION = 5;
+
+ /**
+ * Metric identifier for command issue duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed
+ * issuing draw commands to the GPU.
+ * </p>
+ */
+ public static final int COMMAND_ISSUE_DURATION = 6;
+
+ /**
+ * Metric identifier for swap buffers duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed issuing
+ * the frame buffer for this frame to the display
+ * subsystem.
+ * </p>
+ */
+ public static final int SWAP_BUFFERS_DURATION = 7;
+
+ /**
+ * Metric identifier for total frame duration.
+ * <p>
+ * Represents the total time in nanoseconds this frame took to render
+ * and be issued to the display subsystem.
+ * </p>
+ * <p>
+ * Equal to the sum of the values of all other time-valued metric
+ * identifiers.
+ * </p>
+ */
+ public static final int TOTAL_DURATION = 8;
+
+ /**
+ * Metric identifier for a boolean value determining whether this frame was
+ * the first to draw in a new Window layout.
+ * <p>
+ * {@link #getMetric(int)} will return 0 for false, 1 for true.
+ * </p>
+ * <p>
+ * First draw frames are expected to be slow and should usually be exempt
+ * from display jank calculations as they do not cause skips in animations
+ * and are usually hidden by window animations or other tricks.
+ * </p>
+ */
+ public static final int FIRST_DRAW_FRAME = 9;
+
+ /**
+ * Metric identifier for the timestamp of the intended vsync for this frame.
+ * <p>
+ * The intended start point for the frame. If this value is different from
+ * {@link #VSYNC_TIMESTAMP}, there was work occurring on the UI thread that
+ * prevented it from responding to the vsync signal in a timely fashion.
+ * </p>
+ */
+ public static final int INTENDED_VSYNC_TIMESTAMP = 10;
+
+ /**
+ * Metric identifier for the timestamp of the actual vsync for this frame.
+ * <p>
+ * The time value that was used in all the vsync listeners and drawing for
+ * the frame (Choreographer frame callbacks, animations,
+ * {@link View#getDrawingTime()}, etc…)
+ * </p>
+ */
+ public static final int VSYNC_TIMESTAMP = 11;
+
+ private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
+
+ /**
+ * Identifiers for metrics available for each frame.
+ *
+ * {@see #getMetric(int)}
+ * @hide
+ */
+ @IntDef({
+ UNKNOWN_DELAY_DURATION,
+ INPUT_HANDLING_DURATION,
+ ANIMATION_DURATION,
+ LAYOUT_MEASURE_DURATION,
+ DRAW_DURATION,
+ SYNC_DURATION,
+ COMMAND_ISSUE_DURATION,
+ SWAP_BUFFERS_DURATION,
+ TOTAL_DURATION,
+ FIRST_DRAW_FRAME,
+ INTENDED_VSYNC_TIMESTAMP,
+ VSYNC_TIMESTAMP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Metric {}
+
+ /**
+ * Timestamp indices for frame milestones.
+ *
+ * May change from release to release.
+ *
+ * Must be kept in sync with frameworks/base/libs/hwui/FrameInfo.h.
+ *
+ * @hide
+ */
+ @IntDef ({
+ Index.FLAGS,
+ Index.INTENDED_VSYNC,
+ Index.VSYNC,
+ Index.OLDEST_INPUT_EVENT,
+ Index.NEWEST_INPUT_EVENT,
+ Index.HANDLE_INPUT_START,
+ Index.ANIMATION_START,
+ Index.PERFORM_TRAVERSALS_START,
+ Index.DRAW_START,
+ Index.SYNC_QUEUED,
+ Index.SYNC_START,
+ Index.ISSUE_DRAW_COMMANDS_START,
+ Index.SWAP_BUFFERS,
+ Index.FRAME_COMPLETED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Index {
+ int FLAGS = 0;
+ int INTENDED_VSYNC = 1;
+ int VSYNC = 2;
+ int OLDEST_INPUT_EVENT = 3;
+ int NEWEST_INPUT_EVENT = 4;
+ int HANDLE_INPUT_START = 5;
+ int ANIMATION_START = 6;
+ int PERFORM_TRAVERSALS_START = 7;
+ int DRAW_START = 8;
+ int SYNC_QUEUED = 9;
+ int SYNC_START = 10;
+ int ISSUE_DRAW_COMMANDS_START = 11;
+ int SWAP_BUFFERS = 12;
+ int FRAME_COMPLETED = 13;
+
+ int FRAME_STATS_COUNT = 16; // must always be last
+ }
+
+ /*
+ * Bucket endpoints for each Metric defined above.
+ *
+ * Each defined metric *must* have a corresponding entry
+ * in this list.
+ */
+ private static final int[] DURATIONS = new int[] {
+ // UNKNOWN_DELAY
+ Index.INTENDED_VSYNC, Index.HANDLE_INPUT_START,
+ // INPUT_HANDLING
+ Index.HANDLE_INPUT_START, Index.ANIMATION_START,
+ // ANIMATION
+ Index.ANIMATION_START, Index.PERFORM_TRAVERSALS_START,
+ // LAYOUT_MEASURE
+ Index.PERFORM_TRAVERSALS_START, Index.DRAW_START,
+ // DRAW
+ Index.DRAW_START, Index.SYNC_QUEUED,
+ // SYNC
+ Index.SYNC_START, Index.ISSUE_DRAW_COMMANDS_START,
+ // COMMAND_ISSUE
+ Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS,
+ // SWAP_BUFFERS
+ Index.SWAP_BUFFERS, Index.FRAME_COMPLETED,
+ // TOTAL_DURATION
+ Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
+ };
+
+ /* package */ final long[] mTimingData;
+
+ /**
+ * Constructs a FrameMetrics object as a copy.
+ * <p>
+ * Use this method to copy out metrics reported by
+ * {@link Window.OnFrameMetricsAvailableListener#onFrameMetricsAvailable(
+ * Window, FrameMetrics, int)}
+ * </p>
+ * @param other the FrameMetrics object to copy.
+ */
+ public FrameMetrics(FrameMetrics other) {
+ mTimingData = new long[Index.FRAME_STATS_COUNT];
+ System.arraycopy(other.mTimingData, 0, mTimingData, 0, mTimingData.length);
+ }
+
+ /**
+ * @hide
+ */
+ FrameMetrics() {
+ mTimingData = new long[Index.FRAME_STATS_COUNT];
+ }
+
+ /**
+ * Retrieves the value associated with Metric identifier {@code id}
+ * for this frame.
+ * <p>
+ * Boolean metrics are represented in [0,1], with 0 corresponding to
+ * false, and 1 corresponding to true.
+ * </p>
+ * @param id the metric to retrieve
+ * @return the value of the metric or -1 if it is not available.
+ */
+ public long getMetric(@Metric int id) {
+ if (id < UNKNOWN_DELAY_DURATION || id > VSYNC_TIMESTAMP) {
+ return -1;
+ }
+
+ if (mTimingData == null) {
+ return -1;
+ }
+
+ if (id == FIRST_DRAW_FRAME) {
+ return (mTimingData[Index.FLAGS] & FRAME_INFO_FLAG_FIRST_DRAW) != 0 ? 1 : 0;
+ } else if (id == INTENDED_VSYNC_TIMESTAMP) {
+ return mTimingData[Index.INTENDED_VSYNC];
+ } else if (id == VSYNC_TIMESTAMP) {
+ return mTimingData[Index.VSYNC];
+ }
+
+ int durationsIdx = 2 * id;
+ return mTimingData[DURATIONS[durationsIdx + 1]]
+ - mTimingData[DURATIONS[durationsIdx]];
+ }
+}
+
diff --git a/android/view/FrameMetricsObserver.java b/android/view/FrameMetricsObserver.java
new file mode 100644
index 00000000..9e81de0e
--- /dev/null
+++ b/android/view/FrameMetricsObserver.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.os.Looper;
+import android.os.MessageQueue;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides streaming access to frame stats information from the rendering
+ * subsystem to apps.
+ *
+ * @hide
+ */
+public class FrameMetricsObserver {
+ private MessageQueue mMessageQueue;
+
+ private WeakReference<Window> mWindow;
+
+ private FrameMetrics mFrameMetrics;
+
+ /* package */ Window.OnFrameMetricsAvailableListener mListener;
+ /* package */ VirtualRefBasePtr mNative;
+
+ /**
+ * Creates a FrameMetricsObserver
+ *
+ * @param looper the looper to use when invoking callbacks
+ */
+ FrameMetricsObserver(@NonNull Window window, @NonNull Looper looper,
+ @NonNull Window.OnFrameMetricsAvailableListener listener) {
+ if (looper == null) {
+ throw new NullPointerException("looper cannot be null");
+ }
+
+ mMessageQueue = looper.getQueue();
+ if (mMessageQueue == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameMetrics = new FrameMetrics();
+ mWindow = new WeakReference<>(window);
+ mListener = listener;
+ }
+
+ // Called by native on the provided Handler
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable(int dropCount) {
+ final Window window = mWindow.get();
+ if (window != null) {
+ mListener.onFrameMetricsAvailable(window, mFrameMetrics, dropCount);
+ }
+ }
+}
diff --git a/android/view/FrameStats.java b/android/view/FrameStats.java
new file mode 100644
index 00000000..3fbe6fe8
--- /dev/null
+++ b/android/view/FrameStats.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * This is the base class for frame statistics.
+ */
+public abstract class FrameStats {
+ /**
+ * Undefined time.
+ */
+ public static final long UNDEFINED_TIME_NANO = -1;
+
+ /** @hide */
+ protected long mRefreshPeriodNano;
+
+ /** @hide */
+ protected long[] mFramesPresentedTimeNano;
+
+ /**
+ * Gets the refresh period of the display hosting the window(s) for
+ * which these statistics apply.
+ *
+ * @return The refresh period in nanoseconds.
+ */
+ public final long getRefreshPeriodNano() {
+ return mRefreshPeriodNano;
+ }
+
+ /**
+ * Gets the number of frames for which there is data.
+ *
+ * @return The number of frames.
+ */
+ public final int getFrameCount() {
+ return mFramesPresentedTimeNano != null
+ ? mFramesPresentedTimeNano.length : 0;
+ }
+
+ /**
+ * Gets the start time of the interval for which these statistics
+ * apply. The start interval is the time when the first frame was
+ * presented.
+ *
+ * @return The start time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if there is no frame data.
+ */
+ public final long getStartTimeNano() {
+ if (getFrameCount() <= 0) {
+ return UNDEFINED_TIME_NANO;
+ }
+ return mFramesPresentedTimeNano[0];
+ }
+
+ /**
+ * Gets the end time of the interval for which these statistics
+ * apply. The end interval is the time when the last frame was
+ * presented.
+ *
+ * @return The end time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if there is no frame data.
+ */
+ public final long getEndTimeNano() {
+ if (getFrameCount() <= 0) {
+ return UNDEFINED_TIME_NANO;
+ }
+ return mFramesPresentedTimeNano[mFramesPresentedTimeNano.length - 1];
+ }
+
+ /**
+ * Get the time a frame at a given index was presented.
+ *
+ * @param index The frame index.
+ * @return The presented time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if the frame is not presented yet.
+ */
+ public final long getFramePresentedTimeNano(int index) {
+ if (mFramesPresentedTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesPresentedTimeNano[index];
+ }
+}
diff --git a/android/view/GestureDetector.java b/android/view/GestureDetector.java
new file mode 100644
index 00000000..52e53b07
--- /dev/null
+++ b/android/view/GestureDetector.java
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2008 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.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+/**
+ * Detects various gestures and events using the supplied {@link MotionEvent}s.
+ * The {@link OnGestureListener} callback will notify users when a particular
+ * motion event has occurred. This class should only be used with {@link MotionEvent}s
+ * reported via touch (don't use for trackball events).
+ *
+ * To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code GestureDetector} for your {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
+ * will be executed when the events occur.
+ * <li>If listening for {@link OnContextClickListener#onContextClick(MotionEvent)}
+ * you must call {@link #onGenericMotionEvent(MotionEvent)}
+ * in {@link View#onGenericMotionEvent(MotionEvent)}.
+ * </ul>
+ */
+public class GestureDetector {
+ /**
+ * The listener that is used to notify when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnGestureListener}.
+ */
+ public interface OnGestureListener {
+
+ /**
+ * Notified when a tap occurs with the down {@link MotionEvent}
+ * that triggered it. This will be triggered immediately for
+ * every down event. All other events should be preceded by this.
+ *
+ * @param e The down motion event.
+ */
+ boolean onDown(MotionEvent e);
+
+ /**
+ * The user has performed a down {@link MotionEvent} and not performed
+ * a move or up yet. This event is commonly used to provide visual
+ * feedback to the user to let them know that their action has been
+ * recognized i.e. highlight an element.
+ *
+ * @param e The down motion event
+ */
+ void onShowPress(MotionEvent e);
+
+ /**
+ * Notified when a tap occurs with the up {@link MotionEvent}
+ * that triggered it.
+ *
+ * @param e The up motion event that completed the first tap
+ * @return true if the event is consumed, else false
+ */
+ boolean onSingleTapUp(MotionEvent e);
+
+ /**
+ * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
+ * current move {@link MotionEvent}. The distance in x and y is also supplied for
+ * convenience.
+ *
+ * @param e1 The first down motion event that started the scrolling.
+ * @param e2 The move motion event that triggered the current onScroll.
+ * @param distanceX The distance along the X axis that has been scrolled since the last
+ * call to onScroll. This is NOT the distance between {@code e1}
+ * and {@code e2}.
+ * @param distanceY The distance along the Y axis that has been scrolled since the last
+ * call to onScroll. This is NOT the distance between {@code e1}
+ * and {@code e2}.
+ * @return true if the event is consumed, else false
+ */
+ boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
+
+ /**
+ * Notified when a long press occurs with the initial on down {@link MotionEvent}
+ * that trigged it.
+ *
+ * @param e The initial on down motion event that started the longpress.
+ */
+ void onLongPress(MotionEvent e);
+
+ /**
+ * Notified of a fling event when it occurs with the initial on down {@link MotionEvent}
+ * and the matching up {@link MotionEvent}. The calculated velocity is supplied along
+ * the x and y axis in pixels per second.
+ *
+ * @param e1 The first down motion event that started the fling.
+ * @param e2 The move motion event that triggered the current onFling.
+ * @param velocityX The velocity of this fling measured in pixels per second
+ * along the x axis.
+ * @param velocityY The velocity of this fling measured in pixels per second
+ * along the y axis.
+ * @return true if the event is consumed, else false
+ */
+ boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
+ }
+
+ /**
+ * The listener that is used to notify when a double-tap or a confirmed
+ * single-tap occur.
+ */
+ public interface OnDoubleTapListener {
+ /**
+ * Notified when a single-tap occurs.
+ * <p>
+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
+ * will only be called after the detector is confident that the user's
+ * first tap is not followed by a second tap leading to a double-tap
+ * gesture.
+ *
+ * @param e The down motion event of the single-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onSingleTapConfirmed(MotionEvent e);
+
+ /**
+ * Notified when a double-tap occurs.
+ *
+ * @param e The down motion event of the first tap of the double-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap(MotionEvent e);
+
+ /**
+ * Notified when an event within a double-tap gesture occurs, including
+ * the down, move, and up events.
+ *
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTapEvent(MotionEvent e);
+ }
+
+ /**
+ * The listener that is used to notify when a context click occurs. When listening for a
+ * context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in
+ * {@link View#onGenericMotionEvent(MotionEvent)}.
+ */
+ public interface OnContextClickListener {
+ /**
+ * Notified when a context click occurs.
+ *
+ * @param e The motion event that occurred during the context click.
+ * @return true if the event is consumed, else false
+ */
+ boolean onContextClick(MotionEvent e);
+ }
+
+ /**
+ * A convenience class to extend when you only want to listen for a subset
+ * of all the gestures. This implements all methods in the
+ * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
+ * but does nothing and return {@code false} for all applicable methods.
+ */
+ public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
+ OnContextClickListener {
+
+ public boolean onSingleTapUp(MotionEvent e) {
+ return false;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ }
+
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ return false;
+ }
+
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return false;
+ }
+
+ public void onShowPress(MotionEvent e) {
+ }
+
+ public boolean onDown(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onDoubleTap(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onContextClick(MotionEvent e) {
+ return false;
+ }
+ }
+
+ private int mTouchSlopSquare;
+ private int mDoubleTapTouchSlopSquare;
+ private int mDoubleTapSlopSquare;
+ private int mMinimumFlingVelocity;
+ private int mMaximumFlingVelocity;
+
+ private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+ private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+ private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+ private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
+
+ // constants for Message.what used by GestureHandler below
+ private static final int SHOW_PRESS = 1;
+ private static final int LONG_PRESS = 2;
+ private static final int TAP = 3;
+
+ private final Handler mHandler;
+ private final OnGestureListener mListener;
+ private OnDoubleTapListener mDoubleTapListener;
+ private OnContextClickListener mContextClickListener;
+
+ private boolean mStillDown;
+ private boolean mDeferConfirmSingleTap;
+ private boolean mInLongPress;
+ private boolean mInContextClick;
+ private boolean mAlwaysInTapRegion;
+ private boolean mAlwaysInBiggerTapRegion;
+ private boolean mIgnoreNextUpEvent;
+
+ private MotionEvent mCurrentDownEvent;
+ private MotionEvent mPreviousUpEvent;
+
+ /**
+ * True when the user is still touching for the second tap (down, move, and
+ * up events). Can only be true if there is a double tap listener attached.
+ */
+ private boolean mIsDoubleTapping;
+
+ private float mLastFocusX;
+ private float mLastFocusY;
+ private float mDownFocusX;
+ private float mDownFocusY;
+
+ private boolean mIsLongpressEnabled;
+
+ /**
+ * Determines speed during touch scrolling
+ */
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
+ private class GestureHandler extends Handler {
+ GestureHandler() {
+ super();
+ }
+
+ GestureHandler(Handler handler) {
+ super(handler.getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW_PRESS:
+ mListener.onShowPress(mCurrentDownEvent);
+ break;
+
+ case LONG_PRESS:
+ dispatchLongPress();
+ break;
+
+ case TAP:
+ // If the user's finger is still down, do not count it as a tap
+ if (mDoubleTapListener != null) {
+ if (!mStillDown) {
+ mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ } else {
+ mDeferConfirmSingleTap = true;
+ }
+ }
+ break;
+
+ default:
+ throw new RuntimeException("Unknown message " + msg); //never
+ }
+ }
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * This variant of the constructor should be used from a non-UI thread
+ * (as it allows specifying the Handler).
+ *
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use
+ *
+ * @throws NullPointerException if either {@code listener} or
+ * {@code handler} is null.
+ *
+ * @deprecated Use {@link #GestureDetector(android.content.Context,
+ * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
+ */
+ @Deprecated
+ public GestureDetector(OnGestureListener listener, Handler handler) {
+ this(null, listener, handler);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ *
+ * @deprecated Use {@link #GestureDetector(android.content.Context,
+ * android.view.GestureDetector.OnGestureListener)} instead.
+ */
+ @Deprecated
+ public GestureDetector(OnGestureListener listener) {
+ this(null, listener, null);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a {@link android.os.Looper} thread.
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener) {
+ this(context, listener, null);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener that runs deferred events on the
+ * thread associated with the supplied {@link android.os.Handler}.
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use for running deferred listener events.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
+ if (handler != null) {
+ mHandler = new GestureHandler(handler);
+ } else {
+ mHandler = new GestureHandler();
+ }
+ mListener = listener;
+ if (listener instanceof OnDoubleTapListener) {
+ setOnDoubleTapListener((OnDoubleTapListener) listener);
+ }
+ if (listener instanceof OnContextClickListener) {
+ setContextClickListener((OnContextClickListener) listener);
+ }
+ init(context);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener that runs deferred events on the
+ * thread associated with the supplied {@link android.os.Handler}.
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use for running deferred listener events.
+ * @param unused currently not used.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener, Handler handler,
+ boolean unused) {
+ this(context, listener, handler);
+ }
+
+ private void init(Context context) {
+ if (mListener == null) {
+ throw new NullPointerException("OnGestureListener must not be null");
+ }
+ mIsLongpressEnabled = true;
+
+ // Fallback to support pre-donuts releases
+ int touchSlop, doubleTapSlop, doubleTapTouchSlop;
+ if (context == null) {
+ //noinspection deprecation
+ touchSlop = ViewConfiguration.getTouchSlop();
+ doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
+ doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
+ //noinspection deprecation
+ mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
+ mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
+ } else {
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ touchSlop = configuration.getScaledTouchSlop();
+ doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
+ doubleTapSlop = configuration.getScaledDoubleTapSlop();
+ mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
+ }
+ mTouchSlopSquare = touchSlop * touchSlop;
+ mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
+ mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
+ }
+
+ /**
+ * Sets the listener which will be called for double-tap and related
+ * gestures.
+ *
+ * @param onDoubleTapListener the listener invoked for all the callbacks, or
+ * null to stop listening for double-tap gestures.
+ */
+ public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
+ mDoubleTapListener = onDoubleTapListener;
+ }
+
+ /**
+ * Sets the listener which will be called for context clicks.
+ *
+ * @param onContextClickListener the listener invoked for all the callbacks, or null to stop
+ * listening for context clicks.
+ */
+ public void setContextClickListener(OnContextClickListener onContextClickListener) {
+ mContextClickListener = onContextClickListener;
+ }
+
+ /**
+ * Set whether longpress is enabled, if this is enabled when a user
+ * presses and holds down you get a longpress event and nothing further.
+ * If it's disabled the user can press and hold down and then later
+ * moved their finger and you will get scroll events. By default
+ * longpress is enabled.
+ *
+ * @param isLongpressEnabled whether longpress should be enabled.
+ */
+ public void setIsLongpressEnabled(boolean isLongpressEnabled) {
+ mIsLongpressEnabled = isLongpressEnabled;
+ }
+
+ /**
+ * @return true if longpress is enabled, else false.
+ */
+ public boolean isLongpressEnabled() {
+ return mIsLongpressEnabled;
+ }
+
+ /**
+ * Analyzes the given motion event and if applicable triggers the
+ * appropriate callbacks on the {@link OnGestureListener} supplied.
+ *
+ * @param ev The current motion event.
+ * @return true if the {@link OnGestureListener} consumed the event,
+ * else false.
+ */
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
+ }
+
+ final int action = ev.getAction();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ final boolean pointerUp =
+ (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
+ final boolean isGeneratedGesture =
+ (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = ev.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += ev.getX(i);
+ sumY += ev.getY(i);
+ }
+ final int div = pointerUp ? count - 1 : count;
+ final float focusX = sumX / div;
+ final float focusY = sumY / div;
+
+ boolean handled = false;
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
+ // Cancel long press and taps
+ cancelTaps();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
+
+ // Check the dot product of current velocities.
+ // If the pointer that left was opposing another velocity vector, clear.
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
+ final int upIndex = ev.getActionIndex();
+ final int id1 = ev.getPointerId(upIndex);
+ final float x1 = mVelocityTracker.getXVelocity(id1);
+ final float y1 = mVelocityTracker.getYVelocity(id1);
+ for (int i = 0; i < count; i++) {
+ if (i == upIndex) continue;
+
+ final int id2 = ev.getPointerId(i);
+ final float x = x1 * mVelocityTracker.getXVelocity(id2);
+ final float y = y1 * mVelocityTracker.getYVelocity(id2);
+
+ final float dot = x + y;
+ if (dot < 0) {
+ mVelocityTracker.clear();
+ break;
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ if (mDoubleTapListener != null) {
+ boolean hadTapMessage = mHandler.hasMessages(TAP);
+ if (hadTapMessage) mHandler.removeMessages(TAP);
+ if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
+ isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
+ // This is a second tap
+ mIsDoubleTapping = true;
+ // Give a callback with the first tap of the double-tap
+ handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
+ // Give a callback with down event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else {
+ // This is a first tap
+ mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
+ }
+ }
+
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
+ if (mCurrentDownEvent != null) {
+ mCurrentDownEvent.recycle();
+ }
+ mCurrentDownEvent = MotionEvent.obtain(ev);
+ mAlwaysInTapRegion = true;
+ mAlwaysInBiggerTapRegion = true;
+ mStillDown = true;
+ mInLongPress = false;
+ mDeferConfirmSingleTap = false;
+
+ if (mIsLongpressEnabled) {
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.sendEmptyMessageAtTime(LONG_PRESS,
+ mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
+ }
+ mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
+ mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
+ handled |= mListener.onDown(ev);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mInLongPress || mInContextClick) {
+ break;
+ }
+ final float scrollX = mLastFocusX - focusX;
+ final float scrollY = mLastFocusY - focusY;
+ if (mIsDoubleTapping) {
+ // Give the move events of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else if (mAlwaysInTapRegion) {
+ final int deltaX = (int) (focusX - mDownFocusX);
+ final int deltaY = (int) (focusY - mDownFocusY);
+ int distance = (deltaX * deltaX) + (deltaY * deltaY);
+ int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
+ if (distance > slopSquare) {
+ handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+ mLastFocusX = focusX;
+ mLastFocusY = focusY;
+ mAlwaysInTapRegion = false;
+ mHandler.removeMessages(TAP);
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ }
+ int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
+ if (distance > doubleTapSlopSquare) {
+ mAlwaysInBiggerTapRegion = false;
+ }
+ } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
+ handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+ mLastFocusX = focusX;
+ mLastFocusY = focusY;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ mStillDown = false;
+ MotionEvent currentUpEvent = MotionEvent.obtain(ev);
+ if (mIsDoubleTapping) {
+ // Finally, give the up event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else if (mInLongPress) {
+ mHandler.removeMessages(TAP);
+ mInLongPress = false;
+ } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
+ handled = mListener.onSingleTapUp(ev);
+ if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
+ mDoubleTapListener.onSingleTapConfirmed(ev);
+ }
+ } else if (!mIgnoreNextUpEvent) {
+
+ // A fling must travel the minimum tap distance
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ final int pointerId = ev.getPointerId(0);
+ velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
+ final float velocityY = velocityTracker.getYVelocity(pointerId);
+ final float velocityX = velocityTracker.getXVelocity(pointerId);
+
+ if ((Math.abs(velocityY) > mMinimumFlingVelocity)
+ || (Math.abs(velocityX) > mMinimumFlingVelocity)){
+ handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
+ }
+ }
+ if (mPreviousUpEvent != null) {
+ mPreviousUpEvent.recycle();
+ }
+ // Hold the event we obtained above - listeners may have changed the original.
+ mPreviousUpEvent = currentUpEvent;
+ if (mVelocityTracker != null) {
+ // This may have been cleared when we called out to the
+ // application above.
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ mIsDoubleTapping = false;
+ mDeferConfirmSingleTap = false;
+ mIgnoreNextUpEvent = false;
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ cancel();
+ break;
+ }
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
+ }
+ return handled;
+ }
+
+ /**
+ * Analyzes the given generic motion event and if applicable triggers the
+ * appropriate callbacks on the {@link OnGestureListener} supplied.
+ *
+ * @param ev The current motion event.
+ * @return true if the {@link OnGestureListener} consumed the event,
+ * else false.
+ */
+ public boolean onGenericMotionEvent(MotionEvent ev) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
+ }
+
+ final int actionButton = ev.getActionButton();
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_BUTTON_PRESS:
+ if (mContextClickListener != null && !mInContextClick && !mInLongPress
+ && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+ || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+ if (mContextClickListener.onContextClick(ev)) {
+ mInContextClick = true;
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ return true;
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_BUTTON_RELEASE:
+ if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+ || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+ mInContextClick = false;
+ mIgnoreNextUpEvent = true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ private void cancel() {
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ mIsDoubleTapping = false;
+ mStillDown = false;
+ mAlwaysInTapRegion = false;
+ mAlwaysInBiggerTapRegion = false;
+ mDeferConfirmSingleTap = false;
+ mInLongPress = false;
+ mInContextClick = false;
+ mIgnoreNextUpEvent = false;
+ }
+
+ private void cancelTaps() {
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ mIsDoubleTapping = false;
+ mAlwaysInTapRegion = false;
+ mAlwaysInBiggerTapRegion = false;
+ mDeferConfirmSingleTap = false;
+ mInLongPress = false;
+ mInContextClick = false;
+ mIgnoreNextUpEvent = false;
+ }
+
+ private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
+ MotionEvent secondDown) {
+ if (!mAlwaysInBiggerTapRegion) {
+ return false;
+ }
+
+ final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
+ if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
+ return false;
+ }
+
+ int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
+ int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
+ final boolean isGeneratedGesture =
+ (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
+ int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
+ return (deltaX * deltaX + deltaY * deltaY < slopSquare);
+ }
+
+ private void dispatchLongPress() {
+ mHandler.removeMessages(TAP);
+ mDeferConfirmSingleTap = false;
+ mInLongPress = true;
+ mListener.onLongPress(mCurrentDownEvent);
+ }
+}
diff --git a/android/view/GhostView.java b/android/view/GhostView.java
new file mode 100644
index 00000000..d1b96baa
--- /dev/null
+++ b/android/view/GhostView.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2014 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.Canvas;
+import android.graphics.Matrix;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+/**
+ * This view draws another View in an Overlay without changing the parent. It will not be drawn
+ * by its parent because its visibility is set to INVISIBLE, but will be drawn
+ * here using its render node. When the GhostView is set to INVISIBLE, the View it is
+ * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
+ * view becomes INVISIBLE.
+ * @hide
+ */
+public class GhostView extends View {
+ private final View mView;
+ private int mReferences;
+ private boolean mBeingMoved;
+
+ private GhostView(View view) {
+ super(view.getContext());
+ mView = view;
+ mView.mGhostView = this;
+ final ViewGroup parent = (ViewGroup) mView.getParent();
+ mView.setTransitionVisibility(View.INVISIBLE);
+ parent.invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (canvas instanceof DisplayListCanvas) {
+ DisplayListCanvas dlCanvas = (DisplayListCanvas) canvas;
+ mView.mRecreateDisplayList = true;
+ RenderNode renderNode = mView.updateDisplayListIfDirty();
+ if (renderNode.isValid()) {
+ dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
+ dlCanvas.drawRenderNode(renderNode);
+ dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
+ }
+ }
+ }
+
+ public void setMatrix(Matrix matrix) {
+ mRenderNode.setAnimationMatrix(matrix);
+ }
+
+ @Override
+ public void setVisibility(@Visibility int visibility) {
+ super.setVisibility(visibility);
+ if (mView.mGhostView == this) {
+ int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
+ mView.setTransitionVisibility(inverseVisibility);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (!mBeingMoved) {
+ mView.setTransitionVisibility(View.VISIBLE);
+ mView.mGhostView = null;
+ final ViewGroup parent = (ViewGroup) mView.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ }
+ }
+ }
+
+ public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
+ ViewGroup parent = (ViewGroup) view.getParent();
+ matrix.reset();
+ parent.transformMatrixToGlobal(matrix);
+ matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
+ host.transformMatrixToLocal(matrix);
+ }
+
+ public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
+ if (!(view.getParent() instanceof ViewGroup)) {
+ throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
+ }
+ ViewGroupOverlay overlay = viewGroup.getOverlay();
+ ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
+ GhostView ghostView = view.mGhostView;
+ int previousRefCount = 0;
+ if (ghostView != null) {
+ View oldParent = (View) ghostView.getParent();
+ ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
+ if (oldGrandParent != overlayViewGroup) {
+ previousRefCount = ghostView.mReferences;
+ oldGrandParent.removeView(oldParent);
+ ghostView = null;
+ }
+ }
+ if (ghostView == null) {
+ if (matrix == null) {
+ matrix = new Matrix();
+ calculateMatrix(view, viewGroup, matrix);
+ }
+ ghostView = new GhostView(view);
+ ghostView.setMatrix(matrix);
+ FrameLayout parent = new FrameLayout(view.getContext());
+ parent.setClipChildren(false);
+ copySize(viewGroup, parent);
+ copySize(viewGroup, ghostView);
+ parent.addView(ghostView);
+ ArrayList<View> tempViews = new ArrayList<View>();
+ int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
+ insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
+ ghostView.mReferences = previousRefCount;
+ } else if (matrix != null) {
+ ghostView.setMatrix(matrix);
+ }
+ ghostView.mReferences++;
+ return ghostView;
+ }
+
+ public static GhostView addGhost(View view, ViewGroup viewGroup) {
+ return addGhost(view, viewGroup, null);
+ }
+
+ public static void removeGhost(View view) {
+ GhostView ghostView = view.mGhostView;
+ if (ghostView != null) {
+ ghostView.mReferences--;
+ if (ghostView.mReferences == 0) {
+ ViewGroup parent = (ViewGroup) ghostView.getParent();
+ ViewGroup grandParent = (ViewGroup) parent.getParent();
+ grandParent.removeView(parent);
+ }
+ }
+ }
+
+ public static GhostView getGhost(View view) {
+ return view.mGhostView;
+ }
+
+ private static void copySize(View from, View to) {
+ to.setLeft(0);
+ to.setTop(0);
+ to.setRight(from.getWidth());
+ to.setBottom(from.getHeight());
+ }
+
+ /**
+ * Move the GhostViews to the end so that they are on top of other views and it is easier
+ * to do binary search for the correct location for the GhostViews in insertIntoOverlay.
+ *
+ * @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup
+ */
+ private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) {
+ final int numChildren = viewGroup.getChildCount();
+ if (numChildren == 0) {
+ return -1;
+ } else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) {
+ // GhostViews are already at the end
+ int firstGhost = numChildren - 1;
+ for (int i = numChildren - 2; i >= 0; i--) {
+ if (!isGhostWrapper(viewGroup.getChildAt(i))) {
+ break;
+ }
+ firstGhost = i;
+ }
+ return firstGhost;
+ }
+
+ // Remove all GhostViews from the middle
+ for (int i = numChildren - 2; i >= 0; i--) {
+ View child = viewGroup.getChildAt(i);
+ if (isGhostWrapper(child)) {
+ tempViews.add(child);
+ GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0);
+ ghostView.mBeingMoved = true;
+ viewGroup.removeViewAt(i);
+ ghostView.mBeingMoved = false;
+ }
+ }
+
+ final int firstGhost;
+ if (tempViews.isEmpty()) {
+ firstGhost = -1;
+ } else {
+ firstGhost = viewGroup.getChildCount();
+ // Add the GhostViews to the end
+ for (int i = tempViews.size() - 1; i >= 0; i--) {
+ viewGroup.addView(tempViews.get(i));
+ }
+ tempViews.clear();
+ }
+ return firstGhost;
+ }
+
+ /**
+ * Inserts a GhostView into the overlay's ViewGroup in the order in which they
+ * should be displayed by the UI.
+ */
+ private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper,
+ GhostView ghostView, ArrayList<View> tempParents, int firstGhost) {
+ if (firstGhost == -1) {
+ viewGroup.addView(wrapper);
+ } else {
+ ArrayList<View> viewParents = new ArrayList<View>();
+ getParents(ghostView.mView, viewParents);
+
+ int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost);
+ if (index < 0 || index >= viewGroup.getChildCount()) {
+ viewGroup.addView(wrapper);
+ } else {
+ viewGroup.addView(wrapper, index);
+ }
+ }
+ }
+
+ /**
+ * Find the index into the overlay to insert the GhostView based on the order that the
+ * views should be drawn. This keeps GhostViews layered in the same order
+ * that they are ordered in the UI.
+ */
+ private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents,
+ ArrayList<View> tempParents, int firstGhost) {
+ int low = firstGhost;
+ int high = overlayViewGroup.getChildCount() - 1;
+
+ while (low <= high) {
+ int mid = (low + high) / 2;
+ ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid);
+ GhostView midView = (GhostView) wrapper.getChildAt(0);
+ getParents(midView.mView, tempParents);
+ if (isOnTop(viewParents, tempParents)) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ tempParents.clear();
+ }
+
+ return low;
+ }
+
+ /**
+ * Returns true if view is a GhostView's FrameLayout wrapper.
+ */
+ private static boolean isGhostWrapper(View view) {
+ if (view instanceof FrameLayout) {
+ FrameLayout frameLayout = (FrameLayout) view;
+ if (frameLayout.getChildCount() == 1) {
+ View child = frameLayout.getChildAt(0);
+ return child instanceof GhostView;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if viewParents is from a View that is on top of the comparedWith's view.
+ * The ArrayLists contain the ancestors of views in order from top most grandparent, to
+ * the view itself, in order. The goal is to find the first matching parent and then
+ * compare the draw order of the siblings.
+ */
+ private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) {
+ if (viewParents.isEmpty() || comparedWith.isEmpty() ||
+ viewParents.get(0) != comparedWith.get(0)) {
+ // Not the same decorView -- arbitrary ordering
+ return true;
+ }
+ int depth = Math.min(viewParents.size(), comparedWith.size());
+ for (int i = 1; i < depth; i++) {
+ View viewParent = viewParents.get(i);
+ View comparedWithParent = comparedWith.get(i);
+
+ if (viewParent != comparedWithParent) {
+ // i - 1 is the same parent, but these are different children.
+ return isOnTop(viewParent, comparedWithParent);
+ }
+ }
+
+ // one of these is the parent of the other
+ boolean isComparedWithTheParent = (comparedWith.size() == depth);
+ return isComparedWithTheParent;
+ }
+
+ /**
+ * Adds all the parents, grandparents, etc. of view to parents.
+ */
+ private static void getParents(View view, ArrayList<View> parents) {
+ ViewParent parent = view.getParent();
+ if (parent != null && parent instanceof ViewGroup) {
+ getParents((View) parent, parents);
+ }
+ parents.add(view);
+ }
+
+ /**
+ * Returns true if view would be drawn on top of comparedWith or false otherwise.
+ * view and comparedWith are siblings with the same parent. This uses the logic
+ * that dispatchDraw uses to determine which View should be drawn first.
+ */
+ private static boolean isOnTop(View view, View comparedWith) {
+ ViewGroup parent = (ViewGroup) view.getParent();
+
+ final int childrenCount = parent.getChildCount();
+ final ArrayList<View> preorderedList = parent.buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && parent.isChildrenDrawingOrderEnabled();
+
+ // This default value shouldn't be used because both view and comparedWith
+ // should be in the list. If there is an error, then just return an arbitrary
+ // view is on top.
+ boolean isOnTop = true;
+ for (int i = 0; i < childrenCount; i++) {
+ int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
+ if (child == view) {
+ isOnTop = false;
+ break;
+ } else if (child == comparedWith) {
+ isOnTop = true;
+ break;
+ }
+ }
+
+ if (preorderedList != null) {
+ preorderedList.clear();
+ }
+ return isOnTop;
+ }
+}
diff --git a/android/view/Gravity.java b/android/view/Gravity.java
new file mode 100644
index 00000000..324a1ae3
--- /dev/null
+++ b/android/view/Gravity.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * Standard constants and tools for placing an object within a potentially
+ * larger container.
+ */
+public class Gravity
+{
+ /** Constant indicating that no gravity has been set **/
+ public static final int NO_GRAVITY = 0x0000;
+
+ /** Raw bit indicating the gravity for an axis has been specified. */
+ public static final int AXIS_SPECIFIED = 0x0001;
+
+ /** Raw bit controlling how the left/top edge is placed. */
+ public static final int AXIS_PULL_BEFORE = 0x0002;
+ /** Raw bit controlling how the right/bottom edge is placed. */
+ public static final int AXIS_PULL_AFTER = 0x0004;
+ /** Raw bit controlling whether the right/bottom edge is clipped to its
+ * container, based on the gravity direction being applied. */
+ public static final int AXIS_CLIP = 0x0008;
+
+ /** Bits defining the horizontal axis. */
+ public static final int AXIS_X_SHIFT = 0;
+ /** Bits defining the vertical axis. */
+ public static final int AXIS_Y_SHIFT = 4;
+
+ /** Push object to the top of its container, not changing its size. */
+ public static final int TOP = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;
+ /** Push object to the bottom of its container, not changing its size. */
+ public static final int BOTTOM = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_Y_SHIFT;
+ /** Push object to the left of its container, not changing its size. */
+ public static final int LEFT = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_X_SHIFT;
+ /** Push object to the right of its container, not changing its size. */
+ public static final int RIGHT = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_X_SHIFT;
+
+ /** Place object in the vertical center of its container, not changing its
+ * size. */
+ public static final int CENTER_VERTICAL = AXIS_SPECIFIED<<AXIS_Y_SHIFT;
+ /** Grow the vertical size of the object if needed so it completely fills
+ * its container. */
+ public static final int FILL_VERTICAL = TOP|BOTTOM;
+
+ /** Place object in the horizontal center of its container, not changing its
+ * size. */
+ public static final int CENTER_HORIZONTAL = AXIS_SPECIFIED<<AXIS_X_SHIFT;
+ /** Grow the horizontal size of the object if needed so it completely fills
+ * its container. */
+ public static final int FILL_HORIZONTAL = LEFT|RIGHT;
+
+ /** Place the object in the center of its container in both the vertical
+ * and horizontal axis, not changing its size. */
+ public static final int CENTER = CENTER_VERTICAL|CENTER_HORIZONTAL;
+
+ /** Grow the horizontal and vertical size of the object if needed so it
+ * completely fills its container. */
+ public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL;
+
+ /** Flag to clip the edges of the object to its container along the
+ * vertical axis. */
+ public static final int CLIP_VERTICAL = AXIS_CLIP<<AXIS_Y_SHIFT;
+
+ /** Flag to clip the edges of the object to its container along the
+ * horizontal axis. */
+ public static final int CLIP_HORIZONTAL = AXIS_CLIP<<AXIS_X_SHIFT;
+
+ /** Raw bit controlling whether the layout direction is relative or not (START/END instead of
+ * absolute LEFT/RIGHT).
+ */
+ public static final int RELATIVE_LAYOUT_DIRECTION = 0x00800000;
+
+ /**
+ * Binary mask to get the absolute horizontal gravity of a gravity.
+ */
+ public static final int HORIZONTAL_GRAVITY_MASK = (AXIS_SPECIFIED |
+ AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_X_SHIFT;
+ /**
+ * Binary mask to get the vertical gravity of a gravity.
+ */
+ public static final int VERTICAL_GRAVITY_MASK = (AXIS_SPECIFIED |
+ AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_Y_SHIFT;
+
+ /** Special constant to enable clipping to an overall display along the
+ * vertical dimension. This is not applied by default by
+ * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+ * yourself by calling {@link #applyDisplay}.
+ */
+ public static final int DISPLAY_CLIP_VERTICAL = 0x10000000;
+
+ /** Special constant to enable clipping to an overall display along the
+ * horizontal dimension. This is not applied by default by
+ * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+ * yourself by calling {@link #applyDisplay}.
+ */
+ public static final int DISPLAY_CLIP_HORIZONTAL = 0x01000000;
+
+ /** Push object to x-axis position at the start of its container, not changing its size. */
+ public static final int START = RELATIVE_LAYOUT_DIRECTION | LEFT;
+
+ /** Push object to x-axis position at the end of its container, not changing its size. */
+ public static final int END = RELATIVE_LAYOUT_DIRECTION | RIGHT;
+
+ /**
+ * Binary mask for the horizontal gravity and script specific direction bit.
+ */
+ public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = START | END;
+
+ /**
+ * Apply a gravity constant to an object. This supposes that the layout direction is LTR.
+ *
+ * @param gravity The desired placement of the object, as defined by the
+ * constants in this class.
+ * @param w The horizontal size of the object.
+ * @param h The vertical size of the object.
+ * @param container The frame of the containing space, in which the object
+ * will be placed. Should be large enough to contain the
+ * width and height of the object.
+ * @param outRect Receives the computed frame of the object in its
+ * container.
+ */
+ public static void apply(int gravity, int w, int h, Rect container, Rect outRect) {
+ apply(gravity, w, h, container, 0, 0, outRect);
+ }
+
+ /**
+ * Apply a gravity constant to an object and take care if layout direction is RTL or not.
+ *
+ * @param gravity The desired placement of the object, as defined by the
+ * constants in this class.
+ * @param w The horizontal size of the object.
+ * @param h The vertical size of the object.
+ * @param container The frame of the containing space, in which the object
+ * will be placed. Should be large enough to contain the
+ * width and height of the object.
+ * @param outRect Receives the computed frame of the object in its
+ * container.
+ * @param layoutDirection The layout direction.
+ *
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
+ */
+ public static void apply(int gravity, int w, int h, Rect container,
+ Rect outRect, int layoutDirection) {
+ int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+ apply(absGravity, w, h, container, 0, 0, outRect);
+ }
+
+ /**
+ * Apply a gravity constant to an object.
+ *
+ * @param gravity The desired placement of the object, as defined by the
+ * constants in this class.
+ * @param w The horizontal size of the object.
+ * @param h The vertical size of the object.
+ * @param container The frame of the containing space, in which the object
+ * will be placed. Should be large enough to contain the
+ * width and height of the object.
+ * @param xAdj Offset to apply to the X axis. If gravity is LEFT this
+ * pushes it to the right; if gravity is RIGHT it pushes it to
+ * the left; if gravity is CENTER_HORIZONTAL it pushes it to the
+ * right or left; otherwise it is ignored.
+ * @param yAdj Offset to apply to the Y axis. If gravity is TOP this pushes
+ * it down; if gravity is BOTTOM it pushes it up; if gravity is
+ * CENTER_VERTICAL it pushes it down or up; otherwise it is
+ * ignored.
+ * @param outRect Receives the computed frame of the object in its
+ * container.
+ */
+ public static void apply(int gravity, int w, int h, Rect container,
+ int xAdj, int yAdj, Rect outRect) {
+ switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
+ case 0:
+ outRect.left = container.left
+ + ((container.right - container.left - w)/2) + xAdj;
+ outRect.right = outRect.left + w;
+ if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+ == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+ if (outRect.left < container.left) {
+ outRect.left = container.left;
+ }
+ if (outRect.right > container.right) {
+ outRect.right = container.right;
+ }
+ }
+ break;
+ case AXIS_PULL_BEFORE<<AXIS_X_SHIFT:
+ outRect.left = container.left + xAdj;
+ outRect.right = outRect.left + w;
+ if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+ == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+ if (outRect.right > container.right) {
+ outRect.right = container.right;
+ }
+ }
+ break;
+ case AXIS_PULL_AFTER<<AXIS_X_SHIFT:
+ outRect.right = container.right - xAdj;
+ outRect.left = outRect.right - w;
+ if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+ == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+ if (outRect.left < container.left) {
+ outRect.left = container.left;
+ }
+ }
+ break;
+ default:
+ outRect.left = container.left + xAdj;
+ outRect.right = container.right + xAdj;
+ break;
+ }
+
+ switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
+ case 0:
+ outRect.top = container.top
+ + ((container.bottom - container.top - h)/2) + yAdj;
+ outRect.bottom = outRect.top + h;
+ if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+ == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+ if (outRect.top < container.top) {
+ outRect.top = container.top;
+ }
+ if (outRect.bottom > container.bottom) {
+ outRect.bottom = container.bottom;
+ }
+ }
+ break;
+ case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT:
+ outRect.top = container.top + yAdj;
+ outRect.bottom = outRect.top + h;
+ if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+ == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+ if (outRect.bottom > container.bottom) {
+ outRect.bottom = container.bottom;
+ }
+ }
+ break;
+ case AXIS_PULL_AFTER<<AXIS_Y_SHIFT:
+ outRect.bottom = container.bottom - yAdj;
+ outRect.top = outRect.bottom - h;
+ if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+ == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+ if (outRect.top < container.top) {
+ outRect.top = container.top;
+ }
+ }
+ break;
+ default:
+ outRect.top = container.top + yAdj;
+ outRect.bottom = container.bottom + yAdj;
+ break;
+ }
+ }
+
+ /**
+ * Apply a gravity constant to an object.
+ *
+ * @param gravity The desired placement of the object, as defined by the
+ * constants in this class.
+ * @param w The horizontal size of the object.
+ * @param h The vertical size of the object.
+ * @param container The frame of the containing space, in which the object
+ * will be placed. Should be large enough to contain the
+ * width and height of the object.
+ * @param xAdj Offset to apply to the X axis. If gravity is LEFT this
+ * pushes it to the right; if gravity is RIGHT it pushes it to
+ * the left; if gravity is CENTER_HORIZONTAL it pushes it to the
+ * right or left; otherwise it is ignored.
+ * @param yAdj Offset to apply to the Y axis. If gravity is TOP this pushes
+ * it down; if gravity is BOTTOM it pushes it up; if gravity is
+ * CENTER_VERTICAL it pushes it down or up; otherwise it is
+ * ignored.
+ * @param outRect Receives the computed frame of the object in its
+ * container.
+ * @param layoutDirection The layout direction.
+ *
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
+ */
+ public static void apply(int gravity, int w, int h, Rect container,
+ int xAdj, int yAdj, Rect outRect, int layoutDirection) {
+ int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+ apply(absGravity, w, h, container, xAdj, yAdj, outRect);
+ }
+
+ /**
+ * Apply additional gravity behavior based on the overall "display" that an
+ * object exists in. This can be used after
+ * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
+ * within a visible display. By default this moves or clips the object
+ * to be visible in the display; the gravity flags
+ * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL}
+ * can be used to change this behavior.
+ *
+ * @param gravity Gravity constants to modify the placement within the
+ * display.
+ * @param display The rectangle of the display in which the object is
+ * being placed.
+ * @param inoutObj Supplies the current object position; returns with it
+ * modified if needed to fit in the display.
+ */
+ public static void applyDisplay(int gravity, Rect display, Rect inoutObj) {
+ if ((gravity&DISPLAY_CLIP_VERTICAL) != 0) {
+ if (inoutObj.top < display.top) inoutObj.top = display.top;
+ if (inoutObj.bottom > display.bottom) inoutObj.bottom = display.bottom;
+ } else {
+ int off = 0;
+ if (inoutObj.top < display.top) off = display.top-inoutObj.top;
+ else if (inoutObj.bottom > display.bottom) off = display.bottom-inoutObj.bottom;
+ if (off != 0) {
+ if (inoutObj.height() > (display.bottom-display.top)) {
+ inoutObj.top = display.top;
+ inoutObj.bottom = display.bottom;
+ } else {
+ inoutObj.top += off;
+ inoutObj.bottom += off;
+ }
+ }
+ }
+
+ if ((gravity&DISPLAY_CLIP_HORIZONTAL) != 0) {
+ if (inoutObj.left < display.left) inoutObj.left = display.left;
+ if (inoutObj.right > display.right) inoutObj.right = display.right;
+ } else {
+ int off = 0;
+ if (inoutObj.left < display.left) off = display.left-inoutObj.left;
+ else if (inoutObj.right > display.right) off = display.right-inoutObj.right;
+ if (off != 0) {
+ if (inoutObj.width() > (display.right-display.left)) {
+ inoutObj.left = display.left;
+ inoutObj.right = display.right;
+ } else {
+ inoutObj.left += off;
+ inoutObj.right += off;
+ }
+ }
+ }
+ }
+
+ /**
+ * Apply additional gravity behavior based on the overall "display" that an
+ * object exists in. This can be used after
+ * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
+ * within a visible display. By default this moves or clips the object
+ * to be visible in the display; the gravity flags
+ * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL}
+ * can be used to change this behavior.
+ *
+ * @param gravity Gravity constants to modify the placement within the
+ * display.
+ * @param display The rectangle of the display in which the object is
+ * being placed.
+ * @param inoutObj Supplies the current object position; returns with it
+ * modified if needed to fit in the display.
+ * @param layoutDirection The layout direction.
+ *
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
+ */
+ public static void applyDisplay(int gravity, Rect display, Rect inoutObj, int layoutDirection) {
+ int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+ applyDisplay(absGravity, display, inoutObj);
+ }
+
+ /**
+ * <p>Indicate whether the supplied gravity has a vertical pull.</p>
+ *
+ * @param gravity the gravity to check for vertical pull
+ * @return true if the supplied gravity has a vertical pull
+ */
+ public static boolean isVertical(int gravity) {
+ return gravity > 0 && (gravity & VERTICAL_GRAVITY_MASK) != 0;
+ }
+
+ /**
+ * <p>Indicate whether the supplied gravity has an horizontal pull.</p>
+ *
+ * @param gravity the gravity to check for horizontal pull
+ * @return true if the supplied gravity has an horizontal pull
+ */
+ public static boolean isHorizontal(int gravity) {
+ return gravity > 0 && (gravity & RELATIVE_HORIZONTAL_GRAVITY_MASK) != 0;
+ }
+
+ /**
+ * <p>Convert script specific gravity to absolute horizontal value.</p>
+ *
+ * if horizontal direction is LTR, then START will set LEFT and END will set RIGHT.
+ * if horizontal direction is RTL, then START will set RIGHT and END will set LEFT.
+ *
+ *
+ * @param gravity The gravity to convert to absolute (horizontal) values.
+ * @param layoutDirection The layout direction.
+ * @return gravity converted to absolute (horizontal) values.
+ */
+ public static int getAbsoluteGravity(int gravity, int layoutDirection) {
+ int result = gravity;
+ // If layout is script specific and gravity is horizontal relative (START or END)
+ if ((result & RELATIVE_LAYOUT_DIRECTION) > 0) {
+ if ((result & Gravity.START) == Gravity.START) {
+ // Remove the START bit
+ result &= ~START;
+ if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ // Set the RIGHT bit
+ result |= RIGHT;
+ } else {
+ // Set the LEFT bit
+ result |= LEFT;
+ }
+ } else if ((result & Gravity.END) == Gravity.END) {
+ // Remove the END bit
+ result &= ~END;
+ if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ // Set the LEFT bit
+ result |= LEFT;
+ } else {
+ // Set the RIGHT bit
+ result |= RIGHT;
+ }
+ }
+ // Don't need the script specific bit any more, so remove it as we are converting to
+ // absolute values (LEFT or RIGHT)
+ result &= ~RELATIVE_LAYOUT_DIRECTION;
+ }
+ return result;
+ }
+}
diff --git a/android/view/HandlerActionQueue.java b/android/view/HandlerActionQueue.java
new file mode 100644
index 00000000..d016a744
--- /dev/null
+++ b/android/view/HandlerActionQueue.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 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.os.Handler;
+
+import com.android.internal.util.GrowingArrayUtils;
+
+/**
+ * Class used to enqueue pending work from Views when no Handler is attached.
+ *
+ * @hide Exposed for test framework only.
+ */
+public class HandlerActionQueue {
+ private HandlerAction[] mActions;
+ private int mCount;
+
+ public void post(Runnable action) {
+ postDelayed(action, 0);
+ }
+
+ public void postDelayed(Runnable action, long delayMillis) {
+ final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
+
+ synchronized (this) {
+ if (mActions == null) {
+ mActions = new HandlerAction[4];
+ }
+ mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
+ mCount++;
+ }
+ }
+
+ public void removeCallbacks(Runnable action) {
+ synchronized (this) {
+ final int count = mCount;
+ int j = 0;
+
+ final HandlerAction[] actions = mActions;
+ for (int i = 0; i < count; i++) {
+ if (actions[i].matches(action)) {
+ // Remove this action by overwriting it within
+ // this loop or nulling it out later.
+ continue;
+ }
+
+ if (j != i) {
+ // At least one previous entry was removed, so
+ // this one needs to move to the "new" list.
+ actions[j] = actions[i];
+ }
+
+ j++;
+ }
+
+ // The "new" list only has j entries.
+ mCount = j;
+
+ // Null out any remaining entries.
+ for (; j < count; j++) {
+ actions[j] = null;
+ }
+ }
+ }
+
+ public void executeActions(Handler handler) {
+ synchronized (this) {
+ final HandlerAction[] actions = mActions;
+ for (int i = 0, count = mCount; i < count; i++) {
+ final HandlerAction handlerAction = actions[i];
+ handler.postDelayed(handlerAction.action, handlerAction.delay);
+ }
+
+ mActions = null;
+ mCount = 0;
+ }
+ }
+
+ public int size() {
+ return mCount;
+ }
+
+ public Runnable getRunnable(int index) {
+ if (index >= mCount) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mActions[index].action;
+ }
+
+ public long getDelay(int index) {
+ if (index >= mCount) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mActions[index].delay;
+ }
+
+ private static class HandlerAction {
+ final Runnable action;
+ final long delay;
+
+ public HandlerAction(Runnable action, long delay) {
+ this.action = action;
+ this.delay = delay;
+ }
+
+ public boolean matches(Runnable otherAction) {
+ return otherAction == null && action == null
+ || action != null && action.equals(otherAction);
+ }
+ }
+}
diff --git a/android/view/HandlerActionQueue_Delegate.java b/android/view/HandlerActionQueue_Delegate.java
new file mode 100644
index 00000000..e580ed0e
--- /dev/null
+++ b/android/view/HandlerActionQueue_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link HandlerActionQueue}
+ *
+ * Through the layoutlib_create tool, the original methods of ViewRootImpl.RunQueue have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class HandlerActionQueue_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void postDelayed(HandlerActionQueue thisQueue, Runnable action, long
+ delayMillis) {
+ // The actual HandlerActionQueue is never run and therefore never cleared. This method
+ // avoids runnables to be added to the RunQueue so they do not leak resources.
+ }
+}
diff --git a/android/view/HapticFeedbackConstants.java b/android/view/HapticFeedbackConstants.java
new file mode 100644
index 00000000..b1479284
--- /dev/null
+++ b/android/view/HapticFeedbackConstants.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009 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;
+
+/**
+ * Constants to be used to perform haptic feedback effects via
+ * {@link View#performHapticFeedback(int)}
+ */
+public class HapticFeedbackConstants {
+
+ private HapticFeedbackConstants() {}
+
+ /**
+ * The user has performed a long press on an object that is resulting
+ * in an action being performed.
+ */
+ public static final int LONG_PRESS = 0;
+
+ /**
+ * The user has pressed on a virtual on-screen key.
+ */
+ public static final int VIRTUAL_KEY = 1;
+
+ /**
+ * The user has pressed a soft keyboard key.
+ */
+ public static final int KEYBOARD_TAP = 3;
+
+ /**
+ * The user has pressed either an hour or minute tick of a Clock.
+ */
+ public static final int CLOCK_TICK = 4;
+
+ /**
+ * The user has pressed either a day or month or year date of a Calendar.
+ * @hide
+ */
+ public static final int CALENDAR_DATE = 5;
+
+ /**
+ * The user has performed a context click on an object.
+ */
+ public static final int CONTEXT_CLICK = 6;
+
+ /**
+ * The user has pressed a virtual or software keyboard key.
+ */
+ public static final int KEYBOARD_PRESS = KEYBOARD_TAP;
+
+ /**
+ * The user has released a virtual keyboard key.
+ */
+ public static final int KEYBOARD_RELEASE = 7;
+
+ /**
+ * The user has released a virtual key.
+ */
+ public static final int VIRTUAL_KEY_RELEASE = 8;
+
+ /**
+ * The user has performed a selection/insertion handle move on text field.
+ */
+ public static final int TEXT_HANDLE_MOVE = 9;
+
+ /**
+ * The phone has booted with safe mode enabled.
+ * This is a private constant. Feel free to renumber as desired.
+ * @hide
+ */
+ public static final int SAFE_MODE_ENABLED = 10001;
+
+ /**
+ * Flag for {@link View#performHapticFeedback(int, int)
+ * View.performHapticFeedback(int, int)}: Ignore the setting in the
+ * view for whether to perform haptic feedback, do it always.
+ */
+ public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001;
+
+ /**
+ * Flag for {@link View#performHapticFeedback(int, int)
+ * View.performHapticFeedback(int, int)}: Ignore the global setting
+ * for whether to perform haptic feedback, do it always.
+ */
+ public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002;
+}
diff --git a/android/view/HardwareLayer.java b/android/view/HardwareLayer.java
new file mode 100644
index 00000000..7af10201
--- /dev/null
+++ b/android/view/HardwareLayer.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 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.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.SurfaceTexture;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+/**
+ * A hardware layer can be used to render graphics operations into a hardware
+ * friendly buffer. For instance, with an OpenGL backend a hardware layer
+ * would use a Frame Buffer Object (FBO.) The hardware layer can be used as
+ * a drawing cache when a complex set of graphics operations needs to be
+ * drawn several times.
+ *
+ * @hide
+ */
+final class HardwareLayer {
+ private ThreadedRenderer mRenderer;
+ private VirtualRefBasePtr mFinalizer;
+
+ private HardwareLayer(ThreadedRenderer renderer, long deferredUpdater) {
+ if (renderer == null || deferredUpdater == 0) {
+ throw new IllegalArgumentException("Either hardware renderer: " + renderer
+ + " or deferredUpdater: " + deferredUpdater + " is invalid");
+ }
+ mRenderer = renderer;
+ mFinalizer = new VirtualRefBasePtr(deferredUpdater);
+ }
+
+ /**
+ * Update the paint used when drawing this layer.
+ *
+ * @param paint The paint used when the layer is drawn into the destination canvas.
+ * @see View#setLayerPaint(android.graphics.Paint)
+ */
+ public void setLayerPaint(@Nullable Paint paint) {
+ nSetLayerPaint(mFinalizer.get(), paint != null ? paint.getNativeInstance() : 0);
+ mRenderer.pushLayerUpdate(this);
+ }
+
+ /**
+ * Indicates whether this layer can be rendered.
+ *
+ * @return True if the layer can be rendered into, false otherwise
+ */
+ public boolean isValid() {
+ return mFinalizer != null && mFinalizer.get() != 0;
+ }
+
+ /**
+ * Destroys resources without waiting for a GC.
+ */
+ public void destroy() {
+ if (!isValid()) {
+ // Already destroyed
+ return;
+ }
+ mRenderer.onLayerDestroyed(this);
+ mRenderer = null;
+ mFinalizer.release();
+ mFinalizer = null;
+ }
+
+ public long getDeferredLayerUpdater() {
+ return mFinalizer.get();
+ }
+
+ /**
+ * Copies this layer into the specified bitmap.
+ *
+ * @param bitmap The bitmap to copy they layer into
+ *
+ * @return True if the copy was successful, false otherwise
+ */
+ public boolean copyInto(Bitmap bitmap) {
+ return mRenderer.copyLayerInto(this, bitmap);
+ }
+
+ /**
+ * Update the layer's properties. Note that after calling this isValid() may
+ * return false if the requested width/height cannot be satisfied
+ *
+ * @param width The new width of this layer
+ * @param height The new height of this layer
+ * @param isOpaque Whether this layer is opaque
+ *
+ * @return true if the layer's properties will change, false if they already
+ * match the desired values.
+ */
+ public boolean prepare(int width, int height, boolean isOpaque) {
+ return nPrepare(mFinalizer.get(), width, height, isOpaque);
+ }
+
+ /**
+ * Sets an optional transform on this layer.
+ *
+ * @param matrix The transform to apply to the layer.
+ */
+ public void setTransform(Matrix matrix) {
+ nSetTransform(mFinalizer.get(), matrix.native_instance);
+ mRenderer.pushLayerUpdate(this);
+ }
+
+ /**
+ * Indicates that this layer has lost its texture.
+ */
+ public void detachSurfaceTexture() {
+ mRenderer.detachSurfaceTexture(mFinalizer.get());
+ }
+
+ public long getLayerHandle() {
+ return mFinalizer.get();
+ }
+
+ public void setSurfaceTexture(SurfaceTexture surface) {
+ nSetSurfaceTexture(mFinalizer.get(), surface);
+ mRenderer.pushLayerUpdate(this);
+ }
+
+ public void updateSurfaceTexture() {
+ nUpdateSurfaceTexture(mFinalizer.get());
+ mRenderer.pushLayerUpdate(this);
+ }
+
+ static HardwareLayer adoptTextureLayer(ThreadedRenderer renderer, long layer) {
+ return new HardwareLayer(renderer, layer);
+ }
+
+ private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque);
+ private static native void nSetLayerPaint(long layerUpdater, long paint);
+ private static native void nSetTransform(long layerUpdater, long matrix);
+ private static native void nSetSurfaceTexture(long layerUpdater, SurfaceTexture surface);
+ private static native void nUpdateSurfaceTexture(long layerUpdater);
+}
diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java
new file mode 100644
index 00000000..b34dfbfe
--- /dev/null
+++ b/android/view/IWindowManagerImpl.java
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2011 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.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+
+import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+
+/**
+ * Basic implementation of {@link IWindowManager} so that {@link Display} (and
+ * {@link Display_Delegate}) can return a valid instance.
+ */
+public class IWindowManagerImpl implements IWindowManager {
+
+ private final Configuration mConfig;
+ private final DisplayMetrics mMetrics;
+ private final int mRotation;
+ private final boolean mHasNavigationBar;
+
+ public IWindowManagerImpl(Configuration config, DisplayMetrics metrics, int rotation,
+ boolean hasNavigationBar) {
+ mConfig = config;
+ mMetrics = metrics;
+ mRotation = rotation;
+ mHasNavigationBar = hasNavigationBar;
+ }
+
+ // custom API.
+
+ public DisplayMetrics getMetrics() {
+ return mMetrics;
+ }
+
+ // ---- implementation of IWindowManager that we care about ----
+
+ @Override
+ public int getDefaultDisplayRotation() throws RemoteException {
+ return mRotation;
+ }
+
+ @Override
+ public boolean hasNavigationBar() {
+ return mHasNavigationBar;
+ }
+
+ // ---- unused implementation of IWindowManager ----
+
+ @Override
+ public void addWindowToken(IBinder arg0, int arg1, int arg2) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clearForcedDisplaySize(int displayId) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void clearForcedDisplayDensityForUser(int displayId, int userId) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setOverscan(int displayId, int left, int top, int right, int bottom)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void closeSystemDialogs(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void startFreezingScreen(int exitAnim, int enterAnim) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void stopFreezingScreen() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void disableKeyguard(IBinder arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void executeAppTransition() throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void exitKeyguardSecurely(IOnKeyguardExitResult arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void freezeRotation(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public float getAnimationScale(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public float[] getAnimationScales() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getPendingAppTransition() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public boolean inKeyguardRestrictedInputMode() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardLocked() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardSecure() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isViewServerRunning() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public IWindowSession openSession(IWindowSessionCallback argn1, IInputMethodClient arg0,
+ IInputContext arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void overridePendingAppTransition(String arg0, int arg1, int arg2,
+ IRemoteCallback startedCallback) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+ int startHeight) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void overridePendingAppTransitionClipReveal(int startX, int startY,
+ int startWidth, int startHeight) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, int startY,
+ IRemoteCallback startedCallback, boolean scaleUp) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX,
+ int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
+ boolean scaleUp) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void overridePendingAppTransitionInPlace(String packageName, int anim) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void overridePendingAppTransitionMultiThumbFuture(
+ IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
+ boolean scaleUp) throws RemoteException {
+
+ }
+
+ @Override
+ public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+ IRemoteCallback callback0, IRemoteCallback callback1, boolean scaleUp) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void reenableKeyguard(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeWindowToken(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void setAnimationScale(int arg0, float arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAnimationScales(float[] arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public float getCurrentAnimatorScale() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void setEventDispatching(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setFocusedApp(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getInitialDisplaySize(int displayId, Point size) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getBaseDisplaySize(int displayId, Point size) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setForcedDisplaySize(int displayId, int arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public int getInitialDisplayDensity(int displayId) {
+ return -1;
+ }
+
+ @Override
+ public int getBaseDisplayDensity(int displayId) {
+ return -1;
+ }
+
+ @Override
+ public void setForcedDisplayDensityForUser(int displayId, int density, int userId)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setForcedDisplayScalingMode(int displayId, int mode) {
+ }
+
+ @Override
+ public void setInTouchMode(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setScreenCaptureDisabled(int userId, boolean disabled) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void updateRotation(boolean arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setStrictModeVisualIndicatorPreference(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void showStrictModeViolation(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean startViewServer(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void statusBarVisibilityChanged(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setRecentsVisibility(boolean visible) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setPipVisibility(boolean visible) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean stopViewServer() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void thawRotation() throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int watchRotation(IRotationWatcher arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void removeRotationWatcher(IRotationWatcher arg0) throws RemoteException {
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getPreferredOptionsPanelGravity() throws RemoteException {
+ return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ }
+
+ @Override
+ public void dismissKeyguard(IKeyguardDismissCallback callback) throws RemoteException {
+ }
+
+ @Override
+ public void setSwitchingUser(boolean switching) throws RemoteException {
+ }
+
+ @Override
+ public void lockNow(Bundle options) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean isSafeModeEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isRotationFrozen() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void enableScreenIfNeeded() throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean clearWindowContentFrameStats(IBinder token) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public WindowContentFrameStats getWindowContentFrameStats(IBinder token)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getDockedStackSide() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void setDockedStackResizing(boolean resizing) throws RemoteException {
+ }
+
+ @Override
+ public void endProlongedAnimations() {
+ }
+
+ @Override
+ public void registerDockedStackListener(IDockedStackListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
+ throws RemoteException {
+ }
+
+ @Override
+ public void setDockedStackDividerTouchRegion(Rect touchableRegion) throws RemoteException {
+ }
+
+ @Override
+ public void requestAppKeyboardShortcuts(
+ IResultReceiver receiver, int deviceId) throws RemoteException {
+ }
+
+ @Override
+ public void getStableInsets(int displayId, Rect outInsets) throws RemoteException {
+ }
+
+ @Override
+ public void registerShortcutKey(long shortcutCode, IShortcutService service)
+ throws RemoteException {}
+
+ @Override
+ public void createInputConsumer(String name, InputChannel inputChannel)
+ throws RemoteException {}
+
+ @Override
+ public boolean destroyInputConsumer(String name) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public Bitmap screenshotWallpaper() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void enableSurfaceTrace(ParcelFileDescriptor fd) throws RemoteException {
+ }
+
+ @Override
+ public void disableSurfaceTrace() throws RemoteException {
+ }
+
+ @Override
+ public Region getCurrentImeTouchRegion() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+ int displayId) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+ int displayId) throws RemoteException {
+ }
+}
diff --git a/android/view/InflateException.java b/android/view/InflateException.java
new file mode 100644
index 00000000..7b39d33b
--- /dev/null
+++ b/android/view/InflateException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 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;
+
+/**
+ * This exception is thrown by an inflater on error conditions.
+ */
+public class InflateException extends RuntimeException {
+
+ public InflateException() {
+ super();
+ }
+
+ public InflateException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public InflateException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ public InflateException(Throwable throwable) {
+ super(throwable);
+ }
+
+}
diff --git a/android/view/InputChannel.java b/android/view/InputChannel.java
new file mode 100644
index 00000000..de195ae5
--- /dev/null
+++ b/android/view/InputChannel.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2010 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+/**
+ * An input channel specifies the file descriptors used to send input events to
+ * a window in another process. It is Parcelable so that it can be sent
+ * to the process that is to receive events. Only one thread should be reading
+ * from an InputChannel at a time.
+ * @hide
+ */
+public final class InputChannel implements Parcelable {
+ private static final String TAG = "InputChannel";
+
+ private static final boolean DEBUG = false;
+
+ public static final Parcelable.Creator<InputChannel> CREATOR
+ = new Parcelable.Creator<InputChannel>() {
+ public InputChannel createFromParcel(Parcel source) {
+ InputChannel result = new InputChannel();
+ result.readFromParcel(source);
+ return result;
+ }
+
+ public InputChannel[] newArray(int size) {
+ return new InputChannel[size];
+ }
+ };
+
+ @SuppressWarnings("unused")
+ private long mPtr; // used by native code
+
+ private static native InputChannel[] nativeOpenInputChannelPair(String name);
+
+ private native void nativeDispose(boolean finalized);
+ private native void nativeTransferTo(InputChannel other);
+ private native void nativeReadFromParcel(Parcel parcel);
+ private native void nativeWriteToParcel(Parcel parcel);
+ private native void nativeDup(InputChannel target);
+
+ private native String nativeGetName();
+
+ /**
+ * Creates an uninitialized input channel.
+ * It can be initialized by reading from a Parcel or by transferring the state of
+ * another input channel into this one.
+ */
+ public InputChannel() {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nativeDispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Creates a new input channel pair. One channel should be provided to the input
+ * dispatcher and the other to the application's input queue.
+ * @param name The descriptive (non-unique) name of the channel pair.
+ * @return A pair of input channels. The first channel is designated as the
+ * server channel and should be used to publish input events. The second channel
+ * is designated as the client channel and should be used to consume input events.
+ */
+ public static InputChannel[] openInputChannelPair(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Opening input channel pair '" + name + "'");
+ }
+ return nativeOpenInputChannelPair(name);
+ }
+
+ /**
+ * Gets the name of the input channel.
+ * @return The input channel name.
+ */
+ public String getName() {
+ String name = nativeGetName();
+ return name != null ? name : "uninitialized";
+ }
+
+ /**
+ * Disposes the input channel.
+ * Explicitly releases the reference this object is holding on the input channel.
+ * When all references are released, the input channel will be closed.
+ */
+ public void dispose() {
+ nativeDispose(false);
+ }
+
+ /**
+ * Transfers ownership of the internal state of the input channel to another
+ * instance and invalidates this instance. This is used to pass an input channel
+ * as an out parameter in a binder call.
+ * @param other The other input channel instance.
+ */
+ public void transferTo(InputChannel outParameter) {
+ if (outParameter == null) {
+ throw new IllegalArgumentException("outParameter must not be null");
+ }
+
+ nativeTransferTo(outParameter);
+ }
+
+ /**
+ * Duplicates the input channel.
+ */
+ public InputChannel dup() {
+ InputChannel target = new InputChannel();
+ nativeDup(target);
+ return target;
+ }
+
+ @Override
+ public int describeContents() {
+ return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ public void readFromParcel(Parcel in) {
+ if (in == null) {
+ throw new IllegalArgumentException("in must not be null");
+ }
+
+ nativeReadFromParcel(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (out == null) {
+ throw new IllegalArgumentException("out must not be null");
+ }
+
+ nativeWriteToParcel(out);
+
+ if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ dispose();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+}
diff --git a/android/view/InputDevice.java b/android/view/InputDevice.java
new file mode 100644
index 00000000..8405d9ea
--- /dev/null
+++ b/android/view/InputDevice.java
@@ -0,0 +1,1045 @@
+/*
+ * Copyright (C) 2010 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.RequiresPermission;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
+import android.os.NullVibrator;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Vibrator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes the capabilities of a particular input device.
+ * <p>
+ * Each input device may support multiple classes of input. For example, a multi-function
+ * keyboard may compose the capabilities of a standard keyboard together with a track pad mouse
+ * or other pointing device.
+ * </p><p>
+ * Some input devices present multiple distinguishable sources of input.
+ * Applications can query the framework about the characteristics of each distinct source.
+ * </p><p>
+ * As a further wrinkle, different kinds of input sources uses different coordinate systems
+ * to describe motion events. Refer to the comments on the input source constants for
+ * the appropriate interpretation.
+ * </p>
+ */
+public final class InputDevice implements Parcelable {
+ private final int mId;
+ private final int mGeneration;
+ private final int mControllerNumber;
+ private final String mName;
+ private final int mVendorId;
+ private final int mProductId;
+ private final String mDescriptor;
+ private final InputDeviceIdentifier mIdentifier;
+ private final boolean mIsExternal;
+ private final int mSources;
+ private final int mKeyboardType;
+ private final KeyCharacterMap mKeyCharacterMap;
+ private final boolean mHasVibrator;
+ private final boolean mHasMicrophone;
+ private final boolean mHasButtonUnderPad;
+ private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
+
+ private Vibrator mVibrator; // guarded by mMotionRanges during initialization
+
+ /**
+ * A mask for input source classes.
+ *
+ * Each distinct input source constant has one or more input source class bits set to
+ * specify the desired interpretation for its input events.
+ */
+ public static final int SOURCE_CLASS_MASK = 0x000000ff;
+
+ /**
+ * The input source has no class.
+ *
+ * It is up to the application to determine how to handle the device based on the device type.
+ */
+ public static final int SOURCE_CLASS_NONE = 0x00000000;
+
+ /**
+ * The input source has buttons or keys.
+ * Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_DPAD}.
+ *
+ * A {@link KeyEvent} should be interpreted as a button or key press.
+ *
+ * Use {@link #getKeyCharacterMap} to query the device's button and key mappings.
+ */
+ public static final int SOURCE_CLASS_BUTTON = 0x00000001;
+
+ /**
+ * The input source is a pointing device associated with a display.
+ * Examples: {@link #SOURCE_TOUCHSCREEN}, {@link #SOURCE_MOUSE}.
+ *
+ * A {@link MotionEvent} should be interpreted as absolute coordinates in
+ * display units according to the {@link View} hierarchy. Pointer down/up indicated when
+ * the finger touches the display or when the selection button is pressed/released.
+ *
+ * Use {@link #getMotionRange} to query the range of the pointing device. Some devices permit
+ * touches outside the display area so the effective range may be somewhat smaller or larger
+ * than the actual display size.
+ */
+ public static final int SOURCE_CLASS_POINTER = 0x00000002;
+
+ /**
+ * The input source is a trackball navigation device.
+ * Examples: {@link #SOURCE_TRACKBALL}.
+ *
+ * A {@link MotionEvent} should be interpreted as relative movements in device-specific
+ * units used for navigation purposes. Pointer down/up indicates when the selection button
+ * is pressed/released.
+ *
+ * Use {@link #getMotionRange} to query the range of motion.
+ */
+ public static final int SOURCE_CLASS_TRACKBALL = 0x00000004;
+
+ /**
+ * The input source is an absolute positioning device not associated with a display
+ * (unlike {@link #SOURCE_CLASS_POINTER}).
+ *
+ * A {@link MotionEvent} should be interpreted as absolute coordinates in
+ * device-specific surface units.
+ *
+ * Use {@link #getMotionRange} to query the range of positions.
+ */
+ public static final int SOURCE_CLASS_POSITION = 0x00000008;
+
+ /**
+ * The input source is a joystick.
+ *
+ * A {@link MotionEvent} should be interpreted as absolute joystick movements.
+ *
+ * Use {@link #getMotionRange} to query the range of positions.
+ */
+ public static final int SOURCE_CLASS_JOYSTICK = 0x00000010;
+
+ /**
+ * The input source is unknown.
+ */
+ public static final int SOURCE_UNKNOWN = 0x00000000;
+
+ /**
+ * The input source is a keyboard.
+ *
+ * This source indicates pretty much anything that has buttons. Use
+ * {@link #getKeyboardType()} to determine whether the keyboard has alphabetic keys
+ * and can be used to enter text.
+ *
+ * @see #SOURCE_CLASS_BUTTON
+ */
+ public static final int SOURCE_KEYBOARD = 0x00000100 | SOURCE_CLASS_BUTTON;
+
+ /**
+ * The input source is a DPad.
+ *
+ * @see #SOURCE_CLASS_BUTTON
+ */
+ public static final int SOURCE_DPAD = 0x00000200 | SOURCE_CLASS_BUTTON;
+
+ /**
+ * The input source is a game pad.
+ * (It may also be a {@link #SOURCE_JOYSTICK}).
+ *
+ * @see #SOURCE_CLASS_BUTTON
+ */
+ public static final int SOURCE_GAMEPAD = 0x00000400 | SOURCE_CLASS_BUTTON;
+
+ /**
+ * The input source is a touch screen pointing device.
+ *
+ * @see #SOURCE_CLASS_POINTER
+ */
+ public static final int SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER;
+
+ /**
+ * The input source is a mouse pointing device.
+ * This code is also used for other mouse-like pointing devices such as trackpads
+ * and trackpoints.
+ *
+ * @see #SOURCE_CLASS_POINTER
+ */
+ public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER;
+
+ /**
+ * The input source is a stylus pointing device.
+ * <p>
+ * Note that this bit merely indicates that an input device is capable of obtaining
+ * input from a stylus. To determine whether a given touch event was produced
+ * by a stylus, examine the tool type returned by {@link MotionEvent#getToolType(int)}
+ * for each individual pointer.
+ * </p><p>
+ * A single touch event may multiple pointers with different tool types,
+ * such as an event that has one pointer with tool type
+ * {@link MotionEvent#TOOL_TYPE_FINGER} and another pointer with tool type
+ * {@link MotionEvent#TOOL_TYPE_STYLUS}. So it is important to examine
+ * the tool type of each pointer, regardless of the source reported
+ * by {@link MotionEvent#getSource()}.
+ * </p>
+ *
+ * @see #SOURCE_CLASS_POINTER
+ */
+ public static final int SOURCE_STYLUS = 0x00004000 | SOURCE_CLASS_POINTER;
+
+ /**
+ * The input device is a Bluetooth stylus.
+ * <p>
+ * Note that this bit merely indicates that an input device is capable of
+ * obtaining input from a Bluetooth stylus. To determine whether a given
+ * touch event was produced by a stylus, examine the tool type returned by
+ * {@link MotionEvent#getToolType(int)} for each individual pointer.
+ * </p><p>
+ * A single touch event may multiple pointers with different tool types,
+ * such as an event that has one pointer with tool type
+ * {@link MotionEvent#TOOL_TYPE_FINGER} and another pointer with tool type
+ * {@link MotionEvent#TOOL_TYPE_STYLUS}. So it is important to examine
+ * the tool type of each pointer, regardless of the source reported
+ * by {@link MotionEvent#getSource()}.
+ * </p><p>
+ * A bluetooth stylus generally receives its pressure and button state
+ * information from the stylus itself, and derives the rest from another
+ * source. For example, a Bluetooth stylus used in conjunction with a
+ * touchscreen would derive its contact position and pointer size from the
+ * touchscreen and may not be any more accurate than other tools such as
+ * fingers.
+ * </p>
+ *
+ * @see #SOURCE_STYLUS
+ * @see #SOURCE_CLASS_POINTER
+ */
+ public static final int SOURCE_BLUETOOTH_STYLUS =
+ 0x00008000 | SOURCE_STYLUS;
+
+ /**
+ * The input source is a trackball.
+ *
+ * @see #SOURCE_CLASS_TRACKBALL
+ */
+ public static final int SOURCE_TRACKBALL = 0x00010000 | SOURCE_CLASS_TRACKBALL;
+
+ /**
+ * The input source is a mouse device whose relative motions should be interpreted as
+ * navigation events.
+ *
+ * @see #SOURCE_CLASS_TRACKBALL
+ */
+ public static final int SOURCE_MOUSE_RELATIVE = 0x00020000 | SOURCE_CLASS_TRACKBALL;
+
+ /**
+ * The input source is a touch pad or digitizer tablet that is not
+ * associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
+ *
+ * @see #SOURCE_CLASS_POSITION
+ */
+ public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION;
+
+ /**
+ * The input source is a touch device whose motions should be interpreted as navigation events.
+ *
+ * For example, an upward swipe should be as an upward focus traversal in the same manner as
+ * pressing up on a D-Pad would be. Swipes to the left, right and down should be treated in a
+ * similar manner.
+ *
+ * @see #SOURCE_CLASS_NONE
+ */
+ public static final int SOURCE_TOUCH_NAVIGATION = 0x00200000 | SOURCE_CLASS_NONE;
+
+ /**
+ * The input source is a rotating encoder device whose motions should be interpreted as akin to
+ * those of a scroll wheel.
+ *
+ * @see #SOURCE_CLASS_NONE
+ */
+ public static final int SOURCE_ROTARY_ENCODER = 0x00400000 | SOURCE_CLASS_NONE;
+
+ /**
+ * The input source is a joystick.
+ * (It may also be a {@link #SOURCE_GAMEPAD}).
+ *
+ * @see #SOURCE_CLASS_JOYSTICK
+ */
+ public static final int SOURCE_JOYSTICK = 0x01000000 | SOURCE_CLASS_JOYSTICK;
+
+ /**
+ * The input source is a device connected through HDMI-based bus.
+ *
+ * The key comes in through HDMI-CEC or MHL signal line, and is treated as if it were
+ * generated by a locally connected DPAD or keyboard.
+ */
+ public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
+
+ /**
+ * A special input source constant that is used when filtering input devices
+ * to match devices that provide any type of input source.
+ */
+ public static final int SOURCE_ANY = 0xffffff00;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_X}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_X} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_X = MotionEvent.AXIS_X;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_Y}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_Y} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_Y = MotionEvent.AXIS_Y;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_PRESSURE}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_PRESSURE} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_PRESSURE = MotionEvent.AXIS_PRESSURE;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_SIZE}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_SIZE} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_SIZE = MotionEvent.AXIS_SIZE;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOUCH_MAJOR}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_TOUCH_MAJOR} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_TOUCH_MAJOR = MotionEvent.AXIS_TOUCH_MAJOR;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOUCH_MINOR}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_TOUCH_MINOR} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_TOUCH_MINOR = MotionEvent.AXIS_TOUCH_MINOR;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOOL_MAJOR}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_TOOL_MAJOR} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_TOOL_MAJOR = MotionEvent.AXIS_TOOL_MAJOR;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_TOOL_MINOR}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_TOOL_MINOR} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_TOOL_MINOR = MotionEvent.AXIS_TOOL_MINOR;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent#AXIS_ORIENTATION}.
+ *
+ * @see #getMotionRange
+ * @deprecated Use {@link MotionEvent#AXIS_ORIENTATION} instead.
+ */
+ @Deprecated
+ public static final int MOTION_RANGE_ORIENTATION = MotionEvent.AXIS_ORIENTATION;
+
+ /**
+ * There is no keyboard.
+ */
+ public static final int KEYBOARD_TYPE_NONE = 0;
+
+ /**
+ * The keyboard is not fully alphabetic. It may be a numeric keypad or an assortment
+ * of buttons that are not mapped as alphabetic keys suitable for text input.
+ */
+ public static final int KEYBOARD_TYPE_NON_ALPHABETIC = 1;
+
+ /**
+ * The keyboard supports a complement of alphabetic keys.
+ */
+ public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
+
+ private static final int MAX_RANGES = 1000;
+
+ public static final Parcelable.Creator<InputDevice> CREATOR =
+ new Parcelable.Creator<InputDevice>() {
+ public InputDevice createFromParcel(Parcel in) {
+ return new InputDevice(in);
+ }
+ public InputDevice[] newArray(int size) {
+ return new InputDevice[size];
+ }
+ };
+
+ // Called by native code.
+ private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
+ int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
+ KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
+ boolean hasButtonUnderPad) {
+ mId = id;
+ mGeneration = generation;
+ mControllerNumber = controllerNumber;
+ mName = name;
+ mVendorId = vendorId;
+ mProductId = productId;
+ mDescriptor = descriptor;
+ mIsExternal = isExternal;
+ mSources = sources;
+ mKeyboardType = keyboardType;
+ mKeyCharacterMap = keyCharacterMap;
+ mHasVibrator = hasVibrator;
+ mHasMicrophone = hasMicrophone;
+ mHasButtonUnderPad = hasButtonUnderPad;
+ mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
+ }
+
+ private InputDevice(Parcel in) {
+ mId = in.readInt();
+ mGeneration = in.readInt();
+ mControllerNumber = in.readInt();
+ mName = in.readString();
+ mVendorId = in.readInt();
+ mProductId = in.readInt();
+ mDescriptor = in.readString();
+ mIsExternal = in.readInt() != 0;
+ mSources = in.readInt();
+ mKeyboardType = in.readInt();
+ mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
+ mHasVibrator = in.readInt() != 0;
+ mHasMicrophone = in.readInt() != 0;
+ mHasButtonUnderPad = in.readInt() != 0;
+ mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
+
+ int numRanges = in.readInt();
+ if (numRanges > MAX_RANGES) {
+ numRanges = MAX_RANGES;
+ }
+
+ for (int i = 0; i < numRanges; i++) {
+ addMotionRange(in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
+ in.readFloat(), in.readFloat(), in.readFloat());
+ }
+ }
+
+ /**
+ * Gets information about the input device with the specified id.
+ * @param id The device id.
+ * @return The input device or null if not found.
+ */
+ public static InputDevice getDevice(int id) {
+ return InputManager.getInstance().getInputDevice(id);
+ }
+
+ /**
+ * Gets the ids of all input devices in the system.
+ * @return The input device ids.
+ */
+ public static int[] getDeviceIds() {
+ return InputManager.getInstance().getInputDeviceIds();
+ }
+
+ /**
+ * Gets the input device id.
+ * <p>
+ * Each input device receives a unique id when it is first configured
+ * by the system. The input device id may change when the system is restarted or if the
+ * input device is disconnected, reconnected or reconfigured at any time.
+ * If you require a stable identifier for a device that persists across
+ * boots and reconfigurations, use {@link #getDescriptor()}.
+ * </p>
+ *
+ * @return The input device id.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * The controller number for a given input device.
+ * <p>
+ * Each gamepad or joystick is given a unique, positive controller number when initially
+ * configured by the system. This number may change due to events such as device disconnects /
+ * reconnects or user initiated reassignment. Any change in number will trigger an event that
+ * can be observed by registering an {@link InputManager.InputDeviceListener}.
+ * </p>
+ * <p>
+ * All input devices which are not gamepads or joysticks will be assigned a controller number
+ * of 0.
+ * </p>
+ *
+ * @return The controller number of the device.
+ */
+ public int getControllerNumber() {
+ return mControllerNumber;
+ }
+
+ /**
+ * The set of identifying information for type of input device. This
+ * information can be used by the system to configure appropriate settings
+ * for the device.
+ *
+ * @return The identifier object for this device
+ * @hide
+ */
+ public InputDeviceIdentifier getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * Gets a generation number for this input device.
+ * The generation number is incremented whenever the device is reconfigured and its
+ * properties may have changed.
+ *
+ * @return The generation number.
+ *
+ * @hide
+ */
+ public int getGeneration() {
+ return mGeneration;
+ }
+
+ /**
+ * Gets the vendor id for the given device, if available.
+ * <p>
+ * A vendor id uniquely identifies the company who manufactured the device. A value of 0 will
+ * be assigned where a vendor id is not available.
+ * </p>
+ *
+ * @return The vendor id of a given device
+ */
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ /**
+ * Gets the product id for the given device, if available.
+ * <p>
+ * A product id uniquely identifies which product within the address space of a given vendor,
+ * identified by the device's vendor id. A value of 0 will be assigned where a product id is
+ * not available.
+ * </p>
+ *
+ * @return The product id of a given device
+ */
+ public int getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * Gets the input device descriptor, which is a stable identifier for an input device.
+ * <p>
+ * An input device descriptor uniquely identifies an input device. Its value
+ * is intended to be persistent across system restarts, and should not change even
+ * if the input device is disconnected, reconnected or reconfigured at any time.
+ * </p><p>
+ * It is possible for there to be multiple {@link InputDevice} instances that have the
+ * same input device descriptor. This might happen in situations where a single
+ * human input device registers multiple {@link InputDevice} instances (HID collections)
+ * that describe separate features of the device, such as a keyboard that also
+ * has a trackpad. Alternately, it may be that the input devices are simply
+ * indistinguishable, such as two keyboards made by the same manufacturer.
+ * </p><p>
+ * The input device descriptor returned by {@link #getDescriptor} should only be
+ * used when an application needs to remember settings associated with a particular
+ * input device. For all other purposes when referring to a logical
+ * {@link InputDevice} instance at runtime use the id returned by {@link #getId()}.
+ * </p>
+ *
+ * @return The input device descriptor.
+ */
+ public String getDescriptor() {
+ return mDescriptor;
+ }
+
+ /**
+ * Returns true if the device is a virtual input device rather than a real one,
+ * such as the virtual keyboard (see {@link KeyCharacterMap#VIRTUAL_KEYBOARD}).
+ * <p>
+ * Virtual input devices are provided to implement system-level functionality
+ * and should not be seen or configured by users.
+ * </p>
+ *
+ * @return True if the device is virtual.
+ *
+ * @see KeyCharacterMap#VIRTUAL_KEYBOARD
+ */
+ public boolean isVirtual() {
+ return mId < 0;
+ }
+
+ /**
+ * Returns true if the device is external (connected to USB or Bluetooth or some other
+ * peripheral bus), otherwise it is built-in.
+ *
+ * @return True if the device is external.
+ *
+ * @hide
+ */
+ public boolean isExternal() {
+ return mIsExternal;
+ }
+
+ /**
+ * Returns true if the device is a full keyboard.
+ *
+ * @return True if the device is a full keyboard.
+ *
+ * @hide
+ */
+ public boolean isFullKeyboard() {
+ return (mSources & SOURCE_KEYBOARD) == SOURCE_KEYBOARD
+ && mKeyboardType == KEYBOARD_TYPE_ALPHABETIC;
+ }
+
+ /**
+ * Gets the name of this input device.
+ * @return The input device name.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the input sources supported by this input device as a combined bitfield.
+ * @return The supported input sources.
+ */
+ public int getSources() {
+ return mSources;
+ }
+
+ /**
+ * Determines whether the input device supports the given source or sources.
+ *
+ * @param source The input source or sources to check against. This can be a generic device
+ * type such as {@link InputDevice#SOURCE_MOUSE}, a more generic device class, such as
+ * {@link InputDevice#SOURCE_CLASS_POINTER}, or a combination of sources bitwise ORed together.
+ * @return Whether the device can produce all of the given sources.
+ */
+ public boolean supportsSource(int source) {
+ return (mSources & source) == source;
+ }
+
+ /**
+ * Gets the keyboard type.
+ * @return The keyboard type.
+ */
+ public int getKeyboardType() {
+ return mKeyboardType;
+ }
+
+ /**
+ * Gets the key character map associated with this input device.
+ * @return The key character map.
+ */
+ public KeyCharacterMap getKeyCharacterMap() {
+ return mKeyCharacterMap;
+ }
+
+ /**
+ * Gets whether the device is capable of producing the list of keycodes.
+ * @param keys The list of android keycodes to check for.
+ * @return An array of booleans where each member specifies whether the device is capable of
+ * generating the keycode given by the corresponding value at the same index in the keys array.
+ */
+ public boolean[] hasKeys(int... keys) {
+ return InputManager.getInstance().deviceHasKeys(mId, keys);
+ }
+
+ /**
+ * Gets information about the range of values for a particular {@link MotionEvent} axis.
+ * If the device supports multiple sources, the same axis may have different meanings
+ * for each source. Returns information about the first axis found for any source.
+ * To obtain information about the axis for a specific source, use
+ * {@link #getMotionRange(int, int)}.
+ *
+ * @param axis The axis constant.
+ * @return The range of values, or null if the requested axis is not
+ * supported by the device.
+ *
+ * @see MotionEvent#AXIS_X
+ * @see MotionEvent#AXIS_Y
+ */
+ public MotionRange getMotionRange(int axis) {
+ final int numRanges = mMotionRanges.size();
+ for (int i = 0; i < numRanges; i++) {
+ final MotionRange range = mMotionRanges.get(i);
+ if (range.mAxis == axis) {
+ return range;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets information about the range of values for a particular {@link MotionEvent} axis
+ * used by a particular source on the device.
+ * If the device supports multiple sources, the same axis may have different meanings
+ * for each source.
+ *
+ * @param axis The axis constant.
+ * @param source The source for which to return information.
+ * @return The range of values, or null if the requested axis is not
+ * supported by the device.
+ *
+ * @see MotionEvent#AXIS_X
+ * @see MotionEvent#AXIS_Y
+ */
+ public MotionRange getMotionRange(int axis, int source) {
+ final int numRanges = mMotionRanges.size();
+ for (int i = 0; i < numRanges; i++) {
+ final MotionRange range = mMotionRanges.get(i);
+ if (range.mAxis == axis && range.mSource == source) {
+ return range;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the ranges for all axes supported by the device.
+ * @return The motion ranges for the device.
+ *
+ * @see #getMotionRange(int, int)
+ */
+ public List<MotionRange> getMotionRanges() {
+ return mMotionRanges;
+ }
+
+ // Called from native code.
+ private void addMotionRange(int axis, int source,
+ float min, float max, float flat, float fuzz, float resolution) {
+ mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz, resolution));
+ }
+
+ /**
+ * Gets the vibrator service associated with the device, if there is one.
+ * Even if the device does not have a vibrator, the result is never null.
+ * Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
+ * present.
+ *
+ * Note that the vibrator associated with the device may be different from
+ * the system vibrator. To obtain an instance of the system vibrator instead, call
+ * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as argument.
+ *
+ * @return The vibrator service associated with the device, never null.
+ */
+ public Vibrator getVibrator() {
+ synchronized (mMotionRanges) {
+ if (mVibrator == null) {
+ if (mHasVibrator) {
+ mVibrator = InputManager.getInstance().getInputDeviceVibrator(mId);
+ } else {
+ mVibrator = NullVibrator.getInstance();
+ }
+ }
+ return mVibrator;
+ }
+ }
+
+ /**
+ * Returns true if input device is enabled.
+ * @return Whether the input device is enabled.
+ */
+ public boolean isEnabled() {
+ return InputManager.getInstance().isInputDeviceEnabled(mId);
+ }
+
+ /**
+ * Enables the input device.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE)
+ @TestApi
+ public void enable() {
+ InputManager.getInstance().enableInputDevice(mId);
+ }
+
+ /**
+ * Disables the input device.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE)
+ @TestApi
+ public void disable() {
+ InputManager.getInstance().disableInputDevice(mId);
+ }
+
+ /**
+ * Reports whether the device has a built-in microphone.
+ * @return Whether the device has a built-in microphone.
+ */
+ public boolean hasMicrophone() {
+ return mHasMicrophone;
+ }
+
+ /**
+ * Reports whether the device has a button under its touchpad
+ * @return Whether the device has a button under its touchpad
+ * @hide
+ */
+ public boolean hasButtonUnderPad() {
+ return mHasButtonUnderPad;
+ }
+
+ /**
+ * Sets the current pointer type.
+ * @param pointerType the type of the pointer icon.
+ * @hide
+ */
+ public void setPointerType(int pointerType) {
+ InputManager.getInstance().setPointerIconType(pointerType);
+ }
+
+ /**
+ * Specifies the current custom pointer.
+ * @param icon the icon data.
+ * @hide
+ */
+ public void setCustomPointerIcon(PointerIcon icon) {
+ InputManager.getInstance().setCustomPointerIcon(icon);
+ }
+
+ /**
+ * Provides information about the range of values for a particular {@link MotionEvent} axis.
+ *
+ * @see InputDevice#getMotionRange(int)
+ */
+ public static final class MotionRange {
+ private int mAxis;
+ private int mSource;
+ private float mMin;
+ private float mMax;
+ private float mFlat;
+ private float mFuzz;
+ private float mResolution;
+
+ private MotionRange(int axis, int source, float min, float max, float flat, float fuzz,
+ float resolution) {
+ mAxis = axis;
+ mSource = source;
+ mMin = min;
+ mMax = max;
+ mFlat = flat;
+ mFuzz = fuzz;
+ mResolution = resolution;
+ }
+
+ /**
+ * Gets the axis id.
+ * @return The axis id.
+ */
+ public int getAxis() {
+ return mAxis;
+ }
+
+ /**
+ * Gets the source for which the axis is defined.
+ * @return The source.
+ */
+ public int getSource() {
+ return mSource;
+ }
+
+
+ /**
+ * Determines whether the event is from the given source.
+ *
+ * @param source The input source to check against. This can be a specific device type,
+ * such as {@link InputDevice#SOURCE_TOUCH_NAVIGATION}, or a more generic device class,
+ * such as {@link InputDevice#SOURCE_CLASS_POINTER}.
+ * @return Whether the event is from the given source.
+ */
+ public boolean isFromSource(int source) {
+ return (getSource() & source) == source;
+ }
+
+ /**
+ * Gets the inclusive minimum value for the axis.
+ * @return The inclusive minimum value.
+ */
+ public float getMin() {
+ return mMin;
+ }
+
+ /**
+ * Gets the inclusive maximum value for the axis.
+ * @return The inclusive maximum value.
+ */
+ public float getMax() {
+ return mMax;
+ }
+
+ /**
+ * Gets the range of the axis (difference between maximum and minimum).
+ * @return The range of values.
+ */
+ public float getRange() {
+ return mMax - mMin;
+ }
+
+ /**
+ * Gets the extent of the center flat position with respect to this axis.
+ * <p>
+ * For example, a flat value of 8 means that the center position is between -8 and +8.
+ * This value is mainly useful for calibrating self-centering devices.
+ * </p>
+ * @return The extent of the center flat position.
+ */
+ public float getFlat() {
+ return mFlat;
+ }
+
+ /**
+ * Gets the error tolerance for input device measurements with respect to this axis.
+ * <p>
+ * For example, a value of 2 indicates that the measured value may be up to +/- 2 units
+ * away from the actual value due to noise and device sensitivity limitations.
+ * </p>
+ * @return The error tolerance.
+ */
+ public float getFuzz() {
+ return mFuzz;
+ }
+
+ /**
+ * Gets the resolution for input device measurements with respect to this axis.
+ * @return The resolution in units per millimeter, or units per radian for rotational axes.
+ */
+ public float getResolution() {
+ return mResolution;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mId);
+ out.writeInt(mGeneration);
+ out.writeInt(mControllerNumber);
+ out.writeString(mName);
+ out.writeInt(mVendorId);
+ out.writeInt(mProductId);
+ out.writeString(mDescriptor);
+ out.writeInt(mIsExternal ? 1 : 0);
+ out.writeInt(mSources);
+ out.writeInt(mKeyboardType);
+ mKeyCharacterMap.writeToParcel(out, flags);
+ out.writeInt(mHasVibrator ? 1 : 0);
+ out.writeInt(mHasMicrophone ? 1 : 0);
+ out.writeInt(mHasButtonUnderPad ? 1 : 0);
+
+ final int numRanges = mMotionRanges.size();
+ out.writeInt(numRanges);
+ for (int i = 0; i < numRanges; i++) {
+ MotionRange range = mMotionRanges.get(i);
+ out.writeInt(range.mAxis);
+ out.writeInt(range.mSource);
+ out.writeFloat(range.mMin);
+ out.writeFloat(range.mMax);
+ out.writeFloat(range.mFlat);
+ out.writeFloat(range.mFuzz);
+ out.writeFloat(range.mResolution);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder description = new StringBuilder();
+ description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
+ description.append(" Descriptor: ").append(mDescriptor).append("\n");
+ description.append(" Generation: ").append(mGeneration).append("\n");
+ description.append(" Location: ").append(mIsExternal ? "external" : "built-in").append("\n");
+
+ description.append(" Keyboard Type: ");
+ switch (mKeyboardType) {
+ case KEYBOARD_TYPE_NONE:
+ description.append("none");
+ break;
+ case KEYBOARD_TYPE_NON_ALPHABETIC:
+ description.append("non-alphabetic");
+ break;
+ case KEYBOARD_TYPE_ALPHABETIC:
+ description.append("alphabetic");
+ break;
+ }
+ description.append("\n");
+
+ description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
+
+ description.append(" Has mic: ").append(mHasMicrophone).append("\n");
+
+ description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
+ appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
+ appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
+ appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHSCREEN, "touchscreen");
+ appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
+ appendSourceDescriptionIfApplicable(description, SOURCE_STYLUS, "stylus");
+ appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
+ appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE_RELATIVE, "mouse_relative");
+ appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
+ appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK, "joystick");
+ appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
+ description.append(" )\n");
+
+ final int numAxes = mMotionRanges.size();
+ for (int i = 0; i < numAxes; i++) {
+ MotionRange range = mMotionRanges.get(i);
+ description.append(" ").append(MotionEvent.axisToString(range.mAxis));
+ description.append(": source=0x").append(Integer.toHexString(range.mSource));
+ description.append(" min=").append(range.mMin);
+ description.append(" max=").append(range.mMax);
+ description.append(" flat=").append(range.mFlat);
+ description.append(" fuzz=").append(range.mFuzz);
+ description.append(" resolution=").append(range.mResolution);
+ description.append("\n");
+ }
+ return description.toString();
+ }
+
+ private void appendSourceDescriptionIfApplicable(StringBuilder description, int source,
+ String sourceName) {
+ if ((mSources & source) == source) {
+ description.append(" ");
+ description.append(sourceName);
+ }
+ }
+}
diff --git a/android/view/InputEvent.java b/android/view/InputEvent.java
new file mode 100644
index 00000000..e2ad3ad4
--- /dev/null
+++ b/android/view/InputEvent.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2010 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Common base class for input events.
+ */
+public abstract class InputEvent implements Parcelable {
+ /** @hide */
+ protected static final int PARCEL_TOKEN_MOTION_EVENT = 1;
+ /** @hide */
+ protected static final int PARCEL_TOKEN_KEY_EVENT = 2;
+
+ // Next sequence number.
+ private static final AtomicInteger mNextSeq = new AtomicInteger();
+
+ /** @hide */
+ protected int mSeq;
+
+ /** @hide */
+ protected boolean mRecycled;
+
+ private static final boolean TRACK_RECYCLED_LOCATION = false;
+ private RuntimeException mRecycledLocation;
+
+ /*package*/ InputEvent() {
+ mSeq = mNextSeq.getAndIncrement();
+ }
+
+ /**
+ * Gets the id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device
+ * and maps to the default keymap. The other numbers are arbitrary and
+ * you shouldn't depend on the values.
+ *
+ * @return The device id.
+ * @see InputDevice#getDevice
+ */
+ public abstract int getDeviceId();
+
+ /**
+ * Gets the device that this event came from.
+ *
+ * @return The device, or null if unknown.
+ */
+ public final InputDevice getDevice() {
+ return InputDevice.getDevice(getDeviceId());
+ }
+
+ /**
+ * Gets the source of the event.
+ *
+ * @return The event source or {@link InputDevice#SOURCE_UNKNOWN} if unknown.
+ * @see InputDevice#getSources
+ */
+ public abstract int getSource();
+
+ /**
+ * Modifies the source of the event.
+ *
+ * @param source The new source.
+ * @hide
+ */
+ public abstract void setSource(int source);
+
+ /**
+ * Determines whether the event is from the given source.
+ *
+ * @param source The input source to check against. This can be a specific device type, such as
+ * {@link InputDevice#SOURCE_TOUCH_NAVIGATION}, or a more generic device class, such as
+ * {@link InputDevice#SOURCE_CLASS_POINTER}.
+ * @return Whether the event is from the given source.
+ */
+ public boolean isFromSource(int source) {
+ return (getSource() & source) == source;
+ }
+
+ /**
+ * Copies the event.
+ *
+ * @return A deep copy of the event.
+ * @hide
+ */
+ public abstract InputEvent copy();
+
+ /**
+ * Recycles the event.
+ * This method should only be used by the system since applications do not
+ * expect {@link KeyEvent} objects to be recycled, although {@link MotionEvent}
+ * objects are fine. See {@link KeyEvent#recycle()} for details.
+ * @hide
+ */
+ public void recycle() {
+ if (TRACK_RECYCLED_LOCATION) {
+ if (mRecycledLocation != null) {
+ throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+ }
+ mRecycledLocation = new RuntimeException("Last recycled here");
+ } else {
+ if (mRecycled) {
+ throw new RuntimeException(toString() + " recycled twice!");
+ }
+ mRecycled = true;
+ }
+ }
+
+ /**
+ * Conditionally recycled the event if it is appropriate to do so after
+ * dispatching the event to an application.
+ *
+ * If the event is a {@link MotionEvent} then it is recycled.
+ *
+ * If the event is a {@link KeyEvent} then it is NOT recycled, because applications
+ * expect key events to be immutable so once the event has been dispatched to
+ * the application we can no longer recycle it.
+ * @hide
+ */
+ public void recycleIfNeededAfterDispatch() {
+ recycle();
+ }
+
+ /**
+ * Reinitializes the event on reuse (after recycling).
+ * @hide
+ */
+ protected void prepareForReuse() {
+ mRecycled = false;
+ mRecycledLocation = null;
+ mSeq = mNextSeq.getAndIncrement();
+ }
+
+ /**
+ * Gets a private flag that indicates when the system has detected that this input event
+ * may be inconsistent with respect to the sequence of previously delivered input events,
+ * such as when a key up event is sent but the key was not down or when a pointer
+ * move event is sent but the pointer is not down.
+ *
+ * @return True if this event is tainted.
+ * @hide
+ */
+ public abstract boolean isTainted();
+
+ /**
+ * Sets a private flag that indicates when the system has detected that this input event
+ * may be inconsistent with respect to the sequence of previously delivered input events,
+ * such as when a key up event is sent but the key was not down or when a pointer
+ * move event is sent but the pointer is not down.
+ *
+ * @param tainted True if this event is tainted.
+ * @hide
+ */
+ public abstract void setTainted(boolean tainted);
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ */
+ public abstract long getEventTime();
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond (instead of millisecond) precision.
+ * <p>
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ * </p>
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond (instead of millisecond) precision.
+ *
+ * @hide
+ */
+ public abstract long getEventTimeNano();
+
+ /**
+ * Marks the input event as being canceled.
+ *
+ * @hide
+ */
+ public abstract void cancel();
+
+ /**
+ * Gets the unique sequence number of this event.
+ * Every input event that is created or received by a process has a
+ * unique sequence number. Moreover, a new sequence number is obtained
+ * each time an event object is recycled.
+ *
+ * Sequence numbers are only guaranteed to be locally unique within a process.
+ * Sequence numbers are not preserved when events are parceled.
+ *
+ * @return The unique sequence number of this event.
+ * @hide
+ */
+ public int getSequenceNumber() {
+ return mSeq;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<InputEvent> CREATOR
+ = new Parcelable.Creator<InputEvent>() {
+ public InputEvent createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_KEY_EVENT) {
+ return KeyEvent.createFromParcelBody(in);
+ } else if (token == PARCEL_TOKEN_MOTION_EVENT) {
+ return MotionEvent.createFromParcelBody(in);
+ } else {
+ throw new IllegalStateException("Unexpected input event type token in parcel.");
+ }
+ }
+
+ public InputEvent[] newArray(int size) {
+ return new InputEvent[size];
+ }
+ };
+}
diff --git a/android/view/InputEventConsistencyVerifier.java b/android/view/InputEventConsistencyVerifier.java
new file mode 100644
index 00000000..7e8ec046
--- /dev/null
+++ b/android/view/InputEventConsistencyVerifier.java
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2010 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.os.Build;
+import android.util.Log;
+
+/**
+ * Checks whether a sequence of input events is self-consistent.
+ * Logs a description of each problem detected.
+ * <p>
+ * When a problem is detected, the event is tainted. This mechanism prevents the same
+ * error from being reported multiple times.
+ * </p>
+ *
+ * @hide
+ */
+public final class InputEventConsistencyVerifier {
+ private static final boolean IS_ENG_BUILD = Build.IS_ENG;
+
+ private static final String EVENT_TYPE_KEY = "KeyEvent";
+ private static final String EVENT_TYPE_TRACKBALL = "TrackballEvent";
+ private static final String EVENT_TYPE_TOUCH = "TouchEvent";
+ private static final String EVENT_TYPE_GENERIC_MOTION = "GenericMotionEvent";
+
+ // The number of recent events to log when a problem is detected.
+ // Can be set to 0 to disable logging recent events but the runtime overhead of
+ // this feature is negligible on current hardware.
+ private static final int RECENT_EVENTS_TO_LOG = 5;
+
+ // The object to which the verifier is attached.
+ private final Object mCaller;
+
+ // Consistency verifier flags.
+ private final int mFlags;
+
+ // Tag for logging which a client can set to help distinguish the output
+ // from different verifiers since several can be active at the same time.
+ // If not provided defaults to the simple class name.
+ private final String mLogTag;
+
+ // The most recently checked event and the nesting level at which it was checked.
+ // This is only set when the verifier is called from a nesting level greater than 0
+ // so that the verifier can detect when it has been asked to verify the same event twice.
+ // It does not make sense to examine the contents of the last event since it may have
+ // been recycled.
+ private int mLastEventSeq;
+ private String mLastEventType;
+ private int mLastNestingLevel;
+
+ // Copy of the most recent events.
+ private InputEvent[] mRecentEvents;
+ private boolean[] mRecentEventsUnhandled;
+ private int mMostRecentEventIndex;
+
+ // Current event and its type.
+ private InputEvent mCurrentEvent;
+ private String mCurrentEventType;
+
+ // Linked list of key state objects.
+ private KeyState mKeyStateList;
+
+ // Current state of the trackball.
+ private boolean mTrackballDown;
+ private boolean mTrackballUnhandled;
+
+ // Bitfield of pointer ids that are currently down.
+ // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
+ // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ private int mTouchEventStreamPointers;
+
+ // The device id and source of the current stream of touch events.
+ private int mTouchEventStreamDeviceId = -1;
+ private int mTouchEventStreamSource;
+
+ // Set to true when we discover that the touch event stream is inconsistent.
+ // Reset on down or cancel.
+ private boolean mTouchEventStreamIsTainted;
+
+ // Set to true if the touch event stream is partially unhandled.
+ private boolean mTouchEventStreamUnhandled;
+
+ // Set to true if we received hover enter.
+ private boolean mHoverEntered;
+
+ // The bitset of buttons which we've received ACTION_BUTTON_PRESS for.
+ private int mButtonsPressed;
+
+ // The current violation message.
+ private StringBuilder mViolationMessage;
+
+ /**
+ * Indicates that the verifier is intended to act on raw device input event streams.
+ * Disables certain checks for invariants that are established by the input dispatcher
+ * itself as it delivers input events, such as key repeating behavior.
+ */
+ public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0;
+
+ /**
+ * Creates an input consistency verifier.
+ * @param caller The object to which the verifier is attached.
+ * @param flags Flags to the verifier, or 0 if none.
+ */
+ public InputEventConsistencyVerifier(Object caller, int flags) {
+ this(caller, flags, null);
+ }
+
+ /**
+ * Creates an input consistency verifier.
+ * @param caller The object to which the verifier is attached.
+ * @param flags Flags to the verifier, or 0 if none.
+ * @param logTag Tag for logging. If null defaults to the short class name.
+ */
+ public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
+ this.mCaller = caller;
+ this.mFlags = flags;
+ this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
+ }
+
+ /**
+ * Determines whether the instrumentation should be enabled.
+ * @return True if it should be enabled.
+ */
+ public static boolean isInstrumentationEnabled() {
+ return IS_ENG_BUILD;
+ }
+
+ /**
+ * Resets the state of the input event consistency verifier.
+ */
+ public void reset() {
+ mLastEventSeq = -1;
+ mLastNestingLevel = 0;
+ mTrackballDown = false;
+ mTrackballUnhandled = false;
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mHoverEntered = false;
+ mButtonsPressed = 0;
+
+ while (mKeyStateList != null) {
+ final KeyState state = mKeyStateList;
+ mKeyStateList = state.next;
+ state.recycle();
+ }
+ }
+
+ /**
+ * Checks an arbitrary input event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onInputEvent(InputEvent event, int nestingLevel) {
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ onKeyEvent(keyEvent, nestingLevel);
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ onTouchEvent(motionEvent, nestingLevel);
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ onTrackballEvent(motionEvent, nestingLevel);
+ } else {
+ onGenericMotionEvent(motionEvent, nestingLevel);
+ }
+ }
+ }
+
+ /**
+ * Checks a key event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onKeyEvent(KeyEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+ final int keyCode = event.getKeyCode();
+ switch (action) {
+ case KeyEvent.ACTION_DOWN: {
+ KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ // If the key is already down, ensure it is a repeat.
+ // We don't perform this check when processing raw device input
+ // because the input dispatcher itself is responsible for setting
+ // the key repeat count before it delivers input events.
+ if (state.unhandled) {
+ state.unhandled = false;
+ } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+ && event.getRepeatCount() == 0) {
+ problem("ACTION_DOWN but key is already down and this event "
+ + "is not a key repeat.");
+ }
+ } else {
+ addKeyState(deviceId, source, keyCode);
+ }
+ break;
+ }
+ case KeyEvent.ACTION_UP: {
+ KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true);
+ if (state == null) {
+ problem("ACTION_UP but key was not down.");
+ } else {
+ state.recycle();
+ }
+ break;
+ }
+ case KeyEvent.ACTION_MULTIPLE:
+ break;
+ default:
+ problem("Invalid action " + KeyEvent.actionToString(action)
+ + " for key event.");
+ break;
+ }
+ } finally {
+ finishEvent();
+ }
+ }
+
+ /**
+ * Checks a trackball event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onTrackballEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mTrackballDown && !mTrackballUnhandled) {
+ problem("ACTION_DOWN but trackball is already down.");
+ } else {
+ mTrackballDown = true;
+ mTrackballUnhandled = false;
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ if (!mTrackballDown) {
+ problem("ACTION_UP but trackball is not down.");
+ } else {
+ mTrackballDown = false;
+ mTrackballUnhandled = false;
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action " + MotionEvent.actionToString(action)
+ + " for trackball event.");
+ break;
+ }
+
+ if (mTrackballDown && event.getPressure() <= 0) {
+ problem("Trackball is down but pressure is not greater than 0.");
+ } else if (!mTrackballDown && event.getPressure() != 0) {
+ problem("Trackball is up but pressure is not equal to 0.");
+ }
+ } else {
+ problem("Source was not SOURCE_CLASS_TRACKBALL.");
+ }
+ } finally {
+ finishEvent();
+ }
+ }
+
+ /**
+ * Checks a touch event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onTouchEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) {
+ return;
+ }
+
+ final int action = event.getAction();
+ final boolean newStream = action == MotionEvent.ACTION_DOWN
+ || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE;
+ if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) {
+ mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mTouchEventStreamPointers = 0;
+ }
+ if (mTouchEventStreamIsTainted) {
+ event.setTainted(true);
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+
+ if (!newStream && mTouchEventStreamDeviceId != -1
+ && (mTouchEventStreamDeviceId != deviceId
+ || mTouchEventStreamSource != source)) {
+ problem("Touch event stream contains events from multiple sources: "
+ + "previous device id " + mTouchEventStreamDeviceId
+ + ", previous source " + Integer.toHexString(mTouchEventStreamSource)
+ + ", new device id " + deviceId
+ + ", new source " + Integer.toHexString(source));
+ }
+ mTouchEventStreamDeviceId = deviceId;
+ mTouchEventStreamSource = source;
+
+ final int pointerCount = event.getPointerCount();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mTouchEventStreamPointers != 0) {
+ problem("ACTION_DOWN but pointers are already down. "
+ + "Probably missing ACTION_UP from previous gesture.");
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamPointers = 1 << event.getPointerId(0);
+ break;
+ case MotionEvent.ACTION_UP:
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ break;
+ case MotionEvent.ACTION_MOVE: {
+ final int expectedPointerCount =
+ Integer.bitCount(mTouchEventStreamPointers);
+ if (pointerCount != expectedPointerCount) {
+ problem("ACTION_MOVE contained " + pointerCount
+ + " pointers but there are currently "
+ + expectedPointerCount + " pointers down.");
+ mTouchEventStreamIsTainted = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ break;
+ case MotionEvent.ACTION_OUTSIDE:
+ if (mTouchEventStreamPointers != 0) {
+ problem("ACTION_OUTSIDE but pointers are still down.");
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamIsTainted = false;
+ break;
+ default: {
+ final int actionMasked = event.getActionMasked();
+ final int actionIndex = event.getActionIndex();
+ if (actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mTouchEventStreamPointers == 0) {
+ problem("ACTION_POINTER_DOWN but no other pointers were down.");
+ mTouchEventStreamIsTainted = true;
+ }
+ if (actionIndex < 0 || actionIndex >= pointerCount) {
+ problem("ACTION_POINTER_DOWN index is " + actionIndex
+ + " but the pointer count is " + pointerCount + ".");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ final int id = event.getPointerId(actionIndex);
+ final int idBit = 1 << id;
+ if ((mTouchEventStreamPointers & idBit) != 0) {
+ problem("ACTION_POINTER_DOWN specified pointer id " + id
+ + " which is already down.");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ mTouchEventStreamPointers |= idBit;
+ }
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ if (actionIndex < 0 || actionIndex >= pointerCount) {
+ problem("ACTION_POINTER_UP index is " + actionIndex
+ + " but the pointer count is " + pointerCount + ".");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ final int id = event.getPointerId(actionIndex);
+ final int idBit = 1 << id;
+ if ((mTouchEventStreamPointers & idBit) == 0) {
+ problem("ACTION_POINTER_UP specified pointer id " + id
+ + " which is not currently down.");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ mTouchEventStreamPointers &= ~idBit;
+ }
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ } else {
+ problem("Invalid action " + MotionEvent.actionToString(action)
+ + " for touch event.");
+ }
+ break;
+ }
+ }
+ } else {
+ problem("Source was not SOURCE_CLASS_POINTER.");
+ }
+ } finally {
+ finishEvent();
+ }
+ }
+
+ /**
+ * Checks a generic motion event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int source = event.getSource();
+ final int buttonState = event.getButtonState();
+ final int actionButton = event.getActionButton();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ ensurePointerCountIsOneForThisAction(event);
+ mHoverEntered = true;
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ ensurePointerCountIsOneForThisAction(event);
+ if (!mHoverEntered) {
+ problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER");
+ }
+ mHoverEntered = false;
+ break;
+ case MotionEvent.ACTION_SCROLL:
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_BUTTON_PRESS:
+ ensureActionButtonIsNonZeroForThisAction(event);
+ if ((mButtonsPressed & actionButton) != 0) {
+ problem("Action button for ACTION_BUTTON_PRESS event is " +
+ actionButton + ", but it has already been pressed and " +
+ "has yet to be released.");
+ }
+
+ mButtonsPressed |= actionButton;
+ // The system will automatically mirror the stylus buttons onto the button
+ // state as the old set of generic buttons for apps targeting pre-M. If
+ // it looks this has happened, go ahead and set the generic buttons as
+ // pressed to prevent spurious errors.
+ if (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY &&
+ (buttonState & MotionEvent.BUTTON_SECONDARY) != 0) {
+ mButtonsPressed |= MotionEvent.BUTTON_SECONDARY;
+ } else if (actionButton == MotionEvent.BUTTON_STYLUS_SECONDARY &&
+ (buttonState & MotionEvent.BUTTON_TERTIARY) != 0) {
+ mButtonsPressed |= MotionEvent.BUTTON_TERTIARY;
+ }
+
+ if (mButtonsPressed != buttonState) {
+ problem(String.format("Reported button state differs from " +
+ "expected button state based on press and release events. " +
+ "Is 0x%08x but expected 0x%08x.",
+ buttonState, mButtonsPressed));
+ }
+ break;
+ case MotionEvent.ACTION_BUTTON_RELEASE:
+ ensureActionButtonIsNonZeroForThisAction(event);
+ if ((mButtonsPressed & actionButton) != actionButton) {
+ problem("Action button for ACTION_BUTTON_RELEASE event is " +
+ actionButton + ", but it was either never pressed or has " +
+ "already been released.");
+ }
+
+ mButtonsPressed &= ~actionButton;
+ // The system will automatically mirror the stylus buttons onto the button
+ // state as the old set of generic buttons for apps targeting pre-M. If
+ // it looks this has happened, go ahead and set the generic buttons as
+ // released to prevent spurious errors.
+ if (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY &&
+ (buttonState & MotionEvent.BUTTON_SECONDARY) == 0) {
+ mButtonsPressed &= ~MotionEvent.BUTTON_SECONDARY;
+ } else if (actionButton == MotionEvent.BUTTON_STYLUS_SECONDARY &&
+ (buttonState & MotionEvent.BUTTON_TERTIARY) == 0) {
+ mButtonsPressed &= ~MotionEvent.BUTTON_TERTIARY;
+ }
+
+ if (mButtonsPressed != buttonState) {
+ problem(String.format("Reported button state differs from " +
+ "expected button state based on press and release events. " +
+ "Is 0x%08x but expected 0x%08x.",
+ buttonState, mButtonsPressed));
+ }
+ break;
+ default:
+ problem("Invalid action for generic pointer event.");
+ break;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action for generic joystick event.");
+ break;
+ }
+ }
+ } finally {
+ finishEvent();
+ }
+ }
+
+ /**
+ * Notifies the verifier that a given event was unhandled and the rest of the
+ * trace for the event should be ignored.
+ * This method should only be called if the event was previously checked by
+ * the consistency verifier using {@link #onInputEvent} and other methods.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+ if (nestingLevel != mLastNestingLevel) {
+ return;
+ }
+
+ if (mRecentEventsUnhandled != null) {
+ mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+ }
+
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ final int deviceId = keyEvent.getDeviceId();
+ final int source = keyEvent.getSource();
+ final int keyCode = keyEvent.getKeyCode();
+ final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ state.unhandled = true;
+ }
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ mTouchEventStreamUnhandled = true;
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (mTrackballDown) {
+ mTrackballUnhandled = true;
+ }
+ }
+ }
+ }
+
+ private void ensureMetaStateIsNormalized(int metaState) {
+ final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
+ if (normalizedMetaState != metaState) {
+ problem(String.format("Metastate not normalized. Was 0x%08x but expected 0x%08x.",
+ metaState, normalizedMetaState));
+ }
+ }
+
+ private void ensurePointerCountIsOneForThisAction(MotionEvent event) {
+ final int pointerCount = event.getPointerCount();
+ if (pointerCount != 1) {
+ problem("Pointer count is " + pointerCount + " but it should always be 1 for "
+ + MotionEvent.actionToString(event.getAction()));
+ }
+ }
+
+ private void ensureActionButtonIsNonZeroForThisAction(MotionEvent event) {
+ final int actionButton = event.getActionButton();
+ if (actionButton == 0) {
+ problem("No action button set. Action button should always be non-zero for " +
+ MotionEvent.actionToString(event.getAction()));
+
+ }
+ }
+
+ private void ensureHistorySizeIsZeroForThisAction(MotionEvent event) {
+ final int historySize = event.getHistorySize();
+ if (historySize != 0) {
+ problem("History size is " + historySize + " but it should always be 0 for "
+ + MotionEvent.actionToString(event.getAction()));
+ }
+ }
+
+ private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
+ // Ignore the event if we already checked it at a higher nesting level.
+ final int seq = event.getSequenceNumber();
+ if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel
+ && eventType == mLastEventType) {
+ return false;
+ }
+
+ if (nestingLevel > 0) {
+ mLastEventSeq = seq;
+ mLastEventType = eventType;
+ mLastNestingLevel = nestingLevel;
+ } else {
+ mLastEventSeq = -1;
+ mLastEventType = null;
+ mLastNestingLevel = 0;
+ }
+
+ mCurrentEvent = event;
+ mCurrentEventType = eventType;
+ return true;
+ }
+
+ private void finishEvent() {
+ if (mViolationMessage != null && mViolationMessage.length() != 0) {
+ if (!mCurrentEvent.isTainted()) {
+ // Write a log message only if the event was not already tainted.
+ mViolationMessage.append("\n in ").append(mCaller);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, 0, mCurrentEvent, false);
+
+ if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
+ mViolationMessage.append("\n -- recent events --");
+ for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
+ final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
+ % RECENT_EVENTS_TO_LOG;
+ final InputEvent event = mRecentEvents[index];
+ if (event == null) {
+ break;
+ }
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
+ }
+ }
+
+ Log.d(mLogTag, mViolationMessage.toString());
+
+ // Taint the event so that we do not generate additional violations from it
+ // further downstream.
+ mCurrentEvent.setTainted(true);
+ }
+ mViolationMessage.setLength(0);
+ }
+
+ if (RECENT_EVENTS_TO_LOG != 0) {
+ if (mRecentEvents == null) {
+ mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+ mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
+ }
+ final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
+ mMostRecentEventIndex = index;
+ if (mRecentEvents[index] != null) {
+ mRecentEvents[index].recycle();
+ }
+ mRecentEvents[index] = mCurrentEvent.copy();
+ mRecentEventsUnhandled[index] = false;
+ }
+
+ mCurrentEvent = null;
+ mCurrentEventType = null;
+ }
+
+ private static void appendEvent(StringBuilder message, int index,
+ InputEvent event, boolean unhandled) {
+ message.append(index).append(": sent at ").append(event.getEventTimeNano());
+ message.append(", ");
+ if (unhandled) {
+ message.append("(unhandled) ");
+ }
+ message.append(event);
+ }
+
+ private void problem(String message) {
+ if (mViolationMessage == null) {
+ mViolationMessage = new StringBuilder();
+ }
+ if (mViolationMessage.length() == 0) {
+ mViolationMessage.append(mCurrentEventType).append(": ");
+ } else {
+ mViolationMessage.append("\n ");
+ }
+ mViolationMessage.append(message);
+ }
+
+ private KeyState findKeyState(int deviceId, int source, int keyCode, boolean remove) {
+ KeyState last = null;
+ KeyState state = mKeyStateList;
+ while (state != null) {
+ if (state.deviceId == deviceId && state.source == source
+ && state.keyCode == keyCode) {
+ if (remove) {
+ if (last != null) {
+ last.next = state.next;
+ } else {
+ mKeyStateList = state.next;
+ }
+ state.next = null;
+ }
+ return state;
+ }
+ last = state;
+ state = state.next;
+ }
+ return null;
+ }
+
+ private void addKeyState(int deviceId, int source, int keyCode) {
+ KeyState state = KeyState.obtain(deviceId, source, keyCode);
+ state.next = mKeyStateList;
+ mKeyStateList = state;
+ }
+
+ private static final class KeyState {
+ private static Object mRecycledListLock = new Object();
+ private static KeyState mRecycledList;
+
+ public KeyState next;
+ public int deviceId;
+ public int source;
+ public int keyCode;
+ public boolean unhandled;
+
+ private KeyState() {
+ }
+
+ public static KeyState obtain(int deviceId, int source, int keyCode) {
+ KeyState state;
+ synchronized (mRecycledListLock) {
+ state = mRecycledList;
+ if (state != null) {
+ mRecycledList = state.next;
+ } else {
+ state = new KeyState();
+ }
+ }
+ state.deviceId = deviceId;
+ state.source = source;
+ state.keyCode = keyCode;
+ state.unhandled = false;
+ return state;
+ }
+
+ public void recycle() {
+ synchronized (mRecycledListLock) {
+ next = mRecycledList;
+ mRecycledList = next;
+ }
+ }
+ }
+}
diff --git a/android/view/InputEventReceiver.java b/android/view/InputEventReceiver.java
new file mode 100644
index 00000000..c566a653
--- /dev/null
+++ b/android/view/InputEventReceiver.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2011 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.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a low-level mechanism for an application to receive input events.
+ * @hide
+ */
+public abstract class InputEventReceiver {
+ private static final String TAG = "InputEventReceiver";
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private long mReceiverPtr;
+
+ // We keep references to the input channel and message queue objects here so that
+ // they are not GC'd while the native peer of the receiver is using them.
+ private InputChannel mInputChannel;
+ private MessageQueue mMessageQueue;
+
+ // Map from InputEvent sequence numbers to dispatcher sequence numbers.
+ private final SparseIntArray mSeqMap = new SparseIntArray();
+
+ private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
+ InputChannel inputChannel, MessageQueue messageQueue);
+ private static native void nativeDispose(long receiverPtr);
+ private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
+ private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
+ long frameTimeNanos);
+
+ /**
+ * Creates an input event receiver bound to the specified input channel.
+ *
+ * @param inputChannel The input channel.
+ * @param looper The looper to use when invoking callbacks.
+ */
+ public InputEventReceiver(InputChannel inputChannel, Looper looper) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+ if (looper == null) {
+ throw new IllegalArgumentException("looper must not be null");
+ }
+
+ mInputChannel = inputChannel;
+ mMessageQueue = looper.getQueue();
+ mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
+ inputChannel, mMessageQueue);
+
+ mCloseGuard.open("dispose");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Disposes the receiver.
+ */
+ public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+
+ if (mReceiverPtr != 0) {
+ nativeDispose(mReceiverPtr);
+ mReceiverPtr = 0;
+ }
+ mInputChannel = null;
+ mMessageQueue = null;
+ }
+
+ /**
+ * Called when an input event is received.
+ * The recipient should process the input event and then call {@link #finishInputEvent}
+ * to indicate whether the event was handled. No new input events will be received
+ * until {@link #finishInputEvent} is called.
+ *
+ * @param displayId The display id on which input event triggered.
+ * @param event The input event that was received.
+ */
+ public void onInputEvent(InputEvent event, int displayId) {
+ finishInputEvent(event, false);
+ }
+
+ /**
+ * Called when a batched input event is pending.
+ *
+ * The batched input event will continue to accumulate additional movement
+ * samples until the recipient calls {@link #consumeBatchedInputEvents} or
+ * an event is received that ends the batch and causes it to be consumed
+ * immediately (such as a pointer up event).
+ */
+ public void onBatchedInputEventPending() {
+ consumeBatchedInputEvents(-1);
+ }
+
+ /**
+ * Finishes an input event and indicates whether it was handled.
+ * Must be called on the same Looper thread to which the receiver is attached.
+ *
+ * @param event The input event that was finished.
+ * @param handled True if the event was handled.
+ */
+ public final void finishInputEvent(InputEvent event, boolean handled) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mReceiverPtr == 0) {
+ Log.w(TAG, "Attempted to finish an input event but the input event "
+ + "receiver has already been disposed.");
+ } else {
+ int index = mSeqMap.indexOfKey(event.getSequenceNumber());
+ if (index < 0) {
+ Log.w(TAG, "Attempted to finish an input event that is not in progress.");
+ } else {
+ int seq = mSeqMap.valueAt(index);
+ mSeqMap.removeAt(index);
+ nativeFinishInputEvent(mReceiverPtr, seq, handled);
+ }
+ }
+ event.recycleIfNeededAfterDispatch();
+ }
+
+ /**
+ * Consumes all pending batched input events.
+ * Must be called on the same Looper thread to which the receiver is attached.
+ *
+ * This method forces all batched input events to be delivered immediately.
+ * Should be called just before animating or drawing a new frame in the UI.
+ *
+ * @param frameTimeNanos The time in the {@link System#nanoTime()} time base
+ * when the current display frame started rendering, or -1 if unknown.
+ *
+ * @return Whether a batch was consumed
+ */
+ public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
+ if (mReceiverPtr == 0) {
+ Log.w(TAG, "Attempted to consume batched input events but the input event "
+ + "receiver has already been disposed.");
+ } else {
+ return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
+ }
+ return false;
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
+ mSeqMap.put(event.getSequenceNumber(), seq);
+ onInputEvent(event, displayId);
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchBatchedInputEventPending() {
+ onBatchedInputEventPending();
+ }
+
+ public static interface Factory {
+ public InputEventReceiver createInputEventReceiver(
+ InputChannel inputChannel, Looper looper);
+ }
+}
diff --git a/android/view/InputEventSender.java b/android/view/InputEventSender.java
new file mode 100644
index 00000000..b25fb65b
--- /dev/null
+++ b/android/view/InputEventSender.java
@@ -0,0 +1,143 @@
+/*
+ * 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.view;
+
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a low-level mechanism for an application to send input events.
+ * @hide
+ */
+public abstract class InputEventSender {
+ private static final String TAG = "InputEventSender";
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private long mSenderPtr;
+
+ // We keep references to the input channel and message queue objects here so that
+ // they are not GC'd while the native peer of the receiver is using them.
+ private InputChannel mInputChannel;
+ private MessageQueue mMessageQueue;
+
+ private static native long nativeInit(WeakReference<InputEventSender> sender,
+ InputChannel inputChannel, MessageQueue messageQueue);
+ private static native void nativeDispose(long senderPtr);
+ private static native boolean nativeSendKeyEvent(long senderPtr, int seq, KeyEvent event);
+ private static native boolean nativeSendMotionEvent(long senderPtr, int seq, MotionEvent event);
+
+ /**
+ * Creates an input event sender bound to the specified input channel.
+ *
+ * @param inputChannel The input channel.
+ * @param looper The looper to use when invoking callbacks.
+ */
+ public InputEventSender(InputChannel inputChannel, Looper looper) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+ if (looper == null) {
+ throw new IllegalArgumentException("looper must not be null");
+ }
+
+ mInputChannel = inputChannel;
+ mMessageQueue = looper.getQueue();
+ mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
+ inputChannel, mMessageQueue);
+
+ mCloseGuard.open("dispose");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Disposes the receiver.
+ */
+ public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+
+ if (mSenderPtr != 0) {
+ nativeDispose(mSenderPtr);
+ mSenderPtr = 0;
+ }
+ mInputChannel = null;
+ mMessageQueue = null;
+ }
+
+ /**
+ * Called when an input event is finished.
+ *
+ * @param seq The input event sequence number.
+ * @param handled True if the input event was handled.
+ */
+ public void onInputEventFinished(int seq, boolean handled) {
+ }
+
+ /**
+ * Sends an input event.
+ * Must be called on the same Looper thread to which the sender is attached.
+ *
+ * @param seq The input event sequence number.
+ * @param event The input event to send.
+ * @return True if the entire event was sent successfully. May return false
+ * if the input channel buffer filled before all samples were dispatched.
+ */
+ public final boolean sendInputEvent(int seq, InputEvent event) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mSenderPtr == 0) {
+ Log.w(TAG, "Attempted to send an input event but the input event "
+ + "sender has already been disposed.");
+ return false;
+ }
+
+ if (event instanceof KeyEvent) {
+ return nativeSendKeyEvent(mSenderPtr, seq, (KeyEvent)event);
+ } else {
+ return nativeSendMotionEvent(mSenderPtr, seq, (MotionEvent)event);
+ }
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchInputEventFinished(int seq, boolean handled) {
+ onInputEventFinished(seq, handled);
+ }
+}
diff --git a/android/view/InputFilter.java b/android/view/InputFilter.java
new file mode 100644
index 00000000..d0dab400
--- /dev/null
+++ b/android/view/InputFilter.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2011 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.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * Filters input events before they are dispatched to the system.
+ * <p>
+ * At most one input filter can be installed by calling
+ * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the
+ * system's behavior changes as follows:
+ * <ul>
+ * <li>Input events are first delivered to the {@link WindowManagerPolicy}
+ * interception methods before queuing as usual. This critical step takes care of managing
+ * the power state of the device and handling wake keys.</li>
+ * <li>Input events are then asynchronously delivered to the input filter's
+ * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to
+ * applications as usual. The input filter only receives input events that were
+ * generated by an input device; the input filter will not receive input events that were
+ * injected into the system by other means, such as by instrumentation.</li>
+ * <li>The input filter processes and optionally transforms the stream of events. For example,
+ * it may transform a sequence of motion events representing an accessibility gesture into
+ * a different sequence of motion events, key presses or other system-level interactions.
+ * The input filter can send events to be dispatched by calling
+ * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the
+ * input event.</li>
+ * </ul>
+ * </p>
+ * <h3>The importance of input event consistency</h3>
+ * <p>
+ * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it
+ * sends an internally consistent stream of input events to the dispatcher. There are
+ * very important invariants to be maintained.
+ * </p><p>
+ * For example, if a key down is sent, a corresponding key up should also be sent eventually.
+ * Likewise, for touch events, each pointer must individually go down with
+ * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then
+ * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP}
+ * and the sequence of pointer ids used must be consistent throughout the gesture.
+ * </p><p>
+ * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should
+ * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly.
+ * </p><p>
+ * The input filter must take into account the fact that the input events coming from different
+ * devices or even different sources all consist of distinct streams of input.
+ * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify
+ * the source of the event and its semantics. There may be multiple sources of keys,
+ * touches and other input: they must be kept separate.
+ * </p>
+ * <h3>Policy flags</h3>
+ * <p>
+ * Input events received from the dispatcher and sent to the dispatcher have policy flags
+ * associated with them. Policy flags control some functions of the dispatcher.
+ * </p><p>
+ * The early policy interception decides whether an input event should be delivered
+ * to applications or dropped. The policy indicates its decision by setting the
+ * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may
+ * sometimes receive events that do not have this flag set. It should take note of
+ * the fact that the policy intends to drop the event, clean up its state, and
+ * then send appropriate cancellation events to the dispatcher if needed.
+ * </p><p>
+ * For example, suppose the input filter is processing a gesture and one of the touch events
+ * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set.
+ * The input filter should clear its internal state about the gesture and then send key or
+ * motion events to the dispatcher to cancel any keys or pointers that are down.
+ * </p><p>
+ * Corollary: Events that get sent to the dispatcher should usually include the
+ * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped!
+ * </p><p>
+ * It may be prudent to disable automatic key repeating for synthetic key events
+ * by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag.
+ * </p>
+ *
+ * @hide
+ */
+public abstract class InputFilter extends IInputFilter.Stub {
+ private static final int MSG_INSTALL = 1;
+ private static final int MSG_UNINSTALL = 2;
+ private static final int MSG_INPUT_EVENT = 3;
+
+ // Consistency verifiers for debugging purposes.
+ private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this,
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#InboundInputEventConsistencyVerifier") : null;
+ private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this,
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#OutboundInputEventConsistencyVerifier") : null;
+
+ private final H mH;
+
+ private IInputFilterHost mHost;
+
+ /**
+ * Creates the input filter.
+ *
+ * @param looper The looper to run callbacks on.
+ */
+ public InputFilter(Looper looper) {
+ mH = new H(looper);
+ }
+
+ /**
+ * Called when the input filter is installed.
+ * This method is guaranteed to be non-reentrant.
+ *
+ * @param host The input filter host environment.
+ */
+ public final void install(IInputFilterHost host) {
+ mH.obtainMessage(MSG_INSTALL, host).sendToTarget();
+ }
+
+ /**
+ * Called when the input filter is uninstalled.
+ * This method is guaranteed to be non-reentrant.
+ */
+ public final void uninstall() {
+ mH.obtainMessage(MSG_UNINSTALL).sendToTarget();
+ }
+
+ /**
+ * Called to enqueue the input event for filtering.
+ * The event will be recycled after the input filter processes it.
+ * This method is guaranteed to be non-reentrant.
+ *
+ * @param event The input event to enqueue.
+ */
+ final public void filterInputEvent(InputEvent event, int policyFlags) {
+ mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget();
+ }
+
+ /**
+ * Sends an input event to the dispatcher.
+ *
+ * @param event The input event to publish.
+ * @param policyFlags The input event policy flags.
+ */
+ public void sendInputEvent(InputEvent event, int policyFlags) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mHost == null) {
+ throw new IllegalStateException("Cannot send input event because the input filter " +
+ "is not installed.");
+ }
+ if (mOutboundInputEventConsistencyVerifier != null) {
+ mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0);
+ }
+ try {
+ mHost.sendInputEvent(event, policyFlags);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ /**
+ * Called when an input event has been received from the dispatcher.
+ * <p>
+ * The default implementation sends the input event back to the dispatcher, unchanged.
+ * </p><p>
+ * The event will be recycled when this method returns. If you want to keep it around,
+ * make a copy!
+ * </p>
+ *
+ * @param event The input event that was received.
+ * @param policyFlags The input event policy flags.
+ */
+ public void onInputEvent(InputEvent event, int policyFlags) {
+ sendInputEvent(event, policyFlags);
+ }
+
+ /**
+ * Called when the filter is installed into the dispatch pipeline.
+ * <p>
+ * This method is called before the input filter receives any input events.
+ * The input filter should take this opportunity to prepare itself.
+ * </p>
+ */
+ public void onInstalled() {
+ }
+
+ /**
+ * Called when the filter is uninstalled from the dispatch pipeline.
+ * <p>
+ * This method is called after the input filter receives its last input event.
+ * The input filter should take this opportunity to clean up.
+ * </p>
+ */
+ public void onUninstalled() {
+ }
+
+ private final class H extends Handler {
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INSTALL:
+ mHost = (IInputFilterHost) msg.obj;
+ if (mInboundInputEventConsistencyVerifier != null) {
+ mInboundInputEventConsistencyVerifier.reset();
+ }
+ if (mOutboundInputEventConsistencyVerifier != null) {
+ mOutboundInputEventConsistencyVerifier.reset();
+ }
+ onInstalled();
+ break;
+
+ case MSG_UNINSTALL:
+ try {
+ onUninstalled();
+ } finally {
+ mHost = null;
+ }
+ break;
+
+ case MSG_INPUT_EVENT: {
+ final InputEvent event = (InputEvent)msg.obj;
+ try {
+ if (mInboundInputEventConsistencyVerifier != null) {
+ mInboundInputEventConsistencyVerifier.onInputEvent(event, 0);
+ }
+ onInputEvent(event, msg.arg1);
+ } finally {
+ event.recycle();
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/android/view/InputQueue.java b/android/view/InputQueue.java
new file mode 100644
index 00000000..582ae79a
--- /dev/null
+++ b/android/view/InputQueue.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2010 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.os.Looper;
+import android.os.MessageQueue;
+import android.util.LongSparseArray;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An input queue provides a mechanism for an application to receive incoming
+ * input events. Currently only usable from native code.
+ */
+public final class InputQueue {
+ private final LongSparseArray<ActiveInputEvent> mActiveEventArray =
+ new LongSparseArray<ActiveInputEvent>(20);
+ private final Pool<ActiveInputEvent> mActiveInputEventPool =
+ new SimplePool<ActiveInputEvent>(20);
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private long mPtr;
+
+ private static native long nativeInit(WeakReference<InputQueue> weakQueue,
+ MessageQueue messageQueue);
+ private static native long nativeSendKeyEvent(long ptr, KeyEvent e, boolean preDispatch);
+ private static native long nativeSendMotionEvent(long ptr, MotionEvent e);
+ private static native void nativeDispose(long ptr);
+
+ /** @hide */
+ public InputQueue() {
+ mPtr = nativeInit(new WeakReference<InputQueue>(this), Looper.myQueue());
+
+ mCloseGuard.open("dispose");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /** @hide */
+ public void dispose() {
+ dispose(false);
+ }
+
+ /** @hide */
+ public void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+
+ if (mPtr != 0) {
+ nativeDispose(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ /** @hide */
+ public long getNativePtr() {
+ return mPtr;
+ }
+
+ /** @hide */
+ public void sendInputEvent(InputEvent e, Object token, boolean predispatch,
+ FinishedInputEventCallback callback) {
+ ActiveInputEvent event = obtainActiveInputEvent(token, callback);
+ long id;
+ if (e instanceof KeyEvent) {
+ id = nativeSendKeyEvent(mPtr, (KeyEvent) e, predispatch);
+ } else {
+ id = nativeSendMotionEvent(mPtr, (MotionEvent) e);
+ }
+ mActiveEventArray.put(id, event);
+ }
+
+ private void finishInputEvent(long id, boolean handled) {
+ int index = mActiveEventArray.indexOfKey(id);
+ if (index >= 0) {
+ ActiveInputEvent e = mActiveEventArray.valueAt(index);
+ mActiveEventArray.removeAt(index);
+ e.mCallback.onFinishedInputEvent(e.mToken, handled);
+ recycleActiveInputEvent(e);
+ }
+ }
+
+ private ActiveInputEvent obtainActiveInputEvent(Object token,
+ FinishedInputEventCallback callback) {
+ ActiveInputEvent e = mActiveInputEventPool.acquire();
+ if (e == null) {
+ e = new ActiveInputEvent();
+ }
+ e.mToken = token;
+ e.mCallback = callback;
+ return e;
+ }
+
+ private void recycleActiveInputEvent(ActiveInputEvent e) {
+ e.recycle();
+ mActiveInputEventPool.release(e);
+ }
+
+ private final class ActiveInputEvent {
+ public Object mToken;
+ public FinishedInputEventCallback mCallback;
+
+ public void recycle() {
+ mToken = null;
+ mCallback = null;
+ }
+ }
+
+ /**
+ * Interface to receive notification of when an InputQueue is associated
+ * and dissociated with a thread.
+ */
+ public static interface Callback {
+ /**
+ * Called when the given InputQueue is now associated with the
+ * thread making this call, so it can start receiving events from it.
+ */
+ void onInputQueueCreated(InputQueue queue);
+
+ /**
+ * Called when the given InputQueue is no longer associated with
+ * the thread and thus not dispatching events.
+ */
+ void onInputQueueDestroyed(InputQueue queue);
+ }
+
+ /** @hide */
+ public static interface FinishedInputEventCallback {
+ void onFinishedInputEvent(Object token, boolean handled);
+ }
+
+}
diff --git a/android/view/KeyCharacterMap.java b/android/view/KeyCharacterMap.java
new file mode 100644
index 00000000..02202db5
--- /dev/null
+++ b/android/view/KeyCharacterMap.java
@@ -0,0 +1,784 @@
+/*
+ * Copyright (C) 2007 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.hardware.input.InputManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.method.MetaKeyKeyListener;
+import android.util.AndroidRuntimeException;
+import android.util.SparseIntArray;
+
+import java.text.Normalizer;
+
+/**
+ * Describes the keys provided by a keyboard device and their associated labels.
+ */
+public class KeyCharacterMap implements Parcelable {
+ /**
+ * The id of the device's primary built in keyboard is always 0.
+ *
+ * @deprecated This constant should no longer be used because there is no
+ * guarantee that a device has a built-in keyboard that can be used for
+ * typing text. There might not be a built-in keyboard, the built-in keyboard
+ * might be a {@link #NUMERIC} or {@link #SPECIAL_FUNCTION} keyboard, or there
+ * might be multiple keyboards installed including external keyboards.
+ * When interpreting key presses received from the framework, applications should
+ * use the device id specified in the {@link KeyEvent} received.
+ * When synthesizing key presses for delivery elsewhere or when translating key presses
+ * from unknown keyboards, applications should use the special {@link #VIRTUAL_KEYBOARD}
+ * device id.
+ */
+ @Deprecated
+ public static final int BUILT_IN_KEYBOARD = 0;
+
+ /**
+ * The id of a generic virtual keyboard with a full layout that can be used to
+ * synthesize key events. Typically used with {@link #getEvents}.
+ */
+ public static final int VIRTUAL_KEYBOARD = -1;
+
+ /**
+ * A numeric (12-key) keyboard.
+ * <p>
+ * A numeric keyboard supports text entry using a multi-tap approach.
+ * It may be necessary to tap a key multiple times to generate the desired letter
+ * or symbol.
+ * </p><p>
+ * This type of keyboard is generally designed for thumb typing.
+ * </p>
+ */
+ public static final int NUMERIC = 1;
+
+ /**
+ * A keyboard with all the letters, but with more than one letter per key.
+ * <p>
+ * This type of keyboard is generally designed for thumb typing.
+ * </p>
+ */
+ public static final int PREDICTIVE = 2;
+
+ /**
+ * A keyboard with all the letters, and maybe some numbers.
+ * <p>
+ * An alphabetic keyboard supports text entry directly but may have a condensed
+ * layout with a small form factor. In contrast to a {@link #FULL full keyboard}, some
+ * symbols may only be accessible using special on-screen character pickers.
+ * In addition, to improve typing speed and accuracy, the framework provides
+ * special affordances for alphabetic keyboards such as auto-capitalization
+ * and toggled / locked shift and alt keys.
+ * </p><p>
+ * This type of keyboard is generally designed for thumb typing.
+ * </p>
+ */
+ public static final int ALPHA = 3;
+
+ /**
+ * A full PC-style keyboard.
+ * <p>
+ * A full keyboard behaves like a PC keyboard. All symbols are accessed directly
+ * by pressing keys on the keyboard without on-screen support or affordances such
+ * as auto-capitalization.
+ * </p><p>
+ * This type of keyboard is generally designed for full two hand typing.
+ * </p>
+ */
+ public static final int FULL = 4;
+
+ /**
+ * A keyboard that is only used to control special functions rather than for typing.
+ * <p>
+ * A special function keyboard consists only of non-printing keys such as
+ * HOME and POWER that are not actually used for typing.
+ * </p>
+ */
+ public static final int SPECIAL_FUNCTION = 5;
+
+ /**
+ * This private-use character is used to trigger Unicode character
+ * input by hex digits.
+ */
+ public static final char HEX_INPUT = '\uEF00';
+
+ /**
+ * This private-use character is used to bring up a character picker for
+ * miscellaneous symbols.
+ */
+ public static final char PICKER_DIALOG_INPUT = '\uEF01';
+
+ /**
+ * Modifier keys may be chorded with character keys.
+ *
+ * @see {#link #getModifierBehavior()} for more details.
+ */
+ public static final int MODIFIER_BEHAVIOR_CHORDED = 0;
+
+ /**
+ * Modifier keys may be chorded with character keys or they may toggle
+ * into latched or locked states when pressed independently.
+ *
+ * @see {#link #getModifierBehavior()} for more details.
+ */
+ public static final int MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED = 1;
+
+ /*
+ * This bit will be set in the return value of {@link #get(int, int)} if the
+ * key is a "dead key."
+ */
+ public static final int COMBINING_ACCENT = 0x80000000;
+
+ /**
+ * Mask the return value from {@link #get(int, int)} with this value to get
+ * a printable representation of the accent character of a "dead key."
+ */
+ public static final int COMBINING_ACCENT_MASK = 0x7FFFFFFF;
+
+ /* Characters used to display placeholders for dead keys. */
+ private static final int ACCENT_ACUTE = '\u00B4';
+ private static final int ACCENT_BREVE = '\u02D8';
+ private static final int ACCENT_CARON = '\u02C7';
+ private static final int ACCENT_CEDILLA = '\u00B8';
+ private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+ private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+ private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+ private static final int ACCENT_DOT_ABOVE = '\u02D9';
+ private static final int ACCENT_DOT_BELOW = '.'; // approximate
+ private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
+ private static final int ACCENT_GRAVE = '\u02CB';
+ private static final int ACCENT_HOOK_ABOVE = '\u02C0';
+ private static final int ACCENT_HORN = '\''; // approximate
+ private static final int ACCENT_MACRON = '\u00AF';
+ private static final int ACCENT_MACRON_BELOW = '\u02CD';
+ private static final int ACCENT_OGONEK = '\u02DB';
+ private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+ private static final int ACCENT_RING_ABOVE = '\u02DA';
+ private static final int ACCENT_STROKE = '-'; // approximate
+ private static final int ACCENT_TILDE = '\u02DC';
+ private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
+ private static final int ACCENT_UMLAUT = '\u00A8';
+ private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
+ private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+
+ /* Legacy dead key display characters used in previous versions of the API.
+ * We still support these characters by mapping them to their non-legacy version. */
+ private static final int ACCENT_GRAVE_LEGACY = '`';
+ private static final int ACCENT_CIRCUMFLEX_LEGACY = '^';
+ private static final int ACCENT_TILDE_LEGACY = '~';
+
+ private static final int CHAR_SPACE = ' ';
+
+ /**
+ * Maps Unicode combining diacritical to display-form dead key.
+ */
+ private static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+ private static final SparseIntArray sAccentToCombining = new SparseIntArray();
+ static {
+ addCombining('\u0300', ACCENT_GRAVE);
+ addCombining('\u0301', ACCENT_ACUTE);
+ addCombining('\u0302', ACCENT_CIRCUMFLEX);
+ addCombining('\u0303', ACCENT_TILDE);
+ addCombining('\u0304', ACCENT_MACRON);
+ addCombining('\u0306', ACCENT_BREVE);
+ addCombining('\u0307', ACCENT_DOT_ABOVE);
+ addCombining('\u0308', ACCENT_UMLAUT);
+ addCombining('\u0309', ACCENT_HOOK_ABOVE);
+ addCombining('\u030A', ACCENT_RING_ABOVE);
+ addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+ addCombining('\u030C', ACCENT_CARON);
+ addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+ //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+ //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+ //addCombining('\u0310', ACCENT_CANDRABINDU);
+ //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+ addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+ addCombining('\u0313', ACCENT_COMMA_ABOVE);
+ addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+ addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+ addCombining('\u031B', ACCENT_HORN);
+ addCombining('\u0323', ACCENT_DOT_BELOW);
+ //addCombining('\u0326', ACCENT_COMMA_BELOW);
+ addCombining('\u0327', ACCENT_CEDILLA);
+ addCombining('\u0328', ACCENT_OGONEK);
+ addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+ addCombining('\u0331', ACCENT_MACRON_BELOW);
+ addCombining('\u0335', ACCENT_STROKE);
+ //addCombining('\u0342', ACCENT_PERISPOMENI);
+ //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+ //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+ // One-way mappings to equivalent preferred accents.
+ sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+ sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+ sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+ // One-way legacy mappings to preserve compatibility with older applications.
+ sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+ sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+ sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+ }
+
+ private static void addCombining(int combining, int accent) {
+ sCombiningToAccent.append(combining, accent);
+ sAccentToCombining.append(accent, combining);
+ }
+
+ /**
+ * Maps combinations of (display-form) combining key and second character
+ * to combined output character.
+ * These mappings are derived from the Unicode NFC tables as needed.
+ */
+ private static final SparseIntArray sDeadKeyCache = new SparseIntArray();
+ private static final StringBuilder sDeadKeyBuilder = new StringBuilder();
+ static {
+ // Non-standard decompositions.
+ // Stroke modifier for Finnish multilingual keyboard and others.
+ addDeadKey(ACCENT_STROKE, 'D', '\u0110');
+ addDeadKey(ACCENT_STROKE, 'G', '\u01e4');
+ addDeadKey(ACCENT_STROKE, 'H', '\u0126');
+ addDeadKey(ACCENT_STROKE, 'I', '\u0197');
+ addDeadKey(ACCENT_STROKE, 'L', '\u0141');
+ addDeadKey(ACCENT_STROKE, 'O', '\u00d8');
+ addDeadKey(ACCENT_STROKE, 'T', '\u0166');
+ addDeadKey(ACCENT_STROKE, 'd', '\u0111');
+ addDeadKey(ACCENT_STROKE, 'g', '\u01e5');
+ addDeadKey(ACCENT_STROKE, 'h', '\u0127');
+ addDeadKey(ACCENT_STROKE, 'i', '\u0268');
+ addDeadKey(ACCENT_STROKE, 'l', '\u0142');
+ addDeadKey(ACCENT_STROKE, 'o', '\u00f8');
+ addDeadKey(ACCENT_STROKE, 't', '\u0167');
+ }
+
+ private static void addDeadKey(int accent, int c, int result) {
+ final int combining = sAccentToCombining.get(accent);
+ if (combining == 0) {
+ throw new IllegalStateException("Invalid dead key declaration.");
+ }
+ final int combination = (combining << 16) | c;
+ sDeadKeyCache.put(combination, result);
+ }
+
+ public static final Parcelable.Creator<KeyCharacterMap> CREATOR =
+ new Parcelable.Creator<KeyCharacterMap>() {
+ public KeyCharacterMap createFromParcel(Parcel in) {
+ return new KeyCharacterMap(in);
+ }
+ public KeyCharacterMap[] newArray(int size) {
+ return new KeyCharacterMap[size];
+ }
+ };
+
+ private long mPtr;
+
+ private static native long nativeReadFromParcel(Parcel in);
+ private static native void nativeWriteToParcel(long ptr, Parcel out);
+ private static native void nativeDispose(long ptr);
+
+ private static native char nativeGetCharacter(long ptr, int keyCode, int metaState);
+ private static native boolean nativeGetFallbackAction(long ptr, int keyCode, int metaState,
+ FallbackAction outFallbackAction);
+ private static native char nativeGetNumber(long ptr, int keyCode);
+ private static native char nativeGetMatch(long ptr, int keyCode, char[] chars, int metaState);
+ private static native char nativeGetDisplayLabel(long ptr, int keyCode);
+ private static native int nativeGetKeyboardType(long ptr);
+ private static native KeyEvent[] nativeGetEvents(long ptr, char[] chars);
+
+ private KeyCharacterMap(Parcel in) {
+ if (in == null) {
+ throw new IllegalArgumentException("parcel must not be null");
+ }
+ mPtr = nativeReadFromParcel(in);
+ if (mPtr == 0) {
+ throw new RuntimeException("Could not read KeyCharacterMap from parcel.");
+ }
+ }
+
+ // Called from native
+ private KeyCharacterMap(long ptr) {
+ mPtr = ptr;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mPtr != 0) {
+ nativeDispose(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ /**
+ * Loads the key character maps for the keyboard with the specified device id.
+ *
+ * @param deviceId The device id of the keyboard.
+ * @return The associated key character map.
+ * @throws {@link UnavailableException} if the key character map
+ * could not be loaded because it was malformed or the default key character map
+ * is missing from the system.
+ */
+ public static KeyCharacterMap load(int deviceId) {
+ final InputManager im = InputManager.getInstance();
+ InputDevice inputDevice = im.getInputDevice(deviceId);
+ if (inputDevice == null) {
+ inputDevice = im.getInputDevice(VIRTUAL_KEYBOARD);
+ if (inputDevice == null) {
+ throw new UnavailableException(
+ "Could not load key character map for device " + deviceId);
+ }
+ }
+ return inputDevice.getKeyCharacterMap();
+ }
+
+ /**
+ * Gets the Unicode character generated by the specified key and meta
+ * key state combination.
+ * <p>
+ * Returns the Unicode character that the specified key would produce
+ * when the specified meta bits (see {@link MetaKeyKeyListener})
+ * were active.
+ * </p><p>
+ * Returns 0 if the key is not one that is used to type Unicode
+ * characters.
+ * </p><p>
+ * If the return value has bit {@link #COMBINING_ACCENT} set, the
+ * key is a "dead key" that should be combined with another to
+ * actually produce a character -- see {@link #getDeadChar} --
+ * after masking with {@link #COMBINING_ACCENT_MASK}.
+ * </p>
+ *
+ * @param keyCode The key code.
+ * @param metaState The meta key modifier state.
+ * @return The associated character or combining accent, or 0 if none.
+ */
+ public int get(int keyCode, int metaState) {
+ metaState = KeyEvent.normalizeMetaState(metaState);
+ char ch = nativeGetCharacter(mPtr, keyCode, metaState);
+
+ int map = sCombiningToAccent.get(ch);
+ if (map != 0) {
+ return map | COMBINING_ACCENT;
+ } else {
+ return ch;
+ }
+ }
+
+ /**
+ * Gets the fallback action to perform if the application does not
+ * handle the specified key.
+ * <p>
+ * When an application does not handle a particular key, the system may
+ * translate the key to an alternate fallback key (specified in the
+ * fallback action) and dispatch it to the application.
+ * The event containing the fallback key is flagged
+ * with {@link KeyEvent#FLAG_FALLBACK}.
+ * </p>
+ *
+ * @param keyCode The key code.
+ * @param metaState The meta key modifier state.
+ * @return The fallback action, or null if none. Remember to recycle the fallback action.
+ *
+ * @hide
+ */
+ public FallbackAction getFallbackAction(int keyCode, int metaState) {
+ FallbackAction action = FallbackAction.obtain();
+ metaState = KeyEvent.normalizeMetaState(metaState);
+ if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) {
+ action.metaState = KeyEvent.normalizeMetaState(action.metaState);
+ return action;
+ }
+ action.recycle();
+ return null;
+ }
+
+ /**
+ * Gets the number or symbol associated with the key.
+ * <p>
+ * The character value is returned, not the numeric value.
+ * If the key is not a number, but is a symbol, the symbol is retuned.
+ * </p><p>
+ * This method is intended to to support dial pads and other numeric or
+ * symbolic entry on keyboards where certain keys serve dual function
+ * as alphabetic and symbolic keys. This method returns the number
+ * or symbol associated with the key independent of whether the user
+ * has pressed the required modifier.
+ * </p><p>
+ * For example, on one particular keyboard the keys on the top QWERTY row generate
+ * numbers when ALT is pressed such that ALT-Q maps to '1'. So for that keyboard
+ * when {@link #getNumber} is called with {@link KeyEvent#KEYCODE_Q} it returns '1'
+ * so that the user can type numbers without pressing ALT when it makes sense.
+ * </p>
+ *
+ * @param keyCode The key code.
+ * @return The associated numeric or symbolic character, or 0 if none.
+ */
+ public char getNumber(int keyCode) {
+ return nativeGetNumber(mPtr, keyCode);
+ }
+
+ /**
+ * Gets the first character in the character array that can be generated
+ * by the specified key code.
+ * <p>
+ * This is a convenience function that returns the same value as
+ * {@link #getMatch(int,char[],int) getMatch(keyCode, chars, 0)}.
+ * </p>
+ *
+ * @param keyCode The keycode.
+ * @param chars The array of matching characters to consider.
+ * @return The matching associated character, or 0 if none.
+ * @throws {@link IllegalArgumentException} if the passed array of characters is null.
+ */
+ public char getMatch(int keyCode, char[] chars) {
+ return getMatch(keyCode, chars, 0);
+ }
+
+ /**
+ * Gets the first character in the character array that can be generated
+ * by the specified key code. If there are multiple choices, prefers
+ * the one that would be generated with the specified meta key modifier state.
+ *
+ * @param keyCode The key code.
+ * @param chars The array of matching characters to consider.
+ * @param metaState The preferred meta key modifier state.
+ * @return The matching associated character, or 0 if none.
+ * @throws {@link IllegalArgumentException} if the passed array of characters is null.
+ */
+ public char getMatch(int keyCode, char[] chars, int metaState) {
+ if (chars == null) {
+ throw new IllegalArgumentException("chars must not be null.");
+ }
+
+ metaState = KeyEvent.normalizeMetaState(metaState);
+ return nativeGetMatch(mPtr, keyCode, chars, metaState);
+ }
+
+ /**
+ * Gets the primary character for this key.
+ * In other words, the label that is physically printed on it.
+ *
+ * @param keyCode The key code.
+ * @return The display label character, or 0 if none (eg. for non-printing keys).
+ */
+ public char getDisplayLabel(int keyCode) {
+ return nativeGetDisplayLabel(mPtr, keyCode);
+ }
+
+ /**
+ * Get the character that is produced by combining the dead key producing accent
+ * with the key producing character c.
+ * For example, getDeadChar('`', 'e') returns &egrave;.
+ * getDeadChar('^', ' ') returns '^' and getDeadChar('^', '^') returns '^'.
+ *
+ * @param accent The accent character. eg. '`'
+ * @param c The basic character.
+ * @return The combined character, or 0 if the characters cannot be combined.
+ */
+ public static int getDeadChar(int accent, int c) {
+ if (c == accent || CHAR_SPACE == c) {
+ // The same dead character typed twice or a dead character followed by a
+ // space should both produce the non-combining version of the combining char.
+ // In this case we don't even need to compute the combining character.
+ return accent;
+ }
+
+ int combining = sAccentToCombining.get(accent);
+ if (combining == 0) {
+ return 0;
+ }
+
+ final int combination = (combining << 16) | c;
+ int combined;
+ synchronized (sDeadKeyCache) {
+ combined = sDeadKeyCache.get(combination, -1);
+ if (combined == -1) {
+ sDeadKeyBuilder.setLength(0);
+ sDeadKeyBuilder.append((char)c);
+ sDeadKeyBuilder.append((char)combining);
+ String result = Normalizer.normalize(sDeadKeyBuilder, Normalizer.Form.NFC);
+ combined = result.codePointCount(0, result.length()) == 1
+ ? result.codePointAt(0) : 0;
+ sDeadKeyCache.put(combination, combined);
+ }
+ }
+ return combined;
+ }
+
+ /**
+ * Describes the character mappings associated with a key.
+ *
+ * @deprecated instead use {@link KeyCharacterMap#getDisplayLabel(int)},
+ * {@link KeyCharacterMap#getNumber(int)} and {@link KeyCharacterMap#get(int, int)}.
+ */
+ @Deprecated
+ public static class KeyData {
+ public static final int META_LENGTH = 4;
+
+ /**
+ * The display label (see {@link #getDisplayLabel}).
+ */
+ public char displayLabel;
+ /**
+ * The "number" value (see {@link #getNumber}).
+ */
+ public char number;
+ /**
+ * The character that will be generated in various meta states
+ * (the same ones used for {@link #get} and defined as
+ * {@link KeyEvent#META_SHIFT_ON} and {@link KeyEvent#META_ALT_ON}).
+ * <table>
+ * <tr><th>Index</th><th align="left">Value</th></tr>
+ * <tr><td>0</td><td>no modifiers</td></tr>
+ * <tr><td>1</td><td>caps</td></tr>
+ * <tr><td>2</td><td>alt</td></tr>
+ * <tr><td>3</td><td>caps + alt</td></tr>
+ * </table>
+ */
+ public char[] meta = new char[META_LENGTH];
+ }
+
+ /**
+ * Get the character conversion data for a given key code.
+ *
+ * @param keyCode The keyCode to query.
+ * @param results A {@link KeyData} instance that will be filled with the results.
+ * @return True if the key was mapped. If the key was not mapped, results is not modified.
+ *
+ * @deprecated instead use {@link KeyCharacterMap#getDisplayLabel(int)},
+ * {@link KeyCharacterMap#getNumber(int)} or {@link KeyCharacterMap#get(int, int)}.
+ */
+ @Deprecated
+ public boolean getKeyData(int keyCode, KeyData results) {
+ if (results.meta.length < KeyData.META_LENGTH) {
+ throw new IndexOutOfBoundsException(
+ "results.meta.length must be >= " + KeyData.META_LENGTH);
+ }
+
+ char displayLabel = nativeGetDisplayLabel(mPtr, keyCode);
+ if (displayLabel == 0) {
+ return false;
+ }
+
+ results.displayLabel = displayLabel;
+ results.number = nativeGetNumber(mPtr, keyCode);
+ results.meta[0] = nativeGetCharacter(mPtr, keyCode, 0);
+ results.meta[1] = nativeGetCharacter(mPtr, keyCode, KeyEvent.META_SHIFT_ON);
+ results.meta[2] = nativeGetCharacter(mPtr, keyCode, KeyEvent.META_ALT_ON);
+ results.meta[3] = nativeGetCharacter(mPtr, keyCode,
+ KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON);
+ return true;
+ }
+
+ /**
+ * Get an array of KeyEvent objects that if put into the input stream
+ * could plausibly generate the provided sequence of characters. It is
+ * not guaranteed that the sequence is the only way to generate these
+ * events or that it is optimal.
+ * <p>
+ * This function is primarily offered for instrumentation and testing purposes.
+ * It may fail to map characters to key codes. In particular, the key character
+ * map for the {@link #BUILT_IN_KEYBOARD built-in keyboard} device id may be empty.
+ * Consider using the key character map associated with the
+ * {@link #VIRTUAL_KEYBOARD virtual keyboard} device id instead.
+ * </p><p>
+ * For robust text entry, do not use this function. Instead construct a
+ * {@link KeyEvent} with action code {@link KeyEvent#ACTION_MULTIPLE} that contains
+ * the desired string using {@link KeyEvent#KeyEvent(long, String, int, int)}.
+ * </p>
+ *
+ * @param chars The sequence of characters to generate.
+ * @return An array of {@link KeyEvent} objects, or null if the given char array
+ * can not be generated using the current key character map.
+ * @throws {@link IllegalArgumentException} if the passed array of characters is null.
+ */
+ public KeyEvent[] getEvents(char[] chars) {
+ if (chars == null) {
+ throw new IllegalArgumentException("chars must not be null.");
+ }
+ return nativeGetEvents(mPtr, chars);
+ }
+
+ /**
+ * Returns true if the specified key produces a glyph.
+ *
+ * @param keyCode The key code.
+ * @return True if the key is a printing key.
+ */
+ public boolean isPrintingKey(int keyCode) {
+ int type = Character.getType(nativeGetDisplayLabel(mPtr, keyCode));
+
+ switch (type)
+ {
+ case Character.SPACE_SEPARATOR:
+ case Character.LINE_SEPARATOR:
+ case Character.PARAGRAPH_SEPARATOR:
+ case Character.CONTROL:
+ case Character.FORMAT:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Gets the keyboard type.
+ * Returns {@link #NUMERIC}, {@link #PREDICTIVE}, {@link #ALPHA}, {@link #FULL}
+ * or {@link #SPECIAL_FUNCTION}.
+ * <p>
+ * Different keyboard types have different semantics. Refer to the documentation
+ * associated with the keyboard type constants for details.
+ * </p>
+ *
+ * @return The keyboard type.
+ */
+ public int getKeyboardType() {
+ return nativeGetKeyboardType(mPtr);
+ }
+
+ /**
+ * Gets a constant that describes the behavior of this keyboard's modifier keys
+ * such as {@link KeyEvent#KEYCODE_SHIFT_LEFT}.
+ * <p>
+ * Currently there are two behaviors that may be combined:
+ * </p>
+ * <ul>
+ * <li>Chorded behavior: When the modifier key is pressed together with one or more
+ * character keys, the keyboard inserts the modified keys and
+ * then resets the modifier state when the modifier key is released.</li>
+ * <li>Toggled behavior: When the modifier key is pressed and released on its own
+ * it first toggles into a latched state. When latched, the modifier will apply
+ * to next character key that is pressed and will then reset itself to the initial state.
+ * If the modifier is already latched and the modifier key is pressed and release on
+ * its own again, then it toggles into a locked state. When locked, the modifier will
+ * apply to all subsequent character keys that are pressed until unlocked by pressing
+ * the modifier key on its own one more time to reset it to the initial state.
+ * Toggled behavior is useful for small profile keyboards designed for thumb typing.
+ * </ul>
+ * <p>
+ * This function currently returns {@link #MODIFIER_BEHAVIOR_CHORDED} when the
+ * {@link #getKeyboardType() keyboard type} is {@link #FULL} or {@link #SPECIAL_FUNCTION} and
+ * {@link #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED} otherwise.
+ * In the future, the function may also take into account global keyboard
+ * accessibility settings, other user preferences, or new device capabilities.
+ * </p>
+ *
+ * @return The modifier behavior for this keyboard.
+ *
+ * @see #MODIFIER_BEHAVIOR_CHORDED
+ * @see #MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED
+ */
+ public int getModifierBehavior() {
+ switch (getKeyboardType()) {
+ case FULL:
+ case SPECIAL_FUNCTION:
+ return MODIFIER_BEHAVIOR_CHORDED;
+ default:
+ return MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED;
+ }
+ }
+
+ /**
+ * Queries the framework about whether any physical keys exist on the
+ * any keyboard attached to the device that are capable of producing the given key code.
+ *
+ * @param keyCode The key code to query.
+ * @return True if at least one attached keyboard supports the specified key code.
+ */
+ public static boolean deviceHasKey(int keyCode) {
+ return InputManager.getInstance().deviceHasKeys(new int[] { keyCode })[0];
+ }
+
+ /**
+ * Queries the framework about whether any physical keys exist on the
+ * any keyboard attached to the device that are capable of producing the given
+ * array of key codes.
+ *
+ * @param keyCodes The array of key codes to query.
+ * @return A new array of the same size as the key codes array whose elements
+ * are set to true if at least one attached keyboard supports the corresponding key code
+ * at the same index in the key codes array.
+ */
+ public static boolean[] deviceHasKeys(int[] keyCodes) {
+ return InputManager.getInstance().deviceHasKeys(keyCodes);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (out == null) {
+ throw new IllegalArgumentException("parcel must not be null");
+ }
+ nativeWriteToParcel(mPtr, out);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Thrown by {@link KeyCharacterMap#load} when a key character map could not be loaded.
+ */
+ public static class UnavailableException extends AndroidRuntimeException {
+ public UnavailableException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Specifies a substitute key code and meta state as a fallback action
+ * for an unhandled key.
+ * @hide
+ */
+ public static final class FallbackAction {
+ private static final int MAX_RECYCLED = 10;
+ private static final Object sRecycleLock = new Object();
+ private static FallbackAction sRecycleBin;
+ private static int sRecycledCount;
+
+ private FallbackAction next;
+
+ public int keyCode;
+ public int metaState;
+
+ private FallbackAction() {
+ }
+
+ public static FallbackAction obtain() {
+ final FallbackAction target;
+ synchronized (sRecycleLock) {
+ if (sRecycleBin == null) {
+ target = new FallbackAction();
+ } else {
+ target = sRecycleBin;
+ sRecycleBin = target.next;
+ sRecycledCount--;
+ target.next = null;
+ }
+ }
+ return target;
+ }
+
+ public void recycle() {
+ synchronized (sRecycleLock) {
+ if (sRecycledCount < MAX_RECYCLED) {
+ next = sRecycleBin;
+ sRecycleBin = this;
+ sRecycledCount += 1;
+ } else {
+ next = null;
+ }
+ }
+ }
+ }
+}
diff --git a/android/view/KeyEvent.java b/android/view/KeyEvent.java
new file mode 100644
index 00000000..a2147b71
--- /dev/null
+++ b/android/view/KeyEvent.java
@@ -0,0 +1,2993 @@
+/*
+ * Copyright (C) 2006 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.KeyCharacterMap.KeyData;
+
+/**
+ * Object used to report key and button events.
+ * <p>
+ * Each key press is described by a sequence of key events. A key press
+ * starts with a key event with {@link #ACTION_DOWN}. If the key is held
+ * sufficiently long that it repeats, then the initial down is followed
+ * additional key events with {@link #ACTION_DOWN} and a non-zero value for
+ * {@link #getRepeatCount()}. The last key event is a {@link #ACTION_UP}
+ * for the key up. If the key press is canceled, the key up event will have the
+ * {@link #FLAG_CANCELED} flag set.
+ * </p><p>
+ * Key events are generally accompanied by a key code ({@link #getKeyCode()}),
+ * scan code ({@link #getScanCode()}) and meta state ({@link #getMetaState()}).
+ * Key code constants are defined in this class. Scan code constants are raw
+ * device-specific codes obtained from the OS and so are not generally meaningful
+ * to applications unless interpreted using the {@link KeyCharacterMap}.
+ * Meta states describe the pressed state of key modifiers
+ * such as {@link #META_SHIFT_ON} or {@link #META_ALT_ON}.
+ * </p><p>
+ * Key codes typically correspond one-to-one with individual keys on an input device.
+ * Many keys and key combinations serve quite different functions on different
+ * input devices so care must be taken when interpreting them. Always use the
+ * {@link KeyCharacterMap} associated with the input device when mapping keys
+ * to characters. Be aware that there may be multiple key input devices active
+ * at the same time and each will have its own key character map.
+ * </p><p>
+ * As soft input methods can use multiple and inventive ways of inputting text,
+ * there is no guarantee that any key press on a soft keyboard will generate a key
+ * event: this is left to the IME's discretion, and in fact sending such events is
+ * discouraged. You should never rely on receiving KeyEvents for any key on a soft
+ * input method. In particular, the default software keyboard will never send any
+ * key event to any application targetting Jelly Bean or later, and will only send
+ * events for some presses of the delete and return keys to applications targetting
+ * Ice Cream Sandwich or earlier. Be aware that other software input methods may
+ * never send key events regardless of the version. Consider using editor actions
+ * like {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} if you need
+ * specific interaction with the software keyboard, as it gives more visibility to
+ * the user as to how your application will react to key presses.
+ * </p><p>
+ * When interacting with an IME, the framework may deliver key events
+ * with the special action {@link #ACTION_MULTIPLE} that either specifies
+ * that single repeated key code or a sequence of characters to insert.
+ * </p><p>
+ * In general, the framework cannot guarantee that the key events it delivers
+ * to a view always constitute complete key sequences since some events may be dropped
+ * or modified by containing views before they are delivered. The view implementation
+ * should be prepared to handle {@link #FLAG_CANCELED} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior key press.
+ * </p><p>
+ * Refer to {@link InputDevice} for more information about how different kinds of
+ * input devices and sources represent keys and buttons.
+ * </p>
+ */
+public class KeyEvent extends InputEvent implements Parcelable {
+ /** Key code constant: Unknown key code. */
+ public static final int KEYCODE_UNKNOWN = 0;
+ /** Key code constant: Soft Left key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom left
+ * of the display. */
+ public static final int KEYCODE_SOFT_LEFT = 1;
+ /** Key code constant: Soft Right key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom right
+ * of the display. */
+ public static final int KEYCODE_SOFT_RIGHT = 2;
+ /** Key code constant: Home key.
+ * This key is handled by the framework and is never delivered to applications. */
+ public static final int KEYCODE_HOME = 3;
+ /** Key code constant: Back key. */
+ public static final int KEYCODE_BACK = 4;
+ /** Key code constant: Call key. */
+ public static final int KEYCODE_CALL = 5;
+ /** Key code constant: End Call key. */
+ public static final int KEYCODE_ENDCALL = 6;
+ /** Key code constant: '0' key. */
+ public static final int KEYCODE_0 = 7;
+ /** Key code constant: '1' key. */
+ public static final int KEYCODE_1 = 8;
+ /** Key code constant: '2' key. */
+ public static final int KEYCODE_2 = 9;
+ /** Key code constant: '3' key. */
+ public static final int KEYCODE_3 = 10;
+ /** Key code constant: '4' key. */
+ public static final int KEYCODE_4 = 11;
+ /** Key code constant: '5' key. */
+ public static final int KEYCODE_5 = 12;
+ /** Key code constant: '6' key. */
+ public static final int KEYCODE_6 = 13;
+ /** Key code constant: '7' key. */
+ public static final int KEYCODE_7 = 14;
+ /** Key code constant: '8' key. */
+ public static final int KEYCODE_8 = 15;
+ /** Key code constant: '9' key. */
+ public static final int KEYCODE_9 = 16;
+ /** Key code constant: '*' key. */
+ public static final int KEYCODE_STAR = 17;
+ /** Key code constant: '#' key. */
+ public static final int KEYCODE_POUND = 18;
+ /** Key code constant: Directional Pad Up key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_UP = 19;
+ /** Key code constant: Directional Pad Down key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_DOWN = 20;
+ /** Key code constant: Directional Pad Left key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_LEFT = 21;
+ /** Key code constant: Directional Pad Right key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_RIGHT = 22;
+ /** Key code constant: Directional Pad Center key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_CENTER = 23;
+ /** Key code constant: Volume Up key.
+ * Adjusts the speaker volume up. */
+ public static final int KEYCODE_VOLUME_UP = 24;
+ /** Key code constant: Volume Down key.
+ * Adjusts the speaker volume down. */
+ public static final int KEYCODE_VOLUME_DOWN = 25;
+ /** Key code constant: Power key. */
+ public static final int KEYCODE_POWER = 26;
+ /** Key code constant: Camera key.
+ * Used to launch a camera application or take pictures. */
+ public static final int KEYCODE_CAMERA = 27;
+ /** Key code constant: Clear key. */
+ public static final int KEYCODE_CLEAR = 28;
+ /** Key code constant: 'A' key. */
+ public static final int KEYCODE_A = 29;
+ /** Key code constant: 'B' key. */
+ public static final int KEYCODE_B = 30;
+ /** Key code constant: 'C' key. */
+ public static final int KEYCODE_C = 31;
+ /** Key code constant: 'D' key. */
+ public static final int KEYCODE_D = 32;
+ /** Key code constant: 'E' key. */
+ public static final int KEYCODE_E = 33;
+ /** Key code constant: 'F' key. */
+ public static final int KEYCODE_F = 34;
+ /** Key code constant: 'G' key. */
+ public static final int KEYCODE_G = 35;
+ /** Key code constant: 'H' key. */
+ public static final int KEYCODE_H = 36;
+ /** Key code constant: 'I' key. */
+ public static final int KEYCODE_I = 37;
+ /** Key code constant: 'J' key. */
+ public static final int KEYCODE_J = 38;
+ /** Key code constant: 'K' key. */
+ public static final int KEYCODE_K = 39;
+ /** Key code constant: 'L' key. */
+ public static final int KEYCODE_L = 40;
+ /** Key code constant: 'M' key. */
+ public static final int KEYCODE_M = 41;
+ /** Key code constant: 'N' key. */
+ public static final int KEYCODE_N = 42;
+ /** Key code constant: 'O' key. */
+ public static final int KEYCODE_O = 43;
+ /** Key code constant: 'P' key. */
+ public static final int KEYCODE_P = 44;
+ /** Key code constant: 'Q' key. */
+ public static final int KEYCODE_Q = 45;
+ /** Key code constant: 'R' key. */
+ public static final int KEYCODE_R = 46;
+ /** Key code constant: 'S' key. */
+ public static final int KEYCODE_S = 47;
+ /** Key code constant: 'T' key. */
+ public static final int KEYCODE_T = 48;
+ /** Key code constant: 'U' key. */
+ public static final int KEYCODE_U = 49;
+ /** Key code constant: 'V' key. */
+ public static final int KEYCODE_V = 50;
+ /** Key code constant: 'W' key. */
+ public static final int KEYCODE_W = 51;
+ /** Key code constant: 'X' key. */
+ public static final int KEYCODE_X = 52;
+ /** Key code constant: 'Y' key. */
+ public static final int KEYCODE_Y = 53;
+ /** Key code constant: 'Z' key. */
+ public static final int KEYCODE_Z = 54;
+ /** Key code constant: ',' key. */
+ public static final int KEYCODE_COMMA = 55;
+ /** Key code constant: '.' key. */
+ public static final int KEYCODE_PERIOD = 56;
+ /** Key code constant: Left Alt modifier key. */
+ public static final int KEYCODE_ALT_LEFT = 57;
+ /** Key code constant: Right Alt modifier key. */
+ public static final int KEYCODE_ALT_RIGHT = 58;
+ /** Key code constant: Left Shift modifier key. */
+ public static final int KEYCODE_SHIFT_LEFT = 59;
+ /** Key code constant: Right Shift modifier key. */
+ public static final int KEYCODE_SHIFT_RIGHT = 60;
+ /** Key code constant: Tab key. */
+ public static final int KEYCODE_TAB = 61;
+ /** Key code constant: Space key. */
+ public static final int KEYCODE_SPACE = 62;
+ /** Key code constant: Symbol modifier key.
+ * Used to enter alternate symbols. */
+ public static final int KEYCODE_SYM = 63;
+ /** Key code constant: Explorer special function key.
+ * Used to launch a browser application. */
+ public static final int KEYCODE_EXPLORER = 64;
+ /** Key code constant: Envelope special function key.
+ * Used to launch a mail application. */
+ public static final int KEYCODE_ENVELOPE = 65;
+ /** Key code constant: Enter key. */
+ public static final int KEYCODE_ENTER = 66;
+ /** Key code constant: Backspace key.
+ * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
+ public static final int KEYCODE_DEL = 67;
+ /** Key code constant: '`' (backtick) key. */
+ public static final int KEYCODE_GRAVE = 68;
+ /** Key code constant: '-'. */
+ public static final int KEYCODE_MINUS = 69;
+ /** Key code constant: '=' key. */
+ public static final int KEYCODE_EQUALS = 70;
+ /** Key code constant: '[' key. */
+ public static final int KEYCODE_LEFT_BRACKET = 71;
+ /** Key code constant: ']' key. */
+ public static final int KEYCODE_RIGHT_BRACKET = 72;
+ /** Key code constant: '\' key. */
+ public static final int KEYCODE_BACKSLASH = 73;
+ /** Key code constant: ';' key. */
+ public static final int KEYCODE_SEMICOLON = 74;
+ /** Key code constant: ''' (apostrophe) key. */
+ public static final int KEYCODE_APOSTROPHE = 75;
+ /** Key code constant: '/' key. */
+ public static final int KEYCODE_SLASH = 76;
+ /** Key code constant: '@' key. */
+ public static final int KEYCODE_AT = 77;
+ /** Key code constant: Number modifier key.
+ * Used to enter numeric symbols.
+ * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
+ * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
+ public static final int KEYCODE_NUM = 78;
+ /** Key code constant: Headset Hook key.
+ * Used to hang up calls and stop media. */
+ public static final int KEYCODE_HEADSETHOOK = 79;
+ /** Key code constant: Camera Focus key.
+ * Used to focus the camera. */
+ public static final int KEYCODE_FOCUS = 80; // *Camera* focus
+ /** Key code constant: '+' key. */
+ public static final int KEYCODE_PLUS = 81;
+ /** Key code constant: Menu key. */
+ public static final int KEYCODE_MENU = 82;
+ /** Key code constant: Notification key. */
+ public static final int KEYCODE_NOTIFICATION = 83;
+ /** Key code constant: Search key. */
+ public static final int KEYCODE_SEARCH = 84;
+ /** Key code constant: Play/Pause media key. */
+ public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
+ /** Key code constant: Stop media key. */
+ public static final int KEYCODE_MEDIA_STOP = 86;
+ /** Key code constant: Play Next media key. */
+ public static final int KEYCODE_MEDIA_NEXT = 87;
+ /** Key code constant: Play Previous media key. */
+ public static final int KEYCODE_MEDIA_PREVIOUS = 88;
+ /** Key code constant: Rewind media key. */
+ public static final int KEYCODE_MEDIA_REWIND = 89;
+ /** Key code constant: Fast Forward media key. */
+ public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
+ /** Key code constant: Mute key.
+ * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+ public static final int KEYCODE_MUTE = 91;
+ /** Key code constant: Page Up key. */
+ public static final int KEYCODE_PAGE_UP = 92;
+ /** Key code constant: Page Down key. */
+ public static final int KEYCODE_PAGE_DOWN = 93;
+ /** Key code constant: Picture Symbols modifier key.
+ * Used to switch symbol sets (Emoji, Kao-moji). */
+ public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
+ /** Key code constant: Switch Charset modifier key.
+ * Used to switch character sets (Kanji, Katakana). */
+ public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
+ /** Key code constant: A Button key.
+ * On a game controller, the A button should be either the button labeled A
+ * or the first button on the bottom row of controller buttons. */
+ public static final int KEYCODE_BUTTON_A = 96;
+ /** Key code constant: B Button key.
+ * On a game controller, the B button should be either the button labeled B
+ * or the second button on the bottom row of controller buttons. */
+ public static final int KEYCODE_BUTTON_B = 97;
+ /** Key code constant: C Button key.
+ * On a game controller, the C button should be either the button labeled C
+ * or the third button on the bottom row of controller buttons. */
+ public static final int KEYCODE_BUTTON_C = 98;
+ /** Key code constant: X Button key.
+ * On a game controller, the X button should be either the button labeled X
+ * or the first button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_X = 99;
+ /** Key code constant: Y Button key.
+ * On a game controller, the Y button should be either the button labeled Y
+ * or the second button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Y = 100;
+ /** Key code constant: Z Button key.
+ * On a game controller, the Z button should be either the button labeled Z
+ * or the third button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Z = 101;
+ /** Key code constant: L1 Button key.
+ * On a game controller, the L1 button should be either the button labeled L1 (or L)
+ * or the top left trigger button. */
+ public static final int KEYCODE_BUTTON_L1 = 102;
+ /** Key code constant: R1 Button key.
+ * On a game controller, the R1 button should be either the button labeled R1 (or R)
+ * or the top right trigger button. */
+ public static final int KEYCODE_BUTTON_R1 = 103;
+ /** Key code constant: L2 Button key.
+ * On a game controller, the L2 button should be either the button labeled L2
+ * or the bottom left trigger button. */
+ public static final int KEYCODE_BUTTON_L2 = 104;
+ /** Key code constant: R2 Button key.
+ * On a game controller, the R2 button should be either the button labeled R2
+ * or the bottom right trigger button. */
+ public static final int KEYCODE_BUTTON_R2 = 105;
+ /** Key code constant: Left Thumb Button key.
+ * On a game controller, the left thumb button indicates that the left (or only)
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBL = 106;
+ /** Key code constant: Right Thumb Button key.
+ * On a game controller, the right thumb button indicates that the right
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBR = 107;
+ /** Key code constant: Start Button key.
+ * On a game controller, the button labeled Start. */
+ public static final int KEYCODE_BUTTON_START = 108;
+ /** Key code constant: Select Button key.
+ * On a game controller, the button labeled Select. */
+ public static final int KEYCODE_BUTTON_SELECT = 109;
+ /** Key code constant: Mode Button key.
+ * On a game controller, the button labeled Mode. */
+ public static final int KEYCODE_BUTTON_MODE = 110;
+ /** Key code constant: Escape key. */
+ public static final int KEYCODE_ESCAPE = 111;
+ /** Key code constant: Forward Delete key.
+ * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
+ public static final int KEYCODE_FORWARD_DEL = 112;
+ /** Key code constant: Left Control modifier key. */
+ public static final int KEYCODE_CTRL_LEFT = 113;
+ /** Key code constant: Right Control modifier key. */
+ public static final int KEYCODE_CTRL_RIGHT = 114;
+ /** Key code constant: Caps Lock key. */
+ public static final int KEYCODE_CAPS_LOCK = 115;
+ /** Key code constant: Scroll Lock key. */
+ public static final int KEYCODE_SCROLL_LOCK = 116;
+ /** Key code constant: Left Meta modifier key. */
+ public static final int KEYCODE_META_LEFT = 117;
+ /** Key code constant: Right Meta modifier key. */
+ public static final int KEYCODE_META_RIGHT = 118;
+ /** Key code constant: Function modifier key. */
+ public static final int KEYCODE_FUNCTION = 119;
+ /** Key code constant: System Request / Print Screen key. */
+ public static final int KEYCODE_SYSRQ = 120;
+ /** Key code constant: Break / Pause key. */
+ public static final int KEYCODE_BREAK = 121;
+ /** Key code constant: Home Movement key.
+ * Used for scrolling or moving the cursor around to the start of a line
+ * or to the top of a list. */
+ public static final int KEYCODE_MOVE_HOME = 122;
+ /** Key code constant: End Movement key.
+ * Used for scrolling or moving the cursor around to the end of a line
+ * or to the bottom of a list. */
+ public static final int KEYCODE_MOVE_END = 123;
+ /** Key code constant: Insert key.
+ * Toggles insert / overwrite edit mode. */
+ public static final int KEYCODE_INSERT = 124;
+ /** Key code constant: Forward key.
+ * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */
+ public static final int KEYCODE_FORWARD = 125;
+ /** Key code constant: Play media key. */
+ public static final int KEYCODE_MEDIA_PLAY = 126;
+ /** Key code constant: Pause media key. */
+ public static final int KEYCODE_MEDIA_PAUSE = 127;
+ /** Key code constant: Close media key.
+ * May be used to close a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_CLOSE = 128;
+ /** Key code constant: Eject media key.
+ * May be used to eject a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_EJECT = 129;
+ /** Key code constant: Record media key. */
+ public static final int KEYCODE_MEDIA_RECORD = 130;
+ /** Key code constant: F1 key. */
+ public static final int KEYCODE_F1 = 131;
+ /** Key code constant: F2 key. */
+ public static final int KEYCODE_F2 = 132;
+ /** Key code constant: F3 key. */
+ public static final int KEYCODE_F3 = 133;
+ /** Key code constant: F4 key. */
+ public static final int KEYCODE_F4 = 134;
+ /** Key code constant: F5 key. */
+ public static final int KEYCODE_F5 = 135;
+ /** Key code constant: F6 key. */
+ public static final int KEYCODE_F6 = 136;
+ /** Key code constant: F7 key. */
+ public static final int KEYCODE_F7 = 137;
+ /** Key code constant: F8 key. */
+ public static final int KEYCODE_F8 = 138;
+ /** Key code constant: F9 key. */
+ public static final int KEYCODE_F9 = 139;
+ /** Key code constant: F10 key. */
+ public static final int KEYCODE_F10 = 140;
+ /** Key code constant: F11 key. */
+ public static final int KEYCODE_F11 = 141;
+ /** Key code constant: F12 key. */
+ public static final int KEYCODE_F12 = 142;
+ /** Key code constant: Num Lock key.
+ * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
+ * This key alters the behavior of other keys on the numeric keypad. */
+ public static final int KEYCODE_NUM_LOCK = 143;
+ /** Key code constant: Numeric keypad '0' key. */
+ public static final int KEYCODE_NUMPAD_0 = 144;
+ /** Key code constant: Numeric keypad '1' key. */
+ public static final int KEYCODE_NUMPAD_1 = 145;
+ /** Key code constant: Numeric keypad '2' key. */
+ public static final int KEYCODE_NUMPAD_2 = 146;
+ /** Key code constant: Numeric keypad '3' key. */
+ public static final int KEYCODE_NUMPAD_3 = 147;
+ /** Key code constant: Numeric keypad '4' key. */
+ public static final int KEYCODE_NUMPAD_4 = 148;
+ /** Key code constant: Numeric keypad '5' key. */
+ public static final int KEYCODE_NUMPAD_5 = 149;
+ /** Key code constant: Numeric keypad '6' key. */
+ public static final int KEYCODE_NUMPAD_6 = 150;
+ /** Key code constant: Numeric keypad '7' key. */
+ public static final int KEYCODE_NUMPAD_7 = 151;
+ /** Key code constant: Numeric keypad '8' key. */
+ public static final int KEYCODE_NUMPAD_8 = 152;
+ /** Key code constant: Numeric keypad '9' key. */
+ public static final int KEYCODE_NUMPAD_9 = 153;
+ /** Key code constant: Numeric keypad '/' key (for division). */
+ public static final int KEYCODE_NUMPAD_DIVIDE = 154;
+ /** Key code constant: Numeric keypad '*' key (for multiplication). */
+ public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
+ /** Key code constant: Numeric keypad '-' key (for subtraction). */
+ public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
+ /** Key code constant: Numeric keypad '+' key (for addition). */
+ public static final int KEYCODE_NUMPAD_ADD = 157;
+ /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_DOT = 158;
+ /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_COMMA = 159;
+ /** Key code constant: Numeric keypad Enter key. */
+ public static final int KEYCODE_NUMPAD_ENTER = 160;
+ /** Key code constant: Numeric keypad '=' key. */
+ public static final int KEYCODE_NUMPAD_EQUALS = 161;
+ /** Key code constant: Numeric keypad '(' key. */
+ public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
+ /** Key code constant: Numeric keypad ')' key. */
+ public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
+ /** Key code constant: Volume Mute key.
+ * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
+ * This key should normally be implemented as a toggle such that the first press
+ * mutes the speaker and the second press restores the original volume. */
+ public static final int KEYCODE_VOLUME_MUTE = 164;
+ /** Key code constant: Info key.
+ * Common on TV remotes to show additional information related to what is
+ * currently being viewed. */
+ public static final int KEYCODE_INFO = 165;
+ /** Key code constant: Channel up key.
+ * On TV remotes, increments the television channel. */
+ public static final int KEYCODE_CHANNEL_UP = 166;
+ /** Key code constant: Channel down key.
+ * On TV remotes, decrements the television channel. */
+ public static final int KEYCODE_CHANNEL_DOWN = 167;
+ /** Key code constant: Zoom in key. */
+ public static final int KEYCODE_ZOOM_IN = 168;
+ /** Key code constant: Zoom out key. */
+ public static final int KEYCODE_ZOOM_OUT = 169;
+ /** Key code constant: TV key.
+ * On TV remotes, switches to viewing live TV. */
+ public static final int KEYCODE_TV = 170;
+ /** Key code constant: Window key.
+ * On TV remotes, toggles picture-in-picture mode or other windowing functions.
+ * On Android Wear devices, triggers a display offset. */
+ public static final int KEYCODE_WINDOW = 171;
+ /** Key code constant: Guide key.
+ * On TV remotes, shows a programming guide. */
+ public static final int KEYCODE_GUIDE = 172;
+ /** Key code constant: DVR key.
+ * On some TV remotes, switches to a DVR mode for recorded shows. */
+ public static final int KEYCODE_DVR = 173;
+ /** Key code constant: Bookmark key.
+ * On some TV remotes, bookmarks content or web pages. */
+ public static final int KEYCODE_BOOKMARK = 174;
+ /** Key code constant: Toggle captions key.
+ * Switches the mode for closed-captioning text, for example during television shows. */
+ public static final int KEYCODE_CAPTIONS = 175;
+ /** Key code constant: Settings key.
+ * Starts the system settings activity. */
+ public static final int KEYCODE_SETTINGS = 176;
+ /** Key code constant: TV power key.
+ * On TV remotes, toggles the power on a television screen. */
+ public static final int KEYCODE_TV_POWER = 177;
+ /** Key code constant: TV input key.
+ * On TV remotes, switches the input on a television screen. */
+ public static final int KEYCODE_TV_INPUT = 178;
+ /** Key code constant: Set-top-box power key.
+ * On TV remotes, toggles the power on an external Set-top-box. */
+ public static final int KEYCODE_STB_POWER = 179;
+ /** Key code constant: Set-top-box input key.
+ * On TV remotes, switches the input mode on an external Set-top-box. */
+ public static final int KEYCODE_STB_INPUT = 180;
+ /** Key code constant: A/V Receiver power key.
+ * On TV remotes, toggles the power on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_POWER = 181;
+ /** Key code constant: A/V Receiver input key.
+ * On TV remotes, switches the input mode on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_INPUT = 182;
+ /** Key code constant: Red "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_RED = 183;
+ /** Key code constant: Green "programmable" key.
+ * On TV remotes, actsas a contextual/programmable key. */
+ public static final int KEYCODE_PROG_GREEN = 184;
+ /** Key code constant: Yellow "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_YELLOW = 185;
+ /** Key code constant: Blue "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_BLUE = 186;
+ /** Key code constant: App switch key.
+ * Should bring up the application switcher dialog. */
+ public static final int KEYCODE_APP_SWITCH = 187;
+ /** Key code constant: Generic Game Pad Button #1.*/
+ public static final int KEYCODE_BUTTON_1 = 188;
+ /** Key code constant: Generic Game Pad Button #2.*/
+ public static final int KEYCODE_BUTTON_2 = 189;
+ /** Key code constant: Generic Game Pad Button #3.*/
+ public static final int KEYCODE_BUTTON_3 = 190;
+ /** Key code constant: Generic Game Pad Button #4.*/
+ public static final int KEYCODE_BUTTON_4 = 191;
+ /** Key code constant: Generic Game Pad Button #5.*/
+ public static final int KEYCODE_BUTTON_5 = 192;
+ /** Key code constant: Generic Game Pad Button #6.*/
+ public static final int KEYCODE_BUTTON_6 = 193;
+ /** Key code constant: Generic Game Pad Button #7.*/
+ public static final int KEYCODE_BUTTON_7 = 194;
+ /** Key code constant: Generic Game Pad Button #8.*/
+ public static final int KEYCODE_BUTTON_8 = 195;
+ /** Key code constant: Generic Game Pad Button #9.*/
+ public static final int KEYCODE_BUTTON_9 = 196;
+ /** Key code constant: Generic Game Pad Button #10.*/
+ public static final int KEYCODE_BUTTON_10 = 197;
+ /** Key code constant: Generic Game Pad Button #11.*/
+ public static final int KEYCODE_BUTTON_11 = 198;
+ /** Key code constant: Generic Game Pad Button #12.*/
+ public static final int KEYCODE_BUTTON_12 = 199;
+ /** Key code constant: Generic Game Pad Button #13.*/
+ public static final int KEYCODE_BUTTON_13 = 200;
+ /** Key code constant: Generic Game Pad Button #14.*/
+ public static final int KEYCODE_BUTTON_14 = 201;
+ /** Key code constant: Generic Game Pad Button #15.*/
+ public static final int KEYCODE_BUTTON_15 = 202;
+ /** Key code constant: Generic Game Pad Button #16.*/
+ public static final int KEYCODE_BUTTON_16 = 203;
+ /** Key code constant: Language Switch key.
+ * Toggles the current input language such as switching between English and Japanese on
+ * a QWERTY keyboard. On some devices, the same function may be performed by
+ * pressing Shift+Spacebar. */
+ public static final int KEYCODE_LANGUAGE_SWITCH = 204;
+ /** Key code constant: Manner Mode key.
+ * Toggles silent or vibrate mode on and off to make the device behave more politely
+ * in certain settings such as on a crowded train. On some devices, the key may only
+ * operate when long-pressed. */
+ public static final int KEYCODE_MANNER_MODE = 205;
+ /** Key code constant: 3D Mode key.
+ * Toggles the display between 2D and 3D mode. */
+ public static final int KEYCODE_3D_MODE = 206;
+ /** Key code constant: Contacts special function key.
+ * Used to launch an address book application. */
+ public static final int KEYCODE_CONTACTS = 207;
+ /** Key code constant: Calendar special function key.
+ * Used to launch a calendar application. */
+ public static final int KEYCODE_CALENDAR = 208;
+ /** Key code constant: Music special function key.
+ * Used to launch a music player application. */
+ public static final int KEYCODE_MUSIC = 209;
+ /** Key code constant: Calculator special function key.
+ * Used to launch a calculator application. */
+ public static final int KEYCODE_CALCULATOR = 210;
+ /** Key code constant: Japanese full-width / half-width key. */
+ public static final int KEYCODE_ZENKAKU_HANKAKU = 211;
+ /** Key code constant: Japanese alphanumeric key. */
+ public static final int KEYCODE_EISU = 212;
+ /** Key code constant: Japanese non-conversion key. */
+ public static final int KEYCODE_MUHENKAN = 213;
+ /** Key code constant: Japanese conversion key. */
+ public static final int KEYCODE_HENKAN = 214;
+ /** Key code constant: Japanese katakana / hiragana key. */
+ public static final int KEYCODE_KATAKANA_HIRAGANA = 215;
+ /** Key code constant: Japanese Yen key. */
+ public static final int KEYCODE_YEN = 216;
+ /** Key code constant: Japanese Ro key. */
+ public static final int KEYCODE_RO = 217;
+ /** Key code constant: Japanese kana key. */
+ public static final int KEYCODE_KANA = 218;
+ /** Key code constant: Assist key.
+ * Launches the global assist activity. Not delivered to applications. */
+ public static final int KEYCODE_ASSIST = 219;
+ /** Key code constant: Brightness Down key.
+ * Adjusts the screen brightness down. */
+ public static final int KEYCODE_BRIGHTNESS_DOWN = 220;
+ /** Key code constant: Brightness Up key.
+ * Adjusts the screen brightness up. */
+ public static final int KEYCODE_BRIGHTNESS_UP = 221;
+ /** Key code constant: Audio Track key.
+ * Switches the audio tracks. */
+ public static final int KEYCODE_MEDIA_AUDIO_TRACK = 222;
+ /** Key code constant: Sleep key.
+ * Puts the device to sleep. Behaves somewhat like {@link #KEYCODE_POWER} but it
+ * has no effect if the device is already asleep. */
+ public static final int KEYCODE_SLEEP = 223;
+ /** Key code constant: Wakeup key.
+ * Wakes up the device. Behaves somewhat like {@link #KEYCODE_POWER} but it
+ * has no effect if the device is already awake. */
+ public static final int KEYCODE_WAKEUP = 224;
+ /** Key code constant: Pairing key.
+ * Initiates peripheral pairing mode. Useful for pairing remote control
+ * devices or game controllers, especially if no other input mode is
+ * available. */
+ public static final int KEYCODE_PAIRING = 225;
+ /** Key code constant: Media Top Menu key.
+ * Goes to the top of media menu. */
+ public static final int KEYCODE_MEDIA_TOP_MENU = 226;
+ /** Key code constant: '11' key. */
+ public static final int KEYCODE_11 = 227;
+ /** Key code constant: '12' key. */
+ public static final int KEYCODE_12 = 228;
+ /** Key code constant: Last Channel key.
+ * Goes to the last viewed channel. */
+ public static final int KEYCODE_LAST_CHANNEL = 229;
+ /** Key code constant: TV data service key.
+ * Displays data services like weather, sports. */
+ public static final int KEYCODE_TV_DATA_SERVICE = 230;
+ /** Key code constant: Voice Assist key.
+ * Launches the global voice assist activity. Not delivered to applications. */
+ public static final int KEYCODE_VOICE_ASSIST = 231;
+ /** Key code constant: Radio key.
+ * Toggles TV service / Radio service. */
+ public static final int KEYCODE_TV_RADIO_SERVICE = 232;
+ /** Key code constant: Teletext key.
+ * Displays Teletext service. */
+ public static final int KEYCODE_TV_TELETEXT = 233;
+ /** Key code constant: Number entry key.
+ * Initiates to enter multi-digit channel nubmber when each digit key is assigned
+ * for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
+ * User Control Code. */
+ public static final int KEYCODE_TV_NUMBER_ENTRY = 234;
+ /** Key code constant: Analog Terrestrial key.
+ * Switches to analog terrestrial broadcast service. */
+ public static final int KEYCODE_TV_TERRESTRIAL_ANALOG = 235;
+ /** Key code constant: Digital Terrestrial key.
+ * Switches to digital terrestrial broadcast service. */
+ public static final int KEYCODE_TV_TERRESTRIAL_DIGITAL = 236;
+ /** Key code constant: Satellite key.
+ * Switches to digital satellite broadcast service. */
+ public static final int KEYCODE_TV_SATELLITE = 237;
+ /** Key code constant: BS key.
+ * Switches to BS digital satellite broadcasting service available in Japan. */
+ public static final int KEYCODE_TV_SATELLITE_BS = 238;
+ /** Key code constant: CS key.
+ * Switches to CS digital satellite broadcasting service available in Japan. */
+ public static final int KEYCODE_TV_SATELLITE_CS = 239;
+ /** Key code constant: BS/CS key.
+ * Toggles between BS and CS digital satellite services. */
+ public static final int KEYCODE_TV_SATELLITE_SERVICE = 240;
+ /** Key code constant: Toggle Network key.
+ * Toggles selecting broacast services. */
+ public static final int KEYCODE_TV_NETWORK = 241;
+ /** Key code constant: Antenna/Cable key.
+ * Toggles broadcast input source between antenna and cable. */
+ public static final int KEYCODE_TV_ANTENNA_CABLE = 242;
+ /** Key code constant: HDMI #1 key.
+ * Switches to HDMI input #1. */
+ public static final int KEYCODE_TV_INPUT_HDMI_1 = 243;
+ /** Key code constant: HDMI #2 key.
+ * Switches to HDMI input #2. */
+ public static final int KEYCODE_TV_INPUT_HDMI_2 = 244;
+ /** Key code constant: HDMI #3 key.
+ * Switches to HDMI input #3. */
+ public static final int KEYCODE_TV_INPUT_HDMI_3 = 245;
+ /** Key code constant: HDMI #4 key.
+ * Switches to HDMI input #4. */
+ public static final int KEYCODE_TV_INPUT_HDMI_4 = 246;
+ /** Key code constant: Composite #1 key.
+ * Switches to composite video input #1. */
+ public static final int KEYCODE_TV_INPUT_COMPOSITE_1 = 247;
+ /** Key code constant: Composite #2 key.
+ * Switches to composite video input #2. */
+ public static final int KEYCODE_TV_INPUT_COMPOSITE_2 = 248;
+ /** Key code constant: Component #1 key.
+ * Switches to component video input #1. */
+ public static final int KEYCODE_TV_INPUT_COMPONENT_1 = 249;
+ /** Key code constant: Component #2 key.
+ * Switches to component video input #2. */
+ public static final int KEYCODE_TV_INPUT_COMPONENT_2 = 250;
+ /** Key code constant: VGA #1 key.
+ * Switches to VGA (analog RGB) input #1. */
+ public static final int KEYCODE_TV_INPUT_VGA_1 = 251;
+ /** Key code constant: Audio description key.
+ * Toggles audio description off / on. */
+ public static final int KEYCODE_TV_AUDIO_DESCRIPTION = 252;
+ /** Key code constant: Audio description mixing volume up key.
+ * Louden audio description volume as compared with normal audio volume. */
+ public static final int KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253;
+ /** Key code constant: Audio description mixing volume down key.
+ * Lessen audio description volume as compared with normal audio volume. */
+ public static final int KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254;
+ /** Key code constant: Zoom mode key.
+ * Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.) */
+ public static final int KEYCODE_TV_ZOOM_MODE = 255;
+ /** Key code constant: Contents menu key.
+ * Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
+ * Code */
+ public static final int KEYCODE_TV_CONTENTS_MENU = 256;
+ /** Key code constant: Media context menu key.
+ * Goes to the context menu of media contents. Corresponds to Media Context-sensitive
+ * Menu (0x11) of CEC User Control Code. */
+ public static final int KEYCODE_TV_MEDIA_CONTEXT_MENU = 257;
+ /** Key code constant: Timer programming key.
+ * Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
+ * CEC User Control Code. */
+ public static final int KEYCODE_TV_TIMER_PROGRAMMING = 258;
+ /** Key code constant: Help key. */
+ public static final int KEYCODE_HELP = 259;
+ /** Key code constant: Navigate to previous key.
+ * Goes backward by one item in an ordered collection of items. */
+ public static final int KEYCODE_NAVIGATE_PREVIOUS = 260;
+ /** Key code constant: Navigate to next key.
+ * Advances to the next item in an ordered collection of items. */
+ public static final int KEYCODE_NAVIGATE_NEXT = 261;
+ /** Key code constant: Navigate in key.
+ * Activates the item that currently has focus or expands to the next level of a navigation
+ * hierarchy. */
+ public static final int KEYCODE_NAVIGATE_IN = 262;
+ /** Key code constant: Navigate out key.
+ * Backs out one level of a navigation hierarchy or collapses the item that currently has
+ * focus. */
+ public static final int KEYCODE_NAVIGATE_OUT = 263;
+ /** Key code constant: Primary stem key for Wear
+ * Main power/reset button on watch. */
+ public static final int KEYCODE_STEM_PRIMARY = 264;
+ /** Key code constant: Generic stem key 1 for Wear */
+ public static final int KEYCODE_STEM_1 = 265;
+ /** Key code constant: Generic stem key 2 for Wear */
+ public static final int KEYCODE_STEM_2 = 266;
+ /** Key code constant: Generic stem key 3 for Wear */
+ public static final int KEYCODE_STEM_3 = 267;
+ /** Key code constant: Directional Pad Up-Left */
+ public static final int KEYCODE_DPAD_UP_LEFT = 268;
+ /** Key code constant: Directional Pad Down-Left */
+ public static final int KEYCODE_DPAD_DOWN_LEFT = 269;
+ /** Key code constant: Directional Pad Up-Right */
+ public static final int KEYCODE_DPAD_UP_RIGHT = 270;
+ /** Key code constant: Directional Pad Down-Right */
+ public static final int KEYCODE_DPAD_DOWN_RIGHT = 271;
+ /** Key code constant: Skip forward media key. */
+ public static final int KEYCODE_MEDIA_SKIP_FORWARD = 272;
+ /** Key code constant: Skip backward media key. */
+ public static final int KEYCODE_MEDIA_SKIP_BACKWARD = 273;
+ /** Key code constant: Step forward media key.
+ * Steps media forward, one frame at a time. */
+ public static final int KEYCODE_MEDIA_STEP_FORWARD = 274;
+ /** Key code constant: Step backward media key.
+ * Steps media backward, one frame at a time. */
+ public static final int KEYCODE_MEDIA_STEP_BACKWARD = 275;
+ /** Key code constant: put device to sleep unless a wakelock is held. */
+ public static final int KEYCODE_SOFT_SLEEP = 276;
+ /** Key code constant: Cut key. */
+ public static final int KEYCODE_CUT = 277;
+ /** Key code constant: Copy key. */
+ public static final int KEYCODE_COPY = 278;
+ /** Key code constant: Paste key. */
+ public static final int KEYCODE_PASTE = 279;
+ /** Key code constant: Consumed by the system for navigation up */
+ public static final int KEYCODE_SYSTEM_NAVIGATION_UP = 280;
+ /** Key code constant: Consumed by the system for navigation down */
+ public static final int KEYCODE_SYSTEM_NAVIGATION_DOWN = 281;
+ /** Key code constant: Consumed by the system for navigation left*/
+ public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
+ /** Key code constant: Consumed by the system for navigation right */
+ public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
+ /** Key code constant: Show all apps
+ * @hide */
+ public static final int KEYCODE_ALL_APPS = 284;
+
+ private static final int LAST_KEYCODE = KEYCODE_ALL_APPS;
+
+ // NOTE: If you add a new keycode here you must also add it to:
+ // isSystem()
+ // isWakeKey()
+ // frameworks/native/include/android/keycodes.h
+ // frameworks/native/include/input/InputEventLabels.h
+ // frameworks/base/core/res/res/values/attrs.xml
+ // emulator?
+ // LAST_KEYCODE
+ //
+ // Also Android currently does not reserve code ranges for vendor-
+ // specific key codes. If you have new key codes to have, you
+ // MUST contribute a patch to the open source project to define
+ // those new codes. This is intended to maintain a consistent
+ // set of key code definitions across all Android devices.
+
+ // Symbolic names of all metakeys in bit order from least significant to most significant.
+ // Accordingly there are exactly 32 values in this table.
+ private static final String[] META_SYMBOLIC_NAMES = new String[] {
+ "META_SHIFT_ON",
+ "META_ALT_ON",
+ "META_SYM_ON",
+ "META_FUNCTION_ON",
+ "META_ALT_LEFT_ON",
+ "META_ALT_RIGHT_ON",
+ "META_SHIFT_LEFT_ON",
+ "META_SHIFT_RIGHT_ON",
+ "META_CAP_LOCKED",
+ "META_ALT_LOCKED",
+ "META_SYM_LOCKED",
+ "0x00000800",
+ "META_CTRL_ON",
+ "META_CTRL_LEFT_ON",
+ "META_CTRL_RIGHT_ON",
+ "0x00008000",
+ "META_META_ON",
+ "META_META_LEFT_ON",
+ "META_META_RIGHT_ON",
+ "0x00080000",
+ "META_CAPS_LOCK_ON",
+ "META_NUM_LOCK_ON",
+ "META_SCROLL_LOCK_ON",
+ "0x00800000",
+ "0x01000000",
+ "0x02000000",
+ "0x04000000",
+ "0x08000000",
+ "0x10000000",
+ "0x20000000",
+ "0x40000000",
+ "0x80000000",
+ };
+
+ private static final String LABEL_PREFIX = "KEYCODE_";
+
+ /**
+ * @deprecated There are now more than MAX_KEYCODE keycodes.
+ * Use {@link #getMaxKeyCode()} instead.
+ */
+ @Deprecated
+ public static final int MAX_KEYCODE = 84;
+
+ /**
+ * {@link #getAction} value: the key has been pressed down.
+ */
+ public static final int ACTION_DOWN = 0;
+ /**
+ * {@link #getAction} value: the key has been released.
+ */
+ public static final int ACTION_UP = 1;
+ /**
+ * {@link #getAction} value: multiple duplicate key events have
+ * occurred in a row, or a complex string is being delivered. If the
+ * key code is not {#link {@link #KEYCODE_UNKNOWN} then the
+ * {#link {@link #getRepeatCount()} method returns the number of times
+ * the given key code should be executed.
+ * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then
+ * this is a sequence of characters as returned by {@link #getCharacters}.
+ */
+ public static final int ACTION_MULTIPLE = 2;
+
+ /**
+ * SHIFT key locked in CAPS mode.
+ * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+ * @hide
+ */
+ public static final int META_CAP_LOCKED = 0x100;
+
+ /**
+ * ALT key locked.
+ * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+ * @hide
+ */
+ public static final int META_ALT_LOCKED = 0x200;
+
+ /**
+ * SYM key locked.
+ * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API.
+ * @hide
+ */
+ public static final int META_SYM_LOCKED = 0x400;
+
+ /**
+ * Text is in selection mode.
+ * Reserved for use by {@link MetaKeyKeyListener} for a private unpublished constant
+ * in its API that is currently being retained for legacy reasons.
+ * @hide
+ */
+ public static final int META_SELECTING = 0x800;
+
+ /**
+ * <p>This mask is used to check whether one of the ALT meta keys is pressed.</p>
+ *
+ * @see #isAltPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_ALT_LEFT
+ * @see #KEYCODE_ALT_RIGHT
+ */
+ public static final int META_ALT_ON = 0x02;
+
+ /**
+ * <p>This mask is used to check whether the left ALT meta key is pressed.</p>
+ *
+ * @see #isAltPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_ALT_LEFT
+ */
+ public static final int META_ALT_LEFT_ON = 0x10;
+
+ /**
+ * <p>This mask is used to check whether the right the ALT meta key is pressed.</p>
+ *
+ * @see #isAltPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_ALT_RIGHT
+ */
+ public static final int META_ALT_RIGHT_ON = 0x20;
+
+ /**
+ * <p>This mask is used to check whether one of the SHIFT meta keys is pressed.</p>
+ *
+ * @see #isShiftPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_SHIFT_LEFT
+ * @see #KEYCODE_SHIFT_RIGHT
+ */
+ public static final int META_SHIFT_ON = 0x1;
+
+ /**
+ * <p>This mask is used to check whether the left SHIFT meta key is pressed.</p>
+ *
+ * @see #isShiftPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_SHIFT_LEFT
+ */
+ public static final int META_SHIFT_LEFT_ON = 0x40;
+
+ /**
+ * <p>This mask is used to check whether the right SHIFT meta key is pressed.</p>
+ *
+ * @see #isShiftPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_SHIFT_RIGHT
+ */
+ public static final int META_SHIFT_RIGHT_ON = 0x80;
+
+ /**
+ * <p>This mask is used to check whether the SYM meta key is pressed.</p>
+ *
+ * @see #isSymPressed()
+ * @see #getMetaState()
+ */
+ public static final int META_SYM_ON = 0x4;
+
+ /**
+ * <p>This mask is used to check whether the FUNCTION meta key is pressed.</p>
+ *
+ * @see #isFunctionPressed()
+ * @see #getMetaState()
+ */
+ public static final int META_FUNCTION_ON = 0x8;
+
+ /**
+ * <p>This mask is used to check whether one of the CTRL meta keys is pressed.</p>
+ *
+ * @see #isCtrlPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_CTRL_LEFT
+ * @see #KEYCODE_CTRL_RIGHT
+ */
+ public static final int META_CTRL_ON = 0x1000;
+
+ /**
+ * <p>This mask is used to check whether the left CTRL meta key is pressed.</p>
+ *
+ * @see #isCtrlPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_CTRL_LEFT
+ */
+ public static final int META_CTRL_LEFT_ON = 0x2000;
+
+ /**
+ * <p>This mask is used to check whether the right CTRL meta key is pressed.</p>
+ *
+ * @see #isCtrlPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_CTRL_RIGHT
+ */
+ public static final int META_CTRL_RIGHT_ON = 0x4000;
+
+ /**
+ * <p>This mask is used to check whether one of the META meta keys is pressed.</p>
+ *
+ * @see #isMetaPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_META_LEFT
+ * @see #KEYCODE_META_RIGHT
+ */
+ public static final int META_META_ON = 0x10000;
+
+ /**
+ * <p>This mask is used to check whether the left META meta key is pressed.</p>
+ *
+ * @see #isMetaPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_META_LEFT
+ */
+ public static final int META_META_LEFT_ON = 0x20000;
+
+ /**
+ * <p>This mask is used to check whether the right META meta key is pressed.</p>
+ *
+ * @see #isMetaPressed()
+ * @see #getMetaState()
+ * @see #KEYCODE_META_RIGHT
+ */
+ public static final int META_META_RIGHT_ON = 0x40000;
+
+ /**
+ * <p>This mask is used to check whether the CAPS LOCK meta key is on.</p>
+ *
+ * @see #isCapsLockOn()
+ * @see #getMetaState()
+ * @see #KEYCODE_CAPS_LOCK
+ */
+ public static final int META_CAPS_LOCK_ON = 0x100000;
+
+ /**
+ * <p>This mask is used to check whether the NUM LOCK meta key is on.</p>
+ *
+ * @see #isNumLockOn()
+ * @see #getMetaState()
+ * @see #KEYCODE_NUM_LOCK
+ */
+ public static final int META_NUM_LOCK_ON = 0x200000;
+
+ /**
+ * <p>This mask is used to check whether the SCROLL LOCK meta key is on.</p>
+ *
+ * @see #isScrollLockOn()
+ * @see #getMetaState()
+ * @see #KEYCODE_SCROLL_LOCK
+ */
+ public static final int META_SCROLL_LOCK_ON = 0x400000;
+
+ /**
+ * This mask is a combination of {@link #META_SHIFT_ON}, {@link #META_SHIFT_LEFT_ON}
+ * and {@link #META_SHIFT_RIGHT_ON}.
+ */
+ public static final int META_SHIFT_MASK = META_SHIFT_ON
+ | META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON;
+
+ /**
+ * This mask is a combination of {@link #META_ALT_ON}, {@link #META_ALT_LEFT_ON}
+ * and {@link #META_ALT_RIGHT_ON}.
+ */
+ public static final int META_ALT_MASK = META_ALT_ON
+ | META_ALT_LEFT_ON | META_ALT_RIGHT_ON;
+
+ /**
+ * This mask is a combination of {@link #META_CTRL_ON}, {@link #META_CTRL_LEFT_ON}
+ * and {@link #META_CTRL_RIGHT_ON}.
+ */
+ public static final int META_CTRL_MASK = META_CTRL_ON
+ | META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON;
+
+ /**
+ * This mask is a combination of {@link #META_META_ON}, {@link #META_META_LEFT_ON}
+ * and {@link #META_META_RIGHT_ON}.
+ */
+ public static final int META_META_MASK = META_META_ON
+ | META_META_LEFT_ON | META_META_RIGHT_ON;
+
+ /**
+ * This mask is set if the device woke because of this key event.
+ *
+ * @deprecated This flag will never be set by the system since the system
+ * consumes all wake keys itself.
+ */
+ @Deprecated
+ public static final int FLAG_WOKE_HERE = 0x1;
+
+ /**
+ * This mask is set if the key event was generated by a software keyboard.
+ */
+ public static final int FLAG_SOFT_KEYBOARD = 0x2;
+
+ /**
+ * This mask is set if we don't want the key event to cause us to leave
+ * touch mode.
+ */
+ public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
+
+ /**
+ * This mask is set if an event was known to come from a trusted part
+ * of the system. That is, the event is known to come from the user,
+ * and could not have been spoofed by a third party component.
+ */
+ public static final int FLAG_FROM_SYSTEM = 0x8;
+
+ /**
+ * This mask is used for compatibility, to identify enter keys that are
+ * coming from an IME whose enter key has been auto-labelled "next" or
+ * "done". This allows TextView to dispatch these as normal enter keys
+ * for old applications, but still do the appropriate action when
+ * receiving them.
+ */
+ public static final int FLAG_EDITOR_ACTION = 0x10;
+
+ /**
+ * When associated with up key events, this indicates that the key press
+ * has been canceled. Typically this is used with virtual touch screen
+ * keys, where the user can slide from the virtual key area on to the
+ * display: in that case, the application will receive a canceled up
+ * event and should not perform the action normally associated with the
+ * key. Note that for this to work, the application can not perform an
+ * action for a key until it receives an up or the long press timeout has
+ * expired.
+ */
+ public static final int FLAG_CANCELED = 0x20;
+
+ /**
+ * This key event was generated by a virtual (on-screen) hard key area.
+ * Typically this is an area of the touchscreen, outside of the regular
+ * display, dedicated to "hardware" buttons.
+ */
+ public static final int FLAG_VIRTUAL_HARD_KEY = 0x40;
+
+ /**
+ * This flag is set for the first key repeat that occurs after the
+ * long press timeout.
+ */
+ public static final int FLAG_LONG_PRESS = 0x80;
+
+ /**
+ * Set when a key event has {@link #FLAG_CANCELED} set because a long
+ * press action was executed while it was down.
+ */
+ public static final int FLAG_CANCELED_LONG_PRESS = 0x100;
+
+ /**
+ * Set for {@link #ACTION_UP} when this event's key code is still being
+ * tracked from its initial down. That is, somebody requested that tracking
+ * started on the key down and a long press has not caused
+ * the tracking to be canceled.
+ */
+ public static final int FLAG_TRACKING = 0x200;
+
+ /**
+ * Set when a key event has been synthesized to implement default behavior
+ * for an event that the application did not handle.
+ * Fallback key events are generated by unhandled trackball motions
+ * (to emulate a directional keypad) and by certain unhandled key presses
+ * that are declared in the key map (such as special function numeric keypad
+ * keys when numlock is off).
+ */
+ public static final int FLAG_FALLBACK = 0x400;
+
+ /**
+ * Signifies that the key is being predispatched.
+ * @hide
+ */
+ public static final int FLAG_PREDISPATCH = 0x20000000;
+
+ /**
+ * Private control to determine when an app is tracking a key sequence.
+ * @hide
+ */
+ public static final int FLAG_START_TRACKING = 0x40000000;
+
+ /**
+ * Private flag that indicates when the system has detected that this key event
+ * may be inconsistent with respect to the sequence of previously delivered key events,
+ * such as when a key up event is sent but the key was not down.
+ *
+ * @hide
+ * @see #isTainted
+ * @see #setTainted
+ */
+ public static final int FLAG_TAINTED = 0x80000000;
+
+ /**
+ * Returns the maximum keycode.
+ */
+ public static int getMaxKeyCode() {
+ return LAST_KEYCODE;
+ }
+
+ /**
+ * Get the character that is produced by putting accent on the character
+ * c.
+ * For example, getDeadChar('`', 'e') returns &egrave;.
+ */
+ public static int getDeadChar(int accent, int c) {
+ return KeyCharacterMap.getDeadChar(accent, c);
+ }
+
+ static final boolean DEBUG = false;
+ static final String TAG = "KeyEvent";
+
+ private static final int MAX_RECYCLED = 10;
+ private static final Object gRecyclerLock = new Object();
+ private static int gRecyclerUsed;
+ private static KeyEvent gRecyclerTop;
+
+ private KeyEvent mNext;
+
+ private int mDeviceId;
+ private int mSource;
+ private int mMetaState;
+ private int mAction;
+ private int mKeyCode;
+ private int mScanCode;
+ private int mRepeatCount;
+ private int mFlags;
+ private long mDownTime;
+ private long mEventTime;
+ private String mCharacters;
+
+ public interface Callback {
+ /**
+ * Called when a key down event has occurred. If you return true,
+ * you can first call {@link KeyEvent#startTracking()
+ * KeyEvent.startTracking()} to have the framework track the event
+ * through its {@link #onKeyUp(int, KeyEvent)} and also call your
+ * {@link #onKeyLongPress(int, KeyEvent)} if it occurs.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyDown(int keyCode, KeyEvent event);
+
+ /**
+ * Called when a long press has occurred. If you return true,
+ * the final key up will have {@link KeyEvent#FLAG_CANCELED} and
+ * {@link KeyEvent#FLAG_CANCELED_LONG_PRESS} set. Note that in
+ * order to receive this callback, someone in the event change
+ * <em>must</em> return true from {@link #onKeyDown} <em>and</em>
+ * call {@link KeyEvent#startTracking()} on the event.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyLongPress(int keyCode, KeyEvent event);
+
+ /**
+ * Called when a key up event has occurred.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyUp(int keyCode, KeyEvent event);
+
+ /**
+ * Called when a user's interaction with an analog control, such as
+ * flinging a trackball, generates simulated down/up events for the same
+ * key multiple times in quick succession.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param count Number of pairs as returned by event.getRepeatCount().
+ * @param event Description of the key event.
+ *
+ * @return If you handled the event, return true. If you want to allow
+ * the event to be handled by the next receiver, return false.
+ */
+ boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
+ }
+
+ private static native String nativeKeyCodeToString(int keyCode);
+ private static native int nativeKeyCodeFromString(String keyCode);
+
+ private KeyEvent() {
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ */
+ public KeyEvent(int action, int code) {
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = 0;
+ mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat) {
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState) {
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ * @param deviceId The device ID that generated the key event.
+ * @param scancode Raw device scan code of the event.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode) {
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ * @param deviceId The device ID that generated the key event.
+ * @param scancode Raw device scan code of the event.
+ * @param flags The flags for this key event
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags) {
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
+ mFlags = flags;
+ }
+
+ /**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ * @param deviceId The device ID that generated the key event.
+ * @param scancode Raw device scan code of the event.
+ * @param flags The flags for this key event
+ * @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags, int source) {
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
+ mFlags = flags;
+ mSource = source;
+ }
+
+ /**
+ * Create a new key event for a string of characters. The key code,
+ * action, repeat count and source will automatically be set to
+ * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and
+ * {@link InputDevice#SOURCE_KEYBOARD} for you.
+ *
+ * @param time The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event occured.
+ * @param characters The string of characters.
+ * @param deviceId The device ID that generated the key event.
+ * @param flags The flags for this key event
+ */
+ public KeyEvent(long time, String characters, int deviceId, int flags) {
+ mDownTime = time;
+ mEventTime = time;
+ mCharacters = characters;
+ mAction = ACTION_MULTIPLE;
+ mKeyCode = KEYCODE_UNKNOWN;
+ mRepeatCount = 0;
+ mDeviceId = deviceId;
+ mFlags = flags;
+ mSource = InputDevice.SOURCE_KEYBOARD;
+ }
+
+ /**
+ * Make an exact copy of an existing key event.
+ */
+ public KeyEvent(KeyEvent origEvent) {
+ mDownTime = origEvent.mDownTime;
+ mEventTime = origEvent.mEventTime;
+ mAction = origEvent.mAction;
+ mKeyCode = origEvent.mKeyCode;
+ mRepeatCount = origEvent.mRepeatCount;
+ mMetaState = origEvent.mMetaState;
+ mDeviceId = origEvent.mDeviceId;
+ mSource = origEvent.mSource;
+ mScanCode = origEvent.mScanCode;
+ mFlags = origEvent.mFlags;
+ mCharacters = origEvent.mCharacters;
+ }
+
+ /**
+ * Copy an existing key event, modifying its time and repeat count.
+ *
+ * @deprecated Use {@link #changeTimeRepeat(KeyEvent, long, int)}
+ * instead.
+ *
+ * @param origEvent The existing event to be copied.
+ * @param eventTime The new event time
+ * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+ * @param newRepeat The new repeat count of the event.
+ */
+ @Deprecated
+ public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
+ mDownTime = origEvent.mDownTime;
+ mEventTime = eventTime;
+ mAction = origEvent.mAction;
+ mKeyCode = origEvent.mKeyCode;
+ mRepeatCount = newRepeat;
+ mMetaState = origEvent.mMetaState;
+ mDeviceId = origEvent.mDeviceId;
+ mSource = origEvent.mSource;
+ mScanCode = origEvent.mScanCode;
+ mFlags = origEvent.mFlags;
+ mCharacters = origEvent.mCharacters;
+ }
+
+ private static KeyEvent obtain() {
+ final KeyEvent ev;
+ synchronized (gRecyclerLock) {
+ ev = gRecyclerTop;
+ if (ev == null) {
+ return new KeyEvent();
+ }
+ gRecyclerTop = ev.mNext;
+ gRecyclerUsed -= 1;
+ }
+ ev.mNext = null;
+ ev.prepareForReuse();
+ return ev;
+ }
+
+ /**
+ * Obtains a (potentially recycled) key event.
+ *
+ * @hide
+ */
+ public static KeyEvent obtain(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags, int source, String characters) {
+ KeyEvent ev = obtain();
+ ev.mDownTime = downTime;
+ ev.mEventTime = eventTime;
+ ev.mAction = action;
+ ev.mKeyCode = code;
+ ev.mRepeatCount = repeat;
+ ev.mMetaState = metaState;
+ ev.mDeviceId = deviceId;
+ ev.mScanCode = scancode;
+ ev.mFlags = flags;
+ ev.mSource = source;
+ ev.mCharacters = characters;
+ return ev;
+ }
+
+ /**
+ * Obtains a (potentially recycled) copy of another key event.
+ *
+ * @hide
+ */
+ public static KeyEvent obtain(KeyEvent other) {
+ KeyEvent ev = obtain();
+ ev.mDownTime = other.mDownTime;
+ ev.mEventTime = other.mEventTime;
+ ev.mAction = other.mAction;
+ ev.mKeyCode = other.mKeyCode;
+ ev.mRepeatCount = other.mRepeatCount;
+ ev.mMetaState = other.mMetaState;
+ ev.mDeviceId = other.mDeviceId;
+ ev.mScanCode = other.mScanCode;
+ ev.mFlags = other.mFlags;
+ ev.mSource = other.mSource;
+ ev.mCharacters = other.mCharacters;
+ return ev;
+ }
+
+ /** @hide */
+ @Override
+ public KeyEvent copy() {
+ return obtain(this);
+ }
+
+ /**
+ * Recycles a key event.
+ * Key events should only be recycled if they are owned by the system since user
+ * code expects them to be essentially immutable, "tracking" notwithstanding.
+ *
+ * @hide
+ */
+ @Override
+ public final void recycle() {
+ super.recycle();
+ mCharacters = null;
+
+ synchronized (gRecyclerLock) {
+ if (gRecyclerUsed < MAX_RECYCLED) {
+ gRecyclerUsed++;
+ mNext = gRecyclerTop;
+ gRecyclerTop = this;
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public final void recycleIfNeededAfterDispatch() {
+ // Do nothing.
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * event time and repeat count are replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param eventTime The new event time
+ * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+ * @param newRepeat The new repeat count of the event.
+ */
+ public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
+ int newRepeat) {
+ return new KeyEvent(event, eventTime, newRepeat);
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * event time and repeat count are replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param eventTime The new event time
+ * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+ * @param newRepeat The new repeat count of the event.
+ * @param newFlags New flags for the event, replacing the entire value
+ * in the original event.
+ */
+ public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
+ int newRepeat, int newFlags) {
+ KeyEvent ret = new KeyEvent(event);
+ ret.mEventTime = eventTime;
+ ret.mRepeatCount = newRepeat;
+ ret.mFlags = newFlags;
+ return ret;
+ }
+
+ /**
+ * Copy an existing key event, modifying its action.
+ *
+ * @param origEvent The existing event to be copied.
+ * @param action The new action code of the event.
+ */
+ private KeyEvent(KeyEvent origEvent, int action) {
+ mDownTime = origEvent.mDownTime;
+ mEventTime = origEvent.mEventTime;
+ mAction = action;
+ mKeyCode = origEvent.mKeyCode;
+ mRepeatCount = origEvent.mRepeatCount;
+ mMetaState = origEvent.mMetaState;
+ mDeviceId = origEvent.mDeviceId;
+ mSource = origEvent.mSource;
+ mScanCode = origEvent.mScanCode;
+ mFlags = origEvent.mFlags;
+ // Don't copy mCharacters, since one way or the other we'll lose it
+ // when changing the action.
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * action is replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param action The new action code of the event.
+ */
+ public static KeyEvent changeAction(KeyEvent event, int action) {
+ return new KeyEvent(event, action);
+ }
+
+ /**
+ * Create a new key event that is the same as the given one, but whose
+ * flags are replaced with the given value.
+ *
+ * @param event The existing event to be copied. This is not modified.
+ * @param flags The new flags constant.
+ */
+ public static KeyEvent changeFlags(KeyEvent event, int flags) {
+ event = new KeyEvent(event);
+ event.mFlags = flags;
+ return event;
+ }
+
+ /** @hide */
+ @Override
+ public final boolean isTainted() {
+ return (mFlags & FLAG_TAINTED) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public final void setTainted(boolean tainted) {
+ mFlags = tainted ? mFlags | FLAG_TAINTED : mFlags & ~FLAG_TAINTED;
+ }
+
+ /**
+ * Don't use in new code, instead explicitly check
+ * {@link #getAction()}.
+ *
+ * @return If the action is ACTION_DOWN, returns true; else false.
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated public final boolean isDown() {
+ return mAction == ACTION_DOWN;
+ }
+
+ /** Is this a system key? System keys can not be used for menu shortcuts.
+ */
+ public final boolean isSystem() {
+ return isSystemKey(mKeyCode);
+ }
+
+ /** @hide */
+ public final boolean isWakeKey() {
+ return isWakeKey(mKeyCode);
+ }
+
+ /**
+ * Returns true if the specified keycode is a gamepad button.
+ * @return True if the keycode is a gamepad button, such as {@link #KEYCODE_BUTTON_A}.
+ */
+ public static final boolean isGamepadButton(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BUTTON_A:
+ case KeyEvent.KEYCODE_BUTTON_B:
+ case KeyEvent.KEYCODE_BUTTON_C:
+ case KeyEvent.KEYCODE_BUTTON_X:
+ case KeyEvent.KEYCODE_BUTTON_Y:
+ case KeyEvent.KEYCODE_BUTTON_Z:
+ case KeyEvent.KEYCODE_BUTTON_L1:
+ case KeyEvent.KEYCODE_BUTTON_R1:
+ case KeyEvent.KEYCODE_BUTTON_L2:
+ case KeyEvent.KEYCODE_BUTTON_R2:
+ case KeyEvent.KEYCODE_BUTTON_THUMBL:
+ case KeyEvent.KEYCODE_BUTTON_THUMBR:
+ case KeyEvent.KEYCODE_BUTTON_START:
+ case KeyEvent.KEYCODE_BUTTON_SELECT:
+ case KeyEvent.KEYCODE_BUTTON_MODE:
+ case KeyEvent.KEYCODE_BUTTON_1:
+ case KeyEvent.KEYCODE_BUTTON_2:
+ case KeyEvent.KEYCODE_BUTTON_3:
+ case KeyEvent.KEYCODE_BUTTON_4:
+ case KeyEvent.KEYCODE_BUTTON_5:
+ case KeyEvent.KEYCODE_BUTTON_6:
+ case KeyEvent.KEYCODE_BUTTON_7:
+ case KeyEvent.KEYCODE_BUTTON_8:
+ case KeyEvent.KEYCODE_BUTTON_9:
+ case KeyEvent.KEYCODE_BUTTON_10:
+ case KeyEvent.KEYCODE_BUTTON_11:
+ case KeyEvent.KEYCODE_BUTTON_12:
+ case KeyEvent.KEYCODE_BUTTON_13:
+ case KeyEvent.KEYCODE_BUTTON_14:
+ case KeyEvent.KEYCODE_BUTTON_15:
+ case KeyEvent.KEYCODE_BUTTON_16:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** Whether key will, by default, trigger a click on the focused view.
+ * @hide
+ */
+ public static final boolean isConfirmKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_NUMPAD_ENTER:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Whether this key is a media key, which can be send to apps that are
+ * interested in media key events.
+ *
+ * @hide
+ */
+ public static final boolean isMediaKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ return true;
+ }
+ return false;
+ }
+
+
+ /** Is this a system key? System keys can not be used for menu shortcuts.
+ * @hide
+ */
+ public static final boolean isSystemKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ case KeyEvent.KEYCODE_SOFT_RIGHT:
+ case KeyEvent.KEYCODE_HOME:
+ case KeyEvent.KEYCODE_BACK:
+ case KeyEvent.KEYCODE_CALL:
+ case KeyEvent.KEYCODE_ENDCALL:
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_POWER:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_CAMERA:
+ case KeyEvent.KEYCODE_FOCUS:
+ case KeyEvent.KEYCODE_SEARCH:
+ case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
+ case KeyEvent.KEYCODE_BRIGHTNESS_UP:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
+ case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT:
+ return true;
+ }
+
+ return false;
+ }
+
+ /** @hide */
+ public static final boolean isWakeKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ case KeyEvent.KEYCODE_MENU:
+ case KeyEvent.KEYCODE_WAKEUP:
+ case KeyEvent.KEYCODE_PAIRING:
+ case KeyEvent.KEYCODE_STEM_1:
+ case KeyEvent.KEYCODE_STEM_2:
+ case KeyEvent.KEYCODE_STEM_3:
+ return true;
+ }
+ return false;
+ }
+
+ /** @hide */
+ public static final boolean isMetaKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_META_LEFT || keyCode == KeyEvent.KEYCODE_META_RIGHT;
+ }
+
+ /** @hide */
+ public static final boolean isAltKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final int getDeviceId() {
+ return mDeviceId;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final int getSource() {
+ return mSource;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final void setSource(int source) {
+ mSource = source;
+ }
+
+ /**
+ * <p>Returns the state of the meta keys.</p>
+ *
+ * @return an integer in which each bit set to 1 represents a pressed
+ * meta key
+ *
+ * @see #isAltPressed()
+ * @see #isShiftPressed()
+ * @see #isSymPressed()
+ * @see #isCtrlPressed()
+ * @see #isMetaPressed()
+ * @see #isFunctionPressed()
+ * @see #isCapsLockOn()
+ * @see #isNumLockOn()
+ * @see #isScrollLockOn()
+ * @see #META_ALT_ON
+ * @see #META_ALT_LEFT_ON
+ * @see #META_ALT_RIGHT_ON
+ * @see #META_SHIFT_ON
+ * @see #META_SHIFT_LEFT_ON
+ * @see #META_SHIFT_RIGHT_ON
+ * @see #META_SYM_ON
+ * @see #META_FUNCTION_ON
+ * @see #META_CTRL_ON
+ * @see #META_CTRL_LEFT_ON
+ * @see #META_CTRL_RIGHT_ON
+ * @see #META_META_ON
+ * @see #META_META_LEFT_ON
+ * @see #META_META_RIGHT_ON
+ * @see #META_CAPS_LOCK_ON
+ * @see #META_NUM_LOCK_ON
+ * @see #META_SCROLL_LOCK_ON
+ * @see #getModifiers
+ */
+ public final int getMetaState() {
+ return mMetaState;
+ }
+
+ /**
+ * Returns the state of the modifier keys.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function specifically masks out
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * The value returned consists of the meta state (from {@link #getMetaState})
+ * normalized using {@link #normalizeMetaState(int)} and then masked with
+ * {@link #getModifierMetaStateMask} so that only valid modifier bits are retained.
+ * </p>
+ *
+ * @return An integer in which each bit set to 1 represents a pressed modifier key.
+ * @see #getMetaState
+ */
+ public final int getModifiers() {
+ return normalizeMetaState(mMetaState) & META_MODIFIER_MASK;
+ }
+
+ /**
+ * Returns the flags for this key event.
+ *
+ * @see #FLAG_WOKE_HERE
+ */
+ public final int getFlags() {
+ return mFlags;
+ }
+
+ // Mask of all modifier key meta states. Specifically excludes locked keys like caps lock.
+ private static final int META_MODIFIER_MASK =
+ META_SHIFT_ON | META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON
+ | META_ALT_ON | META_ALT_LEFT_ON | META_ALT_RIGHT_ON
+ | META_CTRL_ON | META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON
+ | META_META_ON | META_META_LEFT_ON | META_META_RIGHT_ON
+ | META_SYM_ON | META_FUNCTION_ON;
+
+ // Mask of all lock key meta states.
+ private static final int META_LOCK_MASK =
+ META_CAPS_LOCK_ON | META_NUM_LOCK_ON | META_SCROLL_LOCK_ON;
+
+ // Mask of all valid meta states.
+ private static final int META_ALL_MASK = META_MODIFIER_MASK | META_LOCK_MASK;
+
+ // Mask of all synthetic meta states that are reserved for API compatibility with
+ // historical uses in MetaKeyKeyListener.
+ private static final int META_SYNTHETIC_MASK =
+ META_CAP_LOCKED | META_ALT_LOCKED | META_SYM_LOCKED | META_SELECTING;
+
+ // Mask of all meta states that are not valid use in specifying a modifier key.
+ // These bits are known to be used for purposes other than specifying modifiers.
+ private static final int META_INVALID_MODIFIER_MASK =
+ META_LOCK_MASK | META_SYNTHETIC_MASK;
+
+ /**
+ * Gets a mask that includes all valid modifier key meta state bits.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, the mask specifically excludes
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p>
+ *
+ * @return The modifier meta state mask which is a combination of
+ * {@link #META_SHIFT_ON}, {@link #META_SHIFT_LEFT_ON}, {@link #META_SHIFT_RIGHT_ON},
+ * {@link #META_ALT_ON}, {@link #META_ALT_LEFT_ON}, {@link #META_ALT_RIGHT_ON},
+ * {@link #META_CTRL_ON}, {@link #META_CTRL_LEFT_ON}, {@link #META_CTRL_RIGHT_ON},
+ * {@link #META_META_ON}, {@link #META_META_LEFT_ON}, {@link #META_META_RIGHT_ON},
+ * {@link #META_SYM_ON}, {@link #META_FUNCTION_ON}.
+ */
+ public static int getModifierMetaStateMask() {
+ return META_MODIFIER_MASK;
+ }
+
+ /**
+ * Returns true if this key code is a modifier key.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function return false
+ * for those keys.
+ * </p>
+ *
+ * @return True if the key code is one of
+ * {@link #KEYCODE_SHIFT_LEFT} {@link #KEYCODE_SHIFT_RIGHT},
+ * {@link #KEYCODE_ALT_LEFT}, {@link #KEYCODE_ALT_RIGHT},
+ * {@link #KEYCODE_CTRL_LEFT}, {@link #KEYCODE_CTRL_RIGHT},
+ * {@link #KEYCODE_META_LEFT}, or {@link #KEYCODE_META_RIGHT},
+ * {@link #KEYCODE_SYM}, {@link #KEYCODE_NUM}, {@link #KEYCODE_FUNCTION}.
+ */
+ public static boolean isModifierKey(int keyCode) {
+ switch (keyCode) {
+ case KEYCODE_SHIFT_LEFT:
+ case KEYCODE_SHIFT_RIGHT:
+ case KEYCODE_ALT_LEFT:
+ case KEYCODE_ALT_RIGHT:
+ case KEYCODE_CTRL_LEFT:
+ case KEYCODE_CTRL_RIGHT:
+ case KEYCODE_META_LEFT:
+ case KEYCODE_META_RIGHT:
+ case KEYCODE_SYM:
+ case KEYCODE_NUM:
+ case KEYCODE_FUNCTION:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Normalizes the specified meta state.
+ * <p>
+ * The meta state is normalized such that if either the left or right modifier meta state
+ * bits are set then the result will also include the universal bit for that modifier.
+ * </p><p>
+ * If the specified meta state contains {@link #META_ALT_LEFT_ON} then
+ * the result will also contain {@link #META_ALT_ON} in addition to {@link #META_ALT_LEFT_ON}
+ * and the other bits that were specified in the input. The same is process is
+ * performed for shift, control and meta.
+ * </p><p>
+ * If the specified meta state contains synthetic meta states defined by
+ * {@link MetaKeyKeyListener}, then those states are translated here and the original
+ * synthetic meta states are removed from the result.
+ * {@link MetaKeyKeyListener#META_CAP_LOCKED} is translated to {@link #META_CAPS_LOCK_ON}.
+ * {@link MetaKeyKeyListener#META_ALT_LOCKED} is translated to {@link #META_ALT_ON}.
+ * {@link MetaKeyKeyListener#META_SYM_LOCKED} is translated to {@link #META_SYM_ON}.
+ * </p><p>
+ * Undefined meta state bits are removed.
+ * </p>
+ *
+ * @param metaState The meta state.
+ * @return The normalized meta state.
+ */
+ public static int normalizeMetaState(int metaState) {
+ if ((metaState & (META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON)) != 0) {
+ metaState |= META_SHIFT_ON;
+ }
+ if ((metaState & (META_ALT_LEFT_ON | META_ALT_RIGHT_ON)) != 0) {
+ metaState |= META_ALT_ON;
+ }
+ if ((metaState & (META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON)) != 0) {
+ metaState |= META_CTRL_ON;
+ }
+ if ((metaState & (META_META_LEFT_ON | META_META_RIGHT_ON)) != 0) {
+ metaState |= META_META_ON;
+ }
+ if ((metaState & MetaKeyKeyListener.META_CAP_LOCKED) != 0) {
+ metaState |= META_CAPS_LOCK_ON;
+ }
+ if ((metaState & MetaKeyKeyListener.META_ALT_LOCKED) != 0) {
+ metaState |= META_ALT_ON;
+ }
+ if ((metaState & MetaKeyKeyListener.META_SYM_LOCKED) != 0) {
+ metaState |= META_SYM_ON;
+ }
+ return metaState & META_ALL_MASK;
+ }
+
+ /**
+ * Returns true if no modifiers keys are pressed according to the specified meta state.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * The meta state is normalized prior to comparison using {@link #normalizeMetaState(int)}.
+ * </p>
+ *
+ * @param metaState The meta state to consider.
+ * @return True if no modifier keys are pressed.
+ * @see #hasNoModifiers()
+ */
+ public static boolean metaStateHasNoModifiers(int metaState) {
+ return (normalizeMetaState(metaState) & META_MODIFIER_MASK) == 0;
+ }
+
+ /**
+ * Returns true if only the specified modifier keys are pressed according to
+ * the specified meta state. Returns false if a different combination of modifier
+ * keys are pressed.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * If the specified modifier mask includes directional modifiers, such as
+ * {@link #META_SHIFT_LEFT_ON}, then this method ensures that the
+ * modifier is pressed on that side.
+ * If the specified modifier mask includes non-directional modifiers, such as
+ * {@link #META_SHIFT_ON}, then this method ensures that the modifier
+ * is pressed on either side.
+ * If the specified modifier mask includes both directional and non-directional modifiers
+ * for the same type of key, such as {@link #META_SHIFT_ON} and {@link #META_SHIFT_LEFT_ON},
+ * then this method throws an illegal argument exception.
+ * </p>
+ *
+ * @param metaState The meta state to consider.
+ * @param modifiers The meta state of the modifier keys to check. May be a combination
+ * of modifier meta states as defined by {@link #getModifierMetaStateMask()}. May be 0 to
+ * ensure that no modifier keys are pressed.
+ * @return True if only the specified modifier keys are pressed.
+ * @throws IllegalArgumentException if the modifiers parameter contains invalid modifiers
+ * @see #hasModifiers
+ */
+ public static boolean metaStateHasModifiers(int metaState, int modifiers) {
+ // Note: For forward compatibility, we allow the parameter to contain meta states
+ // that we do not recognize but we explicitly disallow meta states that
+ // are not valid modifiers.
+ if ((modifiers & META_INVALID_MODIFIER_MASK) != 0) {
+ throw new IllegalArgumentException("modifiers must not contain "
+ + "META_CAPS_LOCK_ON, META_NUM_LOCK_ON, META_SCROLL_LOCK_ON, "
+ + "META_CAP_LOCKED, META_ALT_LOCKED, META_SYM_LOCKED, "
+ + "or META_SELECTING");
+ }
+
+ metaState = normalizeMetaState(metaState) & META_MODIFIER_MASK;
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_SHIFT_ON, META_SHIFT_LEFT_ON, META_SHIFT_RIGHT_ON);
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_ALT_ON, META_ALT_LEFT_ON, META_ALT_RIGHT_ON);
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_CTRL_ON, META_CTRL_LEFT_ON, META_CTRL_RIGHT_ON);
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ META_META_ON, META_META_LEFT_ON, META_META_RIGHT_ON);
+ return metaState == modifiers;
+ }
+
+ private static int metaStateFilterDirectionalModifiers(int metaState,
+ int modifiers, int basic, int left, int right) {
+ final boolean wantBasic = (modifiers & basic) != 0;
+ final int directional = left | right;
+ final boolean wantLeftOrRight = (modifiers & directional) != 0;
+
+ if (wantBasic) {
+ if (wantLeftOrRight) {
+ throw new IllegalArgumentException("modifiers must not contain "
+ + metaStateToString(basic) + " combined with "
+ + metaStateToString(left) + " or " + metaStateToString(right));
+ }
+ return metaState & ~directional;
+ } else if (wantLeftOrRight) {
+ return metaState & ~basic;
+ } else {
+ return metaState;
+ }
+ }
+
+ /**
+ * Returns true if no modifier keys are pressed.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * The meta state is normalized prior to comparison using {@link #normalizeMetaState(int)}.
+ * </p>
+ *
+ * @return True if no modifier keys are pressed.
+ * @see #metaStateHasNoModifiers
+ */
+ public final boolean hasNoModifiers() {
+ return metaStateHasNoModifiers(mMetaState);
+ }
+
+ /**
+ * Returns true if only the specified modifiers keys are pressed.
+ * Returns false if a different combination of modifier keys are pressed.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function ignores
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * If the specified modifier mask includes directional modifiers, such as
+ * {@link #META_SHIFT_LEFT_ON}, then this method ensures that the
+ * modifier is pressed on that side.
+ * If the specified modifier mask includes non-directional modifiers, such as
+ * {@link #META_SHIFT_ON}, then this method ensures that the modifier
+ * is pressed on either side.
+ * If the specified modifier mask includes both directional and non-directional modifiers
+ * for the same type of key, such as {@link #META_SHIFT_ON} and {@link #META_SHIFT_LEFT_ON},
+ * then this method throws an illegal argument exception.
+ * </p>
+ *
+ * @param modifiers The meta state of the modifier keys to check. May be a combination
+ * of modifier meta states as defined by {@link #getModifierMetaStateMask()}. May be 0 to
+ * ensure that no modifier keys are pressed.
+ * @return True if only the specified modifier keys are pressed.
+ * @throws IllegalArgumentException if the modifiers parameter contains invalid modifiers
+ * @see #metaStateHasModifiers
+ */
+ public final boolean hasModifiers(int modifiers) {
+ return metaStateHasModifiers(mMetaState, modifiers);
+ }
+
+ /**
+ * <p>Returns the pressed state of the ALT meta key.</p>
+ *
+ * @return true if the ALT key is pressed, false otherwise
+ *
+ * @see #KEYCODE_ALT_LEFT
+ * @see #KEYCODE_ALT_RIGHT
+ * @see #META_ALT_ON
+ */
+ public final boolean isAltPressed() {
+ return (mMetaState & META_ALT_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the SHIFT meta key.</p>
+ *
+ * @return true if the SHIFT key is pressed, false otherwise
+ *
+ * @see #KEYCODE_SHIFT_LEFT
+ * @see #KEYCODE_SHIFT_RIGHT
+ * @see #META_SHIFT_ON
+ */
+ public final boolean isShiftPressed() {
+ return (mMetaState & META_SHIFT_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the SYM meta key.</p>
+ *
+ * @return true if the SYM key is pressed, false otherwise
+ *
+ * @see #KEYCODE_SYM
+ * @see #META_SYM_ON
+ */
+ public final boolean isSymPressed() {
+ return (mMetaState & META_SYM_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the CTRL meta key.</p>
+ *
+ * @return true if the CTRL key is pressed, false otherwise
+ *
+ * @see #KEYCODE_CTRL_LEFT
+ * @see #KEYCODE_CTRL_RIGHT
+ * @see #META_CTRL_ON
+ */
+ public final boolean isCtrlPressed() {
+ return (mMetaState & META_CTRL_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the META meta key.</p>
+ *
+ * @return true if the META key is pressed, false otherwise
+ *
+ * @see #KEYCODE_META_LEFT
+ * @see #KEYCODE_META_RIGHT
+ * @see #META_META_ON
+ */
+ public final boolean isMetaPressed() {
+ return (mMetaState & META_META_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the pressed state of the FUNCTION meta key.</p>
+ *
+ * @return true if the FUNCTION key is pressed, false otherwise
+ *
+ * @see #KEYCODE_FUNCTION
+ * @see #META_FUNCTION_ON
+ */
+ public final boolean isFunctionPressed() {
+ return (mMetaState & META_FUNCTION_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the locked state of the CAPS LOCK meta key.</p>
+ *
+ * @return true if the CAPS LOCK key is on, false otherwise
+ *
+ * @see #KEYCODE_CAPS_LOCK
+ * @see #META_CAPS_LOCK_ON
+ */
+ public final boolean isCapsLockOn() {
+ return (mMetaState & META_CAPS_LOCK_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the locked state of the NUM LOCK meta key.</p>
+ *
+ * @return true if the NUM LOCK key is on, false otherwise
+ *
+ * @see #KEYCODE_NUM_LOCK
+ * @see #META_NUM_LOCK_ON
+ */
+ public final boolean isNumLockOn() {
+ return (mMetaState & META_NUM_LOCK_ON) != 0;
+ }
+
+ /**
+ * <p>Returns the locked state of the SCROLL LOCK meta key.</p>
+ *
+ * @return true if the SCROLL LOCK key is on, false otherwise
+ *
+ * @see #KEYCODE_SCROLL_LOCK
+ * @see #META_SCROLL_LOCK_ON
+ */
+ public final boolean isScrollLockOn() {
+ return (mMetaState & META_SCROLL_LOCK_ON) != 0;
+ }
+
+ /**
+ * Retrieve the action of this key event. May be either
+ * {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ *
+ * @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE.
+ */
+ public final int getAction() {
+ return mAction;
+ }
+
+ /**
+ * For {@link #ACTION_UP} events, indicates that the event has been
+ * canceled as per {@link #FLAG_CANCELED}.
+ */
+ public final boolean isCanceled() {
+ return (mFlags&FLAG_CANCELED) != 0;
+ }
+
+ /**
+ * Set {@link #FLAG_CANCELED} flag for the key event.
+ *
+ * @hide
+ */
+ @Override
+ public final void cancel() {
+ mFlags |= FLAG_CANCELED;
+ }
+
+ /**
+ * Call this during {@link Callback#onKeyDown} to have the system track
+ * the key through its final up (possibly including a long press). Note
+ * that only one key can be tracked at a time -- if another key down
+ * event is received while a previous one is being tracked, tracking is
+ * stopped on the previous event.
+ */
+ public final void startTracking() {
+ mFlags |= FLAG_START_TRACKING;
+ }
+
+ /**
+ * For {@link #ACTION_UP} events, indicates that the event is still being
+ * tracked from its initial down event as per
+ * {@link #FLAG_TRACKING}.
+ */
+ public final boolean isTracking() {
+ return (mFlags&FLAG_TRACKING) != 0;
+ }
+
+ /**
+ * For {@link #ACTION_DOWN} events, indicates that the event has been
+ * canceled as per {@link #FLAG_LONG_PRESS}.
+ */
+ public final boolean isLongPress() {
+ return (mFlags&FLAG_LONG_PRESS) != 0;
+ }
+
+ /**
+ * Retrieve the key code of the key event. This is the physical key that
+ * was pressed, <em>not</em> the Unicode character.
+ *
+ * @return The key code of the event.
+ */
+ public final int getKeyCode() {
+ return mKeyCode;
+ }
+
+ /**
+ * For the special case of a {@link #ACTION_MULTIPLE} event with key
+ * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters
+ * associated with the event. In all other cases it is null.
+ *
+ * @return Returns a String of 1 or more characters associated with
+ * the event.
+ */
+ public final String getCharacters() {
+ return mCharacters;
+ }
+
+ /**
+ * Retrieve the hardware key id of this key event. These values are not
+ * reliable and vary from device to device.
+ *
+ * {@more}
+ * Mostly this is here for debugging purposes.
+ */
+ public final int getScanCode() {
+ return mScanCode;
+ }
+
+ /**
+ * Retrieve the repeat count of the event. For both key up and key down
+ * events, this is the number of times the key has repeated with the first
+ * down starting at 0 and counting up from there. For multiple key
+ * events, this is the number of down/up pairs that have occurred.
+ *
+ * @return The number of times the key has repeated.
+ */
+ public final int getRepeatCount() {
+ return mRepeatCount;
+ }
+
+ /**
+ * Retrieve the time of the most recent key down event,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base. If this
+ * is a down event, this will be the same as {@link #getEventTime()}.
+ * Note that when chording keys, this value is the down time of the
+ * most recently pressed key, which may <em>not</em> be the same physical
+ * key of this event.
+ *
+ * @return Returns the most recent key down time, in the
+ * {@link android.os.SystemClock#uptimeMillis} time base
+ */
+ public final long getDownTime() {
+ return mDownTime;
+ }
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ */
+ @Override
+ public final long getEventTime() {
+ return mEventTime;
+ }
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond (instead of millisecond) precision.
+ * <p>
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ * </p>
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond (instead of millisecond) precision.
+ *
+ * @hide
+ */
+ @Override
+ public final long getEventTimeNano() {
+ return mEventTime * 1000000L;
+ }
+
+ /**
+ * Renamed to {@link #getDeviceId}.
+ *
+ * @hide
+ * @deprecated use {@link #getDeviceId()} instead.
+ */
+ @Deprecated
+ public final int getKeyboardDevice() {
+ return mDeviceId;
+ }
+
+ /**
+ * Gets the {@link KeyCharacterMap} associated with the keyboard device.
+ *
+ * @return The associated key character map.
+ * @throws {@link KeyCharacterMap.UnavailableException} if the key character map
+ * could not be loaded because it was malformed or the default key character map
+ * is missing from the system.
+ *
+ * @see KeyCharacterMap#load
+ */
+ public final KeyCharacterMap getKeyCharacterMap() {
+ return KeyCharacterMap.load(mDeviceId);
+ }
+
+ /**
+ * Gets the primary character for this key.
+ * In other words, the label that is physically printed on it.
+ *
+ * @return The display label character, or 0 if none (eg. for non-printing keys).
+ */
+ public char getDisplayLabel() {
+ return getKeyCharacterMap().getDisplayLabel(mKeyCode);
+ }
+
+ /**
+ * Gets the Unicode character generated by the specified key and meta
+ * key state combination.
+ * <p>
+ * Returns the Unicode character that the specified key would produce
+ * when the specified meta bits (see {@link MetaKeyKeyListener})
+ * were active.
+ * </p><p>
+ * Returns 0 if the key is not one that is used to type Unicode
+ * characters.
+ * </p><p>
+ * If the return value has bit {@link KeyCharacterMap#COMBINING_ACCENT} set, the
+ * key is a "dead key" that should be combined with another to
+ * actually produce a character -- see {@link KeyCharacterMap#getDeadChar} --
+ * after masking with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+ * </p>
+ *
+ * @return The associated character or combining accent, or 0 if none.
+ */
+ public int getUnicodeChar() {
+ return getUnicodeChar(mMetaState);
+ }
+
+ /**
+ * Gets the Unicode character generated by the specified key and meta
+ * key state combination.
+ * <p>
+ * Returns the Unicode character that the specified key would produce
+ * when the specified meta bits (see {@link MetaKeyKeyListener})
+ * were active.
+ * </p><p>
+ * Returns 0 if the key is not one that is used to type Unicode
+ * characters.
+ * </p><p>
+ * If the return value has bit {@link KeyCharacterMap#COMBINING_ACCENT} set, the
+ * key is a "dead key" that should be combined with another to
+ * actually produce a character -- see {@link KeyCharacterMap#getDeadChar} --
+ * after masking with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
+ * </p>
+ *
+ * @param metaState The meta key modifier state.
+ * @return The associated character or combining accent, or 0 if none.
+ */
+ public int getUnicodeChar(int metaState) {
+ return getKeyCharacterMap().get(mKeyCode, metaState);
+ }
+
+ /**
+ * Get the character conversion data for a given key code.
+ *
+ * @param results A {@link KeyCharacterMap.KeyData} instance that will be
+ * filled with the results.
+ * @return True if the key was mapped. If the key was not mapped, results is not modified.
+ *
+ * @deprecated instead use {@link #getDisplayLabel()},
+ * {@link #getNumber()} or {@link #getUnicodeChar(int)}.
+ */
+ @Deprecated
+ public boolean getKeyData(KeyData results) {
+ return getKeyCharacterMap().getKeyData(mKeyCode, results);
+ }
+
+ /**
+ * Gets the first character in the character array that can be generated
+ * by the specified key code.
+ * <p>
+ * This is a convenience function that returns the same value as
+ * {@link #getMatch(char[],int) getMatch(chars, 0)}.
+ * </p>
+ *
+ * @param chars The array of matching characters to consider.
+ * @return The matching associated character, or 0 if none.
+ */
+ public char getMatch(char[] chars) {
+ return getMatch(chars, 0);
+ }
+
+ /**
+ * Gets the first character in the character array that can be generated
+ * by the specified key code. If there are multiple choices, prefers
+ * the one that would be generated with the specified meta key modifier state.
+ *
+ * @param chars The array of matching characters to consider.
+ * @param metaState The preferred meta key modifier state.
+ * @return The matching associated character, or 0 if none.
+ */
+ public char getMatch(char[] chars, int metaState) {
+ return getKeyCharacterMap().getMatch(mKeyCode, chars, metaState);
+ }
+
+ /**
+ * Gets the number or symbol associated with the key.
+ * <p>
+ * The character value is returned, not the numeric value.
+ * If the key is not a number, but is a symbol, the symbol is retuned.
+ * </p><p>
+ * This method is intended to to support dial pads and other numeric or
+ * symbolic entry on keyboards where certain keys serve dual function
+ * as alphabetic and symbolic keys. This method returns the number
+ * or symbol associated with the key independent of whether the user
+ * has pressed the required modifier.
+ * </p><p>
+ * For example, on one particular keyboard the keys on the top QWERTY row generate
+ * numbers when ALT is pressed such that ALT-Q maps to '1'. So for that keyboard
+ * when {@link #getNumber} is called with {@link KeyEvent#KEYCODE_Q} it returns '1'
+ * so that the user can type numbers without pressing ALT when it makes sense.
+ * </p>
+ *
+ * @return The associated numeric or symbolic character, or 0 if none.
+ */
+ public char getNumber() {
+ return getKeyCharacterMap().getNumber(mKeyCode);
+ }
+
+ /**
+ * Returns true if this key produces a glyph.
+ *
+ * @return True if the key is a printing key.
+ */
+ public boolean isPrintingKey() {
+ return getKeyCharacterMap().isPrintingKey(mKeyCode);
+ }
+
+ /**
+ * @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead.
+ */
+ @Deprecated
+ public final boolean dispatch(Callback receiver) {
+ return dispatch(receiver, null, null);
+ }
+
+ /**
+ * Deliver this key event to a {@link Callback} interface. If this is
+ * an ACTION_MULTIPLE event and it is not handled, then an attempt will
+ * be made to deliver a single normal event.
+ *
+ * @param receiver The Callback that will be given the event.
+ * @param state State information retained across events.
+ * @param target The target of the dispatch, for use in tracking.
+ *
+ * @return The return value from the Callback method that was called.
+ */
+ public final boolean dispatch(Callback receiver, DispatcherState state,
+ Object target) {
+ switch (mAction) {
+ case ACTION_DOWN: {
+ mFlags &= ~FLAG_START_TRACKING;
+ if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ + ": " + this);
+ boolean res = receiver.onKeyDown(mKeyCode, this);
+ if (state != null) {
+ if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
+ if (DEBUG) Log.v(TAG, " Start tracking!");
+ state.startTracking(this, target);
+ } else if (isLongPress() && state.isTracking(this)) {
+ try {
+ if (receiver.onKeyLongPress(mKeyCode, this)) {
+ if (DEBUG) Log.v(TAG, " Clear from long press!");
+ state.performedLongPress(this);
+ res = true;
+ }
+ } catch (AbstractMethodError e) {
+ }
+ }
+ }
+ return res;
+ }
+ case ACTION_UP:
+ if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+ + ": " + this);
+ if (state != null) {
+ state.handleUpEvent(this);
+ }
+ return receiver.onKeyUp(mKeyCode, this);
+ case ACTION_MULTIPLE:
+ final int count = mRepeatCount;
+ final int code = mKeyCode;
+ if (receiver.onKeyMultiple(code, count, this)) {
+ return true;
+ }
+ if (code != KeyEvent.KEYCODE_UNKNOWN) {
+ mAction = ACTION_DOWN;
+ mRepeatCount = 0;
+ boolean handled = receiver.onKeyDown(code, this);
+ if (handled) {
+ mAction = ACTION_UP;
+ receiver.onKeyUp(code, this);
+ }
+ mAction = ACTION_MULTIPLE;
+ mRepeatCount = count;
+ return handled;
+ }
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * Use with {@link KeyEvent#dispatch(Callback, DispatcherState, Object)}
+ * for more advanced key dispatching, such as long presses.
+ */
+ public static class DispatcherState {
+ int mDownKeyCode;
+ Object mDownTarget;
+ SparseIntArray mActiveLongPresses = new SparseIntArray();
+
+ /**
+ * Reset back to initial state.
+ */
+ public void reset() {
+ if (DEBUG) Log.v(TAG, "Reset: " + this);
+ mDownKeyCode = 0;
+ mDownTarget = null;
+ mActiveLongPresses.clear();
+ }
+
+ /**
+ * Stop any tracking associated with this target.
+ */
+ public void reset(Object target) {
+ if (mDownTarget == target) {
+ if (DEBUG) Log.v(TAG, "Reset in " + target + ": " + this);
+ mDownKeyCode = 0;
+ mDownTarget = null;
+ }
+ }
+
+ /**
+ * Start tracking the key code associated with the given event. This
+ * can only be called on a key down. It will allow you to see any
+ * long press associated with the key, and will result in
+ * {@link KeyEvent#isTracking} return true on the long press and up
+ * events.
+ *
+ * <p>This is only needed if you are directly dispatching events, rather
+ * than handling them in {@link Callback#onKeyDown}.
+ */
+ public void startTracking(KeyEvent event, Object target) {
+ if (event.getAction() != ACTION_DOWN) {
+ throw new IllegalArgumentException(
+ "Can only start tracking on a down event");
+ }
+ if (DEBUG) Log.v(TAG, "Start trackingt in " + target + ": " + this);
+ mDownKeyCode = event.getKeyCode();
+ mDownTarget = target;
+ }
+
+ /**
+ * Return true if the key event is for a key code that is currently
+ * being tracked by the dispatcher.
+ */
+ public boolean isTracking(KeyEvent event) {
+ return mDownKeyCode == event.getKeyCode();
+ }
+
+ /**
+ * Keep track of the given event's key code as having performed an
+ * action with a long press, so no action should occur on the up.
+ * <p>This is only needed if you are directly dispatching events, rather
+ * than handling them in {@link Callback#onKeyLongPress}.
+ */
+ public void performedLongPress(KeyEvent event) {
+ mActiveLongPresses.put(event.getKeyCode(), 1);
+ }
+
+ /**
+ * Handle key up event to stop tracking. This resets the dispatcher state,
+ * and updates the key event state based on it.
+ * <p>This is only needed if you are directly dispatching events, rather
+ * than handling them in {@link Callback#onKeyUp}.
+ */
+ public void handleUpEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (DEBUG) Log.v(TAG, "Handle key up " + event + ": " + this);
+ int index = mActiveLongPresses.indexOfKey(keyCode);
+ if (index >= 0) {
+ if (DEBUG) Log.v(TAG, " Index: " + index);
+ event.mFlags |= FLAG_CANCELED | FLAG_CANCELED_LONG_PRESS;
+ mActiveLongPresses.removeAt(index);
+ }
+ if (mDownKeyCode == keyCode) {
+ if (DEBUG) Log.v(TAG, " Tracking!");
+ event.mFlags |= FLAG_TRACKING;
+ mDownKeyCode = 0;
+ mDownTarget = null;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder msg = new StringBuilder();
+ msg.append("KeyEvent { action=").append(actionToString(mAction));
+ msg.append(", keyCode=").append(keyCodeToString(mKeyCode));
+ msg.append(", scanCode=").append(mScanCode);
+ if (mCharacters != null) {
+ msg.append(", characters=\"").append(mCharacters).append("\"");
+ }
+ msg.append(", metaState=").append(metaStateToString(mMetaState));
+ msg.append(", flags=0x").append(Integer.toHexString(mFlags));
+ msg.append(", repeatCount=").append(mRepeatCount);
+ msg.append(", eventTime=").append(mEventTime);
+ msg.append(", downTime=").append(mDownTime);
+ msg.append(", deviceId=").append(mDeviceId);
+ msg.append(", source=0x").append(Integer.toHexString(mSource));
+ msg.append(" }");
+ return msg.toString();
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified action
+ * such as "ACTION_DOWN", or an equivalent numeric constant such as "35" if unknown.
+ *
+ * @param action The action.
+ * @return The symbolic name of the specified action.
+ * @hide
+ */
+ public static String actionToString(int action) {
+ switch (action) {
+ case ACTION_DOWN:
+ return "ACTION_DOWN";
+ case ACTION_UP:
+ return "ACTION_UP";
+ case ACTION_MULTIPLE:
+ return "ACTION_MULTIPLE";
+ default:
+ return Integer.toString(action);
+ }
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified keycode
+ * such as "KEYCODE_A", "KEYCODE_DPAD_UP", or an equivalent numeric constant
+ * such as "1001" if unknown.
+ *
+ * @param keyCode The key code.
+ * @return The symbolic name of the specified keycode.
+ *
+ * @see KeyCharacterMap#getDisplayLabel
+ */
+ public static String keyCodeToString(int keyCode) {
+ String symbolicName = nativeKeyCodeToString(keyCode);
+ return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(keyCode);
+ }
+
+ /**
+ * Gets a keycode by its symbolic name such as "KEYCODE_A" or an equivalent
+ * numeric constant such as "1001".
+ *
+ * @param symbolicName The symbolic name of the keycode.
+ * @return The keycode or {@link #KEYCODE_UNKNOWN} if not found.
+ * @see #keycodeToString(int)
+ */
+ public static int keyCodeFromString(String symbolicName) {
+ if (symbolicName.startsWith(LABEL_PREFIX)) {
+ symbolicName = symbolicName.substring(LABEL_PREFIX.length());
+ int keyCode = nativeKeyCodeFromString(symbolicName);
+ if (keyCode > 0) {
+ return keyCode;
+ }
+ }
+ try {
+ return Integer.parseInt(symbolicName, 10);
+ } catch (NumberFormatException ex) {
+ return KEYCODE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified combined meta
+ * key modifier state flags such as "0", "META_SHIFT_ON",
+ * "META_ALT_ON|META_SHIFT_ON" or an equivalent numeric constant such as "0x10000000"
+ * if unknown.
+ *
+ * @param metaState The meta state.
+ * @return The symbolic name of the specified combined meta state flags.
+ * @hide
+ */
+ public static String metaStateToString(int metaState) {
+ if (metaState == 0) {
+ return "0";
+ }
+ StringBuilder result = null;
+ int i = 0;
+ while (metaState != 0) {
+ final boolean isSet = (metaState & 1) != 0;
+ metaState >>>= 1; // unsigned shift!
+ if (isSet) {
+ final String name = META_SYMBOLIC_NAMES[i];
+ if (result == null) {
+ if (metaState == 0) {
+ return name;
+ }
+ result = new StringBuilder(name);
+ } else {
+ result.append('|');
+ result.append(name);
+ }
+ }
+ i += 1;
+ }
+ return result.toString();
+ }
+
+ public static final Parcelable.Creator<KeyEvent> CREATOR
+ = new Parcelable.Creator<KeyEvent>() {
+ @Override
+ public KeyEvent createFromParcel(Parcel in) {
+ in.readInt(); // skip token, we already know this is a KeyEvent
+ return KeyEvent.createFromParcelBody(in);
+ }
+
+ @Override
+ public KeyEvent[] newArray(int size) {
+ return new KeyEvent[size];
+ }
+ };
+
+ /** @hide */
+ public static KeyEvent createFromParcelBody(Parcel in) {
+ return new KeyEvent(in);
+ }
+
+ private KeyEvent(Parcel in) {
+ mDeviceId = in.readInt();
+ mSource = in.readInt();
+ mAction = in.readInt();
+ mKeyCode = in.readInt();
+ mRepeatCount = in.readInt();
+ mMetaState = in.readInt();
+ mScanCode = in.readInt();
+ mFlags = in.readInt();
+ mDownTime = in.readLong();
+ mEventTime = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_KEY_EVENT);
+
+ out.writeInt(mDeviceId);
+ out.writeInt(mSource);
+ out.writeInt(mAction);
+ out.writeInt(mKeyCode);
+ out.writeInt(mRepeatCount);
+ out.writeInt(mMetaState);
+ out.writeInt(mScanCode);
+ out.writeInt(mFlags);
+ out.writeLong(mDownTime);
+ out.writeLong(mEventTime);
+ }
+}
diff --git a/android/view/KeyboardShortcutGroup.java b/android/view/KeyboardShortcutGroup.java
new file mode 100644
index 00000000..52e9832e
--- /dev/null
+++ b/android/view/KeyboardShortcutGroup.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 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 static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A group of {@link KeyboardShortcutInfo}.
+ */
+public final class KeyboardShortcutGroup implements Parcelable {
+ private final CharSequence mLabel;
+ private final List<KeyboardShortcutInfo> mItems;
+ // The system group looks different UI wise.
+ private boolean mSystemGroup;
+
+ /**
+ * @param label The title to be used for this group, or null if there is none.
+ * @param items The set of items to be included.
+ */
+ public KeyboardShortcutGroup(@Nullable CharSequence label,
+ @NonNull List<KeyboardShortcutInfo> items) {
+ mLabel = label;
+ mItems = new ArrayList<>(checkNotNull(items));
+ }
+
+ /**
+ * @param label The title to be used for this group, or null if there is none.
+ */
+ public KeyboardShortcutGroup(@Nullable CharSequence label) {
+ this(label, Collections.<KeyboardShortcutInfo>emptyList());
+ }
+
+ /**
+ * @param label The title to be used for this group, or null if there is none.
+ * @param items The set of items to be included.
+ * @param isSystemGroup Set this to {@code true} if this is s system group.
+ * @hide
+ */
+ @TestApi
+ public KeyboardShortcutGroup(@Nullable CharSequence label,
+ @NonNull List<KeyboardShortcutInfo> items, boolean isSystemGroup) {
+ mLabel = label;
+ mItems = new ArrayList<>(checkNotNull(items));
+ mSystemGroup = isSystemGroup;
+ }
+
+ /**
+ * @param label The title to be used for this group, or null if there is none.
+ * @param isSystemGroup Set this to {@code true} if this is s system group.
+ * @hide
+ */
+ @TestApi
+ public KeyboardShortcutGroup(@Nullable CharSequence label, boolean isSystemGroup) {
+ this(label, Collections.<KeyboardShortcutInfo>emptyList(), isSystemGroup);
+ }
+
+ private KeyboardShortcutGroup(Parcel source) {
+ mItems = new ArrayList<>();
+ mLabel = source.readCharSequence();
+ source.readTypedList(mItems, KeyboardShortcutInfo.CREATOR);
+ mSystemGroup = source.readInt() == 1;
+ }
+
+ /**
+ * Returns the label to be used to describe this group.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Returns the list of items included in this group.
+ */
+ public List<KeyboardShortcutInfo> getItems() {
+ return mItems;
+ }
+
+ /** @hide **/
+ @TestApi
+ public boolean isSystemGroup() {
+ return mSystemGroup;
+ }
+
+ /**
+ * Adds an item to the existing list.
+ *
+ * @param item The item to be added.
+ */
+ public void addItem(KeyboardShortcutInfo item) {
+ mItems.add(item);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeCharSequence(mLabel);
+ dest.writeTypedList(mItems);
+ dest.writeInt(mSystemGroup ? 1 : 0);
+ }
+
+ public static final Creator<KeyboardShortcutGroup> CREATOR =
+ new Creator<KeyboardShortcutGroup>() {
+ public KeyboardShortcutGroup createFromParcel(Parcel source) {
+ return new KeyboardShortcutGroup(source);
+ }
+ public KeyboardShortcutGroup[] newArray(int size) {
+ return new KeyboardShortcutGroup[size];
+ }
+ };
+}
diff --git a/android/view/KeyboardShortcutInfo.java b/android/view/KeyboardShortcutInfo.java
new file mode 100644
index 00000000..c934a4e3
--- /dev/null
+++ b/android/view/KeyboardShortcutInfo.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 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 static com.android.internal.util.Preconditions.checkArgument;
+
+import static java.lang.Character.MIN_VALUE;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about a Keyboard Shortcut.
+ */
+public final class KeyboardShortcutInfo implements Parcelable {
+ private final CharSequence mLabel;
+ private final Icon mIcon;
+ private final char mBaseCharacter;
+ private final int mKeycode;
+ private final int mModifiers;
+
+ /**
+ * @param label The label that identifies the action performed by this shortcut.
+ * @param icon An icon that identifies the action performed by this shortcut.
+ * @param keycode The keycode that triggers the shortcut. This should be a valid constant
+ * defined in {@link KeyEvent}.
+ * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+ * These should be a combination of {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+ * {@link KeyEvent#META_SYM_ON}.
+ *
+ * @hide
+ */
+ public KeyboardShortcutInfo(
+ @Nullable CharSequence label, @Nullable Icon icon, int keycode, int modifiers) {
+ mLabel = label;
+ mIcon = icon;
+ mBaseCharacter = MIN_VALUE;
+ checkArgument(keycode >= KeyEvent.KEYCODE_UNKNOWN && keycode <= KeyEvent.getMaxKeyCode());
+ mKeycode = keycode;
+ mModifiers = modifiers;
+ }
+
+ /**
+ * @param label The label that identifies the action performed by this shortcut.
+ * @param keycode The keycode that triggers the shortcut. This should be a valid constant
+ * defined in {@link KeyEvent}.
+ * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+ * These should be a combination of {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+ * {@link KeyEvent#META_SYM_ON}.
+ */
+ public KeyboardShortcutInfo(CharSequence label, int keycode, int modifiers) {
+ this(label, null, keycode, modifiers);
+ }
+
+ /**
+ * @param label The label that identifies the action performed by this shortcut.
+ * @param baseCharacter The character that triggers the shortcut.
+ * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+ * These should be a combination of {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+ * {@link KeyEvent#META_SYM_ON}.
+ */
+ public KeyboardShortcutInfo(CharSequence label, char baseCharacter, int modifiers) {
+ mLabel = label;
+ checkArgument(baseCharacter != MIN_VALUE);
+ mBaseCharacter = baseCharacter;
+ mKeycode = KeyEvent.KEYCODE_UNKNOWN;
+ mModifiers = modifiers;
+ mIcon = null;
+ }
+
+ private KeyboardShortcutInfo(Parcel source) {
+ mLabel = source.readCharSequence();
+ mIcon = source.readParcelable(null);
+ mBaseCharacter = (char) source.readInt();
+ mKeycode = source.readInt();
+ mModifiers = source.readInt();
+ }
+
+ /**
+ * Returns the label to be used to describe this shortcut.
+ */
+ @Nullable
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Returns the icon to be used to describe this shortcut.
+ *
+ * @hide
+ */
+ @Nullable
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns the base keycode that, combined with the modifiers, triggers this shortcut. If the
+ * base character was set instead, returns {@link KeyEvent#KEYCODE_UNKNOWN}. Valid keycodes are
+ * defined as constants in {@link KeyEvent}.
+ */
+ public int getKeycode() {
+ return mKeycode;
+ }
+
+ /**
+ * Returns the base character that, combined with the modifiers, triggers this shortcut. If the
+ * keycode was set instead, returns {@link Character#MIN_VALUE}.
+ */
+ public char getBaseCharacter() {
+ return mBaseCharacter;
+ }
+
+ /**
+ * Returns the set of modifiers that, combined with the key, trigger this shortcut. These can
+ * be a combination of {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_SHIFT_ON},
+ * {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_ALT_ON},
+ * {@link KeyEvent#META_FUNCTION_ON} and {@link KeyEvent#META_SYM_ON}.
+ */
+ public int getModifiers() {
+ return mModifiers;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeCharSequence(mLabel);
+ dest.writeParcelable(mIcon, 0);
+ dest.writeInt(mBaseCharacter);
+ dest.writeInt(mKeycode);
+ dest.writeInt(mModifiers);
+ }
+
+ public static final Creator<KeyboardShortcutInfo> CREATOR =
+ new Creator<KeyboardShortcutInfo>() {
+ public KeyboardShortcutInfo createFromParcel(Parcel source) {
+ return new KeyboardShortcutInfo(source);
+ }
+ public KeyboardShortcutInfo[] newArray(int size) {
+ return new KeyboardShortcutInfo[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/android/view/LayoutInflater.java b/android/view/LayoutInflater.java
new file mode 100644
index 00000000..47b8d921
--- /dev/null
+++ b/android/view/LayoutInflater.java
@@ -0,0 +1,1098 @@
+/*
+ * Copyright (C) 2007 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.LayoutRes;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Canvas;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+/**
+ * Instantiates a layout XML file into its corresponding {@link android.view.View}
+ * objects. It is never used directly. Instead, use
+ * {@link android.app.Activity#getLayoutInflater()} or
+ * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
+ * that is already hooked up to the current context and correctly configured
+ * for the device you are running on.
+ *
+ * <p>
+ * To create a new LayoutInflater with an additional {@link Factory} for your
+ * own views, you can use {@link #cloneInContext} to clone an existing
+ * ViewFactory, and then call {@link #setFactory} on it to include your
+ * Factory.
+ *
+ * <p>
+ * For performance reasons, view inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource
+ * (R.<em>something</em> file.)
+ */
+@SystemService(Context.LAYOUT_INFLATER_SERVICE)
+public abstract class LayoutInflater {
+
+ private static final String TAG = LayoutInflater.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /** Empty stack trace used to avoid log spam in re-throw exceptions. */
+ private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
+
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected final Context mContext;
+
+ // these are optional, set by the caller
+ private boolean mFactorySet;
+ private Factory mFactory;
+ private Factory2 mFactory2;
+ private Factory2 mPrivateFactory;
+ private Filter mFilter;
+
+ final Object[] mConstructorArgs = new Object[2];
+
+ static final Class<?>[] mConstructorSignature = new Class[] {
+ Context.class, AttributeSet.class};
+
+ private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
+ new HashMap<String, Constructor<? extends View>>();
+
+ private HashMap<String, Boolean> mFilterMap;
+
+ private TypedValue mTempValue;
+
+ private static final String TAG_MERGE = "merge";
+ private static final String TAG_INCLUDE = "include";
+ private static final String TAG_1995 = "blink";
+ private static final String TAG_REQUEST_FOCUS = "requestFocus";
+ private static final String TAG_TAG = "tag";
+
+ private static final String ATTR_LAYOUT = "layout";
+
+ private static final int[] ATTRS_THEME = new int[] {
+ com.android.internal.R.attr.theme };
+
+ /**
+ * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
+ * to be inflated.
+ *
+ */
+ public interface Filter {
+ /**
+ * Hook to allow clients of the LayoutInflater to restrict the set of Views
+ * that are allowed to be inflated.
+ *
+ * @param clazz The class object for the View that is about to be inflated
+ *
+ * @return True if this class is allowed to be inflated, or false otherwise
+ */
+ @SuppressWarnings("unchecked")
+ boolean onLoadClass(Class clazz);
+ }
+
+ public interface Factory {
+ /**
+ * Hook you can supply that is called when inflating from a LayoutInflater.
+ * You can use this to customize the tag names available in your XML
+ * layout files.
+ *
+ * <p>
+ * Note that it is good practice to prefix these custom names with your
+ * package (i.e., com.coolcompany.apps) to avoid conflicts with system
+ * names.
+ *
+ * @param name Tag name to be inflated.
+ * @param context The context the view is being created in.
+ * @param attrs Inflation attributes as specified in XML file.
+ *
+ * @return View Newly created view. Return null for the default
+ * behavior.
+ */
+ public View onCreateView(String name, Context context, AttributeSet attrs);
+ }
+
+ public interface Factory2 extends Factory {
+ /**
+ * Version of {@link #onCreateView(String, Context, AttributeSet)}
+ * that also supplies the parent that the view created view will be
+ * placed in.
+ *
+ * @param parent The parent that the created view will be placed
+ * in; <em>note that this may be null</em>.
+ * @param name Tag name to be inflated.
+ * @param context The context the view is being created in.
+ * @param attrs Inflation attributes as specified in XML file.
+ *
+ * @return View Newly created view. Return null for the default
+ * behavior.
+ */
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
+ }
+
+ private static class FactoryMerger implements Factory2 {
+ private final Factory mF1, mF2;
+ private final Factory2 mF12, mF22;
+
+ FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
+ mF1 = f1;
+ mF2 = f2;
+ mF12 = f12;
+ mF22 = f22;
+ }
+
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ View v = mF1.onCreateView(name, context, attrs);
+ if (v != null) return v;
+ return mF2.onCreateView(name, context, attrs);
+ }
+
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
+ : mF1.onCreateView(name, context, attrs);
+ if (v != null) return v;
+ return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
+ : mF2.onCreateView(name, context, attrs);
+ }
+ }
+
+ /**
+ * Create a new LayoutInflater instance associated with a particular Context.
+ * Applications will almost always want to use
+ * {@link Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
+ *
+ * @param context The Context in which this LayoutInflater will create its
+ * Views; most importantly, this supplies the theme from which the default
+ * values for their attributes are retrieved.
+ */
+ protected LayoutInflater(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Create a new LayoutInflater instance that is a copy of an existing
+ * LayoutInflater, optionally with its Context changed. For use in
+ * implementing {@link #cloneInContext}.
+ *
+ * @param original The original LayoutInflater to copy.
+ * @param newContext The new Context to use.
+ */
+ protected LayoutInflater(LayoutInflater original, Context newContext) {
+ mContext = newContext;
+ mFactory = original.mFactory;
+ mFactory2 = original.mFactory2;
+ mPrivateFactory = original.mPrivateFactory;
+ setFilter(original.mFilter);
+ }
+
+ /**
+ * Obtains the LayoutInflater from the given context.
+ */
+ public static LayoutInflater from(Context context) {
+ LayoutInflater LayoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ if (LayoutInflater == null) {
+ throw new AssertionError("LayoutInflater not found.");
+ }
+ return LayoutInflater;
+ }
+
+ /**
+ * Create a copy of the existing LayoutInflater object, with the copy
+ * pointing to a different Context than the original. This is used by
+ * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
+ * with the new Context theme.
+ *
+ * @param newContext The new Context to associate with the new LayoutInflater.
+ * May be the same as the original Context if desired.
+ *
+ * @return Returns a brand spanking new LayoutInflater object associated with
+ * the given Context.
+ */
+ public abstract LayoutInflater cloneInContext(Context newContext);
+
+ /**
+ * Return the context we are running in, for access to resources, class
+ * loader, etc.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Return the current {@link Factory} (or null). This is called on each element
+ * name. If the factory returns a View, add that to the hierarchy. If it
+ * returns null, proceed to call onCreateView(name).
+ */
+ public final Factory getFactory() {
+ return mFactory;
+ }
+
+ /**
+ * Return the current {@link Factory2}. Returns null if no factory is set
+ * or the set factory does not implement the {@link Factory2} interface.
+ * This is called on each element
+ * name. If the factory returns a View, add that to the hierarchy. If it
+ * returns null, proceed to call onCreateView(name).
+ */
+ public final Factory2 getFactory2() {
+ return mFactory2;
+ }
+
+ /**
+ * Attach a custom Factory interface for creating views while using
+ * this LayoutInflater. This must not be null, and can only be set once;
+ * after setting, you can not change the factory. This is
+ * called on each element name as the xml is parsed. If the factory returns
+ * a View, that is added to the hierarchy. If it returns null, the next
+ * factory default {@link #onCreateView} method is called.
+ *
+ * <p>If you have an existing
+ * LayoutInflater and want to add your own factory to it, use
+ * {@link #cloneInContext} to clone the existing instance and then you
+ * can use this function (once) on the returned new instance. This will
+ * merge your own factory with whatever factory the original instance is
+ * using.
+ */
+ public void setFactory(Factory factory) {
+ if (mFactorySet) {
+ throw new IllegalStateException("A factory has already been set on this LayoutInflater");
+ }
+ if (factory == null) {
+ throw new NullPointerException("Given factory can not be null");
+ }
+ mFactorySet = true;
+ if (mFactory == null) {
+ mFactory = factory;
+ } else {
+ mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
+ }
+ }
+
+ /**
+ * Like {@link #setFactory}, but allows you to set a {@link Factory2}
+ * interface.
+ */
+ public void setFactory2(Factory2 factory) {
+ if (mFactorySet) {
+ throw new IllegalStateException("A factory has already been set on this LayoutInflater");
+ }
+ if (factory == null) {
+ throw new NullPointerException("Given factory can not be null");
+ }
+ mFactorySet = true;
+ if (mFactory == null) {
+ mFactory = mFactory2 = factory;
+ } else {
+ mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
+ }
+ }
+
+ /**
+ * @hide for use by framework
+ */
+ public void setPrivateFactory(Factory2 factory) {
+ if (mPrivateFactory == null) {
+ mPrivateFactory = factory;
+ } else {
+ mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
+ }
+ }
+
+ /**
+ * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
+ * that are allowed to be inflated.
+ */
+ public Filter getFilter() {
+ return mFilter;
+ }
+
+ /**
+ * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
+ * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
+ * throw an {@link InflateException}. This filter will replace any previous filter set on this
+ * LayoutInflater.
+ *
+ * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
+ * This filter will replace any previous filter set on this LayoutInflater.
+ */
+ public void setFilter(Filter filter) {
+ mFilter = filter;
+ if (filter != null) {
+ mFilterMap = new HashMap<String, Boolean>();
+ }
+ }
+
+ /**
+ * Inflate a new view hierarchy from the specified xml resource. Throws
+ * {@link InflateException} if there is an error.
+ *
+ * @param resource ID for an XML layout resource to load (e.g.,
+ * <code>R.layout.main_page</code>)
+ * @param root Optional view to be the parent of the generated hierarchy.
+ * @return The root View of the inflated hierarchy. If root was supplied,
+ * this is the root View; otherwise it is the root of the inflated
+ * XML file.
+ */
+ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
+ return inflate(resource, root, root != null);
+ }
+
+ /**
+ * Inflate a new view hierarchy from the specified xml node. Throws
+ * {@link InflateException} if there is an error. *
+ * <p>
+ * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+ * reasons, view inflation relies heavily on pre-processing of XML files
+ * that is done at build time. Therefore, it is not currently possible to
+ * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
+ *
+ * @param parser XML dom node containing the description of the view
+ * hierarchy.
+ * @param root Optional view to be the parent of the generated hierarchy.
+ * @return The root View of the inflated hierarchy. If root was supplied,
+ * this is the root View; otherwise it is the root of the inflated
+ * XML file.
+ */
+ public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
+ return inflate(parser, root, root != null);
+ }
+
+ /**
+ * Inflate a new view hierarchy from the specified xml resource. Throws
+ * {@link InflateException} if there is an error.
+ *
+ * @param resource ID for an XML layout resource to load (e.g.,
+ * <code>R.layout.main_page</code>)
+ * @param root Optional view to be the parent of the generated hierarchy (if
+ * <em>attachToRoot</em> is true), or else simply an object that
+ * provides a set of LayoutParams values for root of the returned
+ * hierarchy (if <em>attachToRoot</em> is false.)
+ * @param attachToRoot Whether the inflated hierarchy should be attached to
+ * the root parameter? If false, root is only used to create the
+ * correct subclass of LayoutParams for the root view in the XML.
+ * @return The root View of the inflated hierarchy. If root was supplied and
+ * attachToRoot is true, this is root; otherwise it is the root of
+ * the inflated XML file.
+ */
+ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
+ final Resources res = getContext().getResources();
+ if (DEBUG) {
+ Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ + Integer.toHexString(resource) + ")");
+ }
+
+ final XmlResourceParser parser = res.getLayout(resource);
+ try {
+ return inflate(parser, root, attachToRoot);
+ } finally {
+ parser.close();
+ }
+ }
+
+ /**
+ * Inflate a new view hierarchy from the specified XML node. Throws
+ * {@link InflateException} if there is an error.
+ * <p>
+ * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+ * reasons, view inflation relies heavily on pre-processing of XML files
+ * that is done at build time. Therefore, it is not currently possible to
+ * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
+ *
+ * @param parser XML dom node containing the description of the view
+ * hierarchy.
+ * @param root Optional view to be the parent of the generated hierarchy (if
+ * <em>attachToRoot</em> is true), or else simply an object that
+ * provides a set of LayoutParams values for root of the returned
+ * hierarchy (if <em>attachToRoot</em> is false.)
+ * @param attachToRoot Whether the inflated hierarchy should be attached to
+ * the root parameter? If false, root is only used to create the
+ * correct subclass of LayoutParams for the root view in the XML.
+ * @return The root View of the inflated hierarchy. If root was supplied and
+ * attachToRoot is true, this is root; otherwise it is the root of
+ * the inflated XML file.
+ */
+ public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
+ synchronized (mConstructorArgs) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
+
+ final Context inflaterContext = mContext;
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ Context lastContext = (Context) mConstructorArgs[0];
+ mConstructorArgs[0] = inflaterContext;
+ View result = root;
+
+ try {
+ // Look for the root node.
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new InflateException(parser.getPositionDescription()
+ + ": No start tag found!");
+ }
+
+ final String name = parser.getName();
+
+ if (DEBUG) {
+ System.out.println("**************************");
+ System.out.println("Creating root view: "
+ + name);
+ System.out.println("**************************");
+ }
+
+ if (TAG_MERGE.equals(name)) {
+ if (root == null || !attachToRoot) {
+ throw new InflateException("<merge /> can be used only with a valid "
+ + "ViewGroup root and attachToRoot=true");
+ }
+
+ rInflate(parser, root, inflaterContext, attrs, false);
+ } else {
+ // Temp is the root view that was found in the xml
+ final View temp = createViewFromTag(root, name, inflaterContext, attrs);
+
+ ViewGroup.LayoutParams params = null;
+
+ if (root != null) {
+ if (DEBUG) {
+ System.out.println("Creating params from root: " +
+ root);
+ }
+ // Create layout params that match root, if supplied
+ params = root.generateLayoutParams(attrs);
+ if (!attachToRoot) {
+ // Set the layout params for temp if we are not
+ // attaching. (If we are, we use addView, below)
+ temp.setLayoutParams(params);
+ }
+ }
+
+ if (DEBUG) {
+ System.out.println("-----> start inflating children");
+ }
+
+ // Inflate all children under temp against its context.
+ rInflateChildren(parser, temp, attrs, true);
+
+ if (DEBUG) {
+ System.out.println("-----> done inflating children");
+ }
+
+ // We are supposed to attach all the views we found (int temp)
+ // to root. Do that now.
+ if (root != null && attachToRoot) {
+ root.addView(temp, params);
+ }
+
+ // Decide whether to return the root that was passed in or the
+ // top view found in xml.
+ if (root == null || !attachToRoot) {
+ result = temp;
+ }
+ }
+
+ } catch (XmlPullParserException e) {
+ final InflateException ie = new InflateException(e.getMessage(), e);
+ ie.setStackTrace(EMPTY_STACK_TRACE);
+ throw ie;
+ } catch (Exception e) {
+ final InflateException ie = new InflateException(parser.getPositionDescription()
+ + ": " + e.getMessage(), e);
+ ie.setStackTrace(EMPTY_STACK_TRACE);
+ throw ie;
+ } finally {
+ // Don't retain static reference on context.
+ mConstructorArgs[0] = lastContext;
+ mConstructorArgs[1] = null;
+
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ return result;
+ }
+ }
+
+ private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
+
+ private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
+ final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
+ if (constructorLoader == BOOT_CLASS_LOADER) {
+ // fast path for boot class loader (most common case?) - always ok
+ return true;
+ }
+ // in all normal cases (no dynamic code loading), we will exit the following loop on the
+ // first iteration (i.e. when the declaring classloader is the contexts class loader).
+ ClassLoader cl = mContext.getClassLoader();
+ do {
+ if (constructorLoader == cl) {
+ return true;
+ }
+ cl = cl.getParent();
+ } while (cl != null);
+ return false;
+ }
+
+ /**
+ * Low-level function for instantiating a view by name. This attempts to
+ * instantiate a view class of the given <var>name</var> found in this
+ * LayoutInflater's ClassLoader.
+ *
+ * <p>
+ * There are two things that can happen in an error case: either the
+ * exception describing the error will be thrown, or a null will be
+ * returned. You must deal with both possibilities -- the former will happen
+ * the first time createView() is called for a class of a particular name,
+ * the latter every time there-after for that class name.
+ *
+ * @param name The full name of the class to be instantiated.
+ * @param attrs The XML attributes supplied for this instance.
+ *
+ * @return View The newly instantiated view, or null.
+ */
+ public final View createView(String name, String prefix, AttributeSet attrs)
+ throws ClassNotFoundException, InflateException {
+ Constructor<? extends View> constructor = sConstructorMap.get(name);
+ if (constructor != null && !verifyClassLoader(constructor)) {
+ constructor = null;
+ sConstructorMap.remove(name);
+ }
+ Class<? extends View> clazz = null;
+
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
+
+ if (constructor == null) {
+ // Class not found in the cache, see if it's real, and try to add it
+ clazz = mContext.getClassLoader().loadClass(
+ prefix != null ? (prefix + name) : name).asSubclass(View.class);
+
+ if (mFilter != null && clazz != null) {
+ boolean allowed = mFilter.onLoadClass(clazz);
+ if (!allowed) {
+ failNotAllowed(name, prefix, attrs);
+ }
+ }
+ constructor = clazz.getConstructor(mConstructorSignature);
+ constructor.setAccessible(true);
+ sConstructorMap.put(name, constructor);
+ } else {
+ // If we have a filter, apply it to cached constructor
+ if (mFilter != null) {
+ // Have we seen this name before?
+ Boolean allowedState = mFilterMap.get(name);
+ if (allowedState == null) {
+ // New class -- remember whether it is allowed
+ clazz = mContext.getClassLoader().loadClass(
+ prefix != null ? (prefix + name) : name).asSubclass(View.class);
+
+ boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
+ mFilterMap.put(name, allowed);
+ if (!allowed) {
+ failNotAllowed(name, prefix, attrs);
+ }
+ } else if (allowedState.equals(Boolean.FALSE)) {
+ failNotAllowed(name, prefix, attrs);
+ }
+ }
+ }
+
+ Object lastContext = mConstructorArgs[0];
+ if (mConstructorArgs[0] == null) {
+ // Fill in the context if not already within inflation.
+ mConstructorArgs[0] = mContext;
+ }
+ Object[] args = mConstructorArgs;
+ args[1] = attrs;
+
+ final View view = constructor.newInstance(args);
+ if (view instanceof ViewStub) {
+ // Use the same context when inflating ViewStub later.
+ final ViewStub viewStub = (ViewStub) view;
+ viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
+ }
+ mConstructorArgs[0] = lastContext;
+ return view;
+
+ } catch (NoSuchMethodException e) {
+ final InflateException ie = new InflateException(attrs.getPositionDescription()
+ + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
+ ie.setStackTrace(EMPTY_STACK_TRACE);
+ throw ie;
+
+ } catch (ClassCastException e) {
+ // If loaded class is not a View subclass
+ final InflateException ie = new InflateException(attrs.getPositionDescription()
+ + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
+ ie.setStackTrace(EMPTY_STACK_TRACE);
+ throw ie;
+ } catch (ClassNotFoundException e) {
+ // If loadClass fails, we should propagate the exception.
+ throw e;
+ } catch (Exception e) {
+ final InflateException ie = new InflateException(
+ attrs.getPositionDescription() + ": Error inflating class "
+ + (clazz == null ? "<unknown>" : clazz.getName()), e);
+ ie.setStackTrace(EMPTY_STACK_TRACE);
+ throw ie;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ /**
+ * Throw an exception because the specified class is not allowed to be inflated.
+ */
+ private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
+ throw new InflateException(attrs.getPositionDescription()
+ + ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
+ }
+
+ /**
+ * This routine is responsible for creating the correct subclass of View
+ * given the xml element name. Override it to handle custom view objects. If
+ * you override this in your subclass be sure to call through to
+ * super.onCreateView(name) for names you do not recognize.
+ *
+ * @param name The fully qualified class name of the View to be create.
+ * @param attrs An AttributeSet of attributes to apply to the View.
+ *
+ * @return View The View created.
+ */
+ protected View onCreateView(String name, AttributeSet attrs)
+ throws ClassNotFoundException {
+ return createView(name, "android.view.", attrs);
+ }
+
+ /**
+ * Version of {@link #onCreateView(String, AttributeSet)} that also
+ * takes the future parent of the view being constructed. The default
+ * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
+ *
+ * @param parent The future parent of the returned view. <em>Note that
+ * this may be null.</em>
+ * @param name The fully qualified class name of the View to be create.
+ * @param attrs An AttributeSet of attributes to apply to the View.
+ *
+ * @return View The View created.
+ */
+ protected View onCreateView(View parent, String name, AttributeSet attrs)
+ throws ClassNotFoundException {
+ return onCreateView(name, attrs);
+ }
+
+ /**
+ * Convenience method for calling through to the five-arg createViewFromTag
+ * method. This method passes {@code false} for the {@code ignoreThemeAttr}
+ * argument and should be used for everything except {@code &gt;include>}
+ * tag parsing.
+ */
+ private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
+ return createViewFromTag(parent, name, context, attrs, false);
+ }
+
+ /**
+ * Creates a view from a tag name using the supplied attribute set.
+ * <p>
+ * <strong>Note:</strong> Default visibility so the BridgeInflater can
+ * override it.
+ *
+ * @param parent the parent view, used to inflate layout params
+ * @param name the name of the XML tag used to define the view
+ * @param context the inflation context for the view, typically the
+ * {@code parent} or base layout inflater context
+ * @param attrs the attribute set for the XML tag used to define the view
+ * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
+ * attribute (if set) for the view being inflated,
+ * {@code false} otherwise
+ */
+ View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
+ boolean ignoreThemeAttr) {
+ if (name.equals("view")) {
+ name = attrs.getAttributeValue(null, "class");
+ }
+
+ // Apply a theme wrapper, if allowed and one is specified.
+ if (!ignoreThemeAttr) {
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ if (themeResId != 0) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+ }
+
+ if (name.equals(TAG_1995)) {
+ // Let's party like it's 1995!
+ return new BlinkLayout(context, attrs);
+ }
+
+ try {
+ View view;
+ if (mFactory2 != null) {
+ view = mFactory2.onCreateView(parent, name, context, attrs);
+ } else if (mFactory != null) {
+ view = mFactory.onCreateView(name, context, attrs);
+ } else {
+ view = null;
+ }
+
+ if (view == null && mPrivateFactory != null) {
+ view = mPrivateFactory.onCreateView(parent, name, context, attrs);
+ }
+
+ if (view == null) {
+ final Object lastContext = mConstructorArgs[0];
+ mConstructorArgs[0] = context;
+ try {
+ if (-1 == name.indexOf('.')) {
+ view = onCreateView(parent, name, attrs);
+ } else {
+ view = createView(name, null, attrs);
+ }
+ } finally {
+ mConstructorArgs[0] = lastContext;
+ }
+ }
+
+ return view;
+ } catch (InflateException e) {
+ throw e;
+
+ } catch (ClassNotFoundException e) {
+ final InflateException ie = new InflateException(attrs.getPositionDescription()
+ + ": Error inflating class " + name, e);
+ ie.setStackTrace(EMPTY_STACK_TRACE);
+ throw ie;
+
+ } catch (Exception e) {
+ final InflateException ie = new InflateException(attrs.getPositionDescription()
+ + ": Error inflating class " + name, e);
+ ie.setStackTrace(EMPTY_STACK_TRACE);
+ throw ie;
+ }
+ }
+
+ /**
+ * Recursive method used to inflate internal (non-root) children. This
+ * method calls through to {@link #rInflate} using the parent context as
+ * the inflation context.
+ * <strong>Note:</strong> Default visibility so the BridgeInflater can
+ * call it.
+ */
+ final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
+ boolean finishInflate) throws XmlPullParserException, IOException {
+ rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
+ }
+
+ /**
+ * Recursive method used to descend down the xml hierarchy and instantiate
+ * views, instantiate their children, and then call onFinishInflate().
+ * <p>
+ * <strong>Note:</strong> Default visibility so the BridgeInflater can
+ * override it.
+ */
+ void rInflate(XmlPullParser parser, View parent, Context context,
+ AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
+
+ final int depth = parser.getDepth();
+ int type;
+ boolean pendingRequestFocus = false;
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String name = parser.getName();
+
+ if (TAG_REQUEST_FOCUS.equals(name)) {
+ pendingRequestFocus = true;
+ consumeChildElements(parser);
+ } else if (TAG_TAG.equals(name)) {
+ parseViewTag(parser, parent, attrs);
+ } else if (TAG_INCLUDE.equals(name)) {
+ if (parser.getDepth() == 0) {
+ throw new InflateException("<include /> cannot be the root element");
+ }
+ parseInclude(parser, context, parent, attrs);
+ } else if (TAG_MERGE.equals(name)) {
+ throw new InflateException("<merge /> must be the root element");
+ } else {
+ final View view = createViewFromTag(parent, name, context, attrs);
+ final ViewGroup viewGroup = (ViewGroup) parent;
+ final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
+ rInflateChildren(parser, view, attrs, true);
+ viewGroup.addView(view, params);
+ }
+ }
+
+ if (pendingRequestFocus) {
+ parent.restoreDefaultFocus();
+ }
+
+ if (finishInflate) {
+ parent.onFinishInflate();
+ }
+ }
+
+ /**
+ * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
+ * containing View.
+ */
+ private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+ final Context context = view.getContext();
+ final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
+ final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
+ final CharSequence value = ta.getText(R.styleable.ViewTag_value);
+ view.setTag(key, value);
+ ta.recycle();
+
+ consumeChildElements(parser);
+ }
+
+ private void parseInclude(XmlPullParser parser, Context context, View parent,
+ AttributeSet attrs) throws XmlPullParserException, IOException {
+ int type;
+
+ if (parent instanceof ViewGroup) {
+ // Apply a theme wrapper, if requested. This is sort of a weird
+ // edge case, since developers think the <include> overwrites
+ // values in the AttributeSet of the included View. So, if the
+ // included View has a theme attribute, we'll need to ignore it.
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ final boolean hasThemeOverride = themeResId != 0;
+ if (hasThemeOverride) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+
+ // If the layout is pointing to a theme attribute, we have to
+ // massage the value to get a resource identifier out of it.
+ int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ if (value == null || value.length() <= 0) {
+ throw new InflateException("You must specify a layout in the"
+ + " include tag: <include layout=\"@layout/layoutID\" />");
+ }
+
+ // Attempt to resolve the "?attr/name" string to an attribute
+ // within the default (e.g. application) package.
+ layout = context.getResources().getIdentifier(
+ value.substring(1), "attr", context.getPackageName());
+
+ }
+
+ // The layout might be referencing a theme attribute.
+ if (mTempValue == null) {
+ mTempValue = new TypedValue();
+ }
+ if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
+ layout = mTempValue.resourceId;
+ }
+
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ throw new InflateException("You must specify a valid layout "
+ + "reference. The layout ID " + value + " is not valid.");
+ } else {
+ final XmlResourceParser childParser = context.getResources().getLayout(layout);
+
+ try {
+ final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
+
+ while ((type = childParser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new InflateException(childParser.getPositionDescription() +
+ ": No start tag found!");
+ }
+
+ final String childName = childParser.getName();
+
+ if (TAG_MERGE.equals(childName)) {
+ // The <merge> tag doesn't support android:theme, so
+ // nothing special to do here.
+ rInflate(childParser, parent, context, childAttrs, false);
+ } else {
+ final View view = createViewFromTag(parent, childName,
+ context, childAttrs, hasThemeOverride);
+ final ViewGroup group = (ViewGroup) parent;
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.Include);
+ final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
+ final int visibility = a.getInt(R.styleable.Include_visibility, -1);
+ a.recycle();
+
+ // We try to load the layout params set in the <include /> tag.
+ // If the parent can't generate layout params (ex. missing width
+ // or height for the framework ViewGroups, though this is not
+ // necessarily true of all ViewGroups) then we expect it to throw
+ // a runtime exception.
+ // We catch this exception and set localParams accordingly: true
+ // means we successfully loaded layout params from the <include>
+ // tag, false means we need to rely on the included layout params.
+ ViewGroup.LayoutParams params = null;
+ try {
+ params = group.generateLayoutParams(attrs);
+ } catch (RuntimeException e) {
+ // Ignore, just fail over to child attrs.
+ }
+ if (params == null) {
+ params = group.generateLayoutParams(childAttrs);
+ }
+ view.setLayoutParams(params);
+
+ // Inflate all children.
+ rInflateChildren(childParser, view, childAttrs, true);
+
+ if (id != View.NO_ID) {
+ view.setId(id);
+ }
+
+ switch (visibility) {
+ case 0:
+ view.setVisibility(View.VISIBLE);
+ break;
+ case 1:
+ view.setVisibility(View.INVISIBLE);
+ break;
+ case 2:
+ view.setVisibility(View.GONE);
+ break;
+ }
+
+ group.addView(view);
+ }
+ } finally {
+ childParser.close();
+ }
+ }
+ } else {
+ throw new InflateException("<include /> can only be used inside of a ViewGroup");
+ }
+
+ LayoutInflater.consumeChildElements(parser);
+ }
+
+ /**
+ * <strong>Note:</strong> default visibility so that
+ * LayoutInflater_Delegate can call it.
+ */
+ final static void consumeChildElements(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ final int currentDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
+ // Empty
+ }
+ }
+
+ private static class BlinkLayout extends FrameLayout {
+ private static final int MESSAGE_BLINK = 0x42;
+ private static final int BLINK_DELAY = 500;
+
+ private boolean mBlink;
+ private boolean mBlinkState;
+ private final Handler mHandler;
+
+ public BlinkLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mHandler = new Handler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MESSAGE_BLINK) {
+ if (mBlink) {
+ mBlinkState = !mBlinkState;
+ makeBlink();
+ }
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private void makeBlink() {
+ Message message = mHandler.obtainMessage(MESSAGE_BLINK);
+ mHandler.sendMessageDelayed(message, BLINK_DELAY);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mBlink = true;
+ mBlinkState = true;
+
+ makeBlink();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ mBlink = false;
+ mBlinkState = true;
+
+ mHandler.removeMessages(MESSAGE_BLINK);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mBlinkState) {
+ super.dispatchDraw(canvas);
+ }
+ }
+ }
+}
diff --git a/android/view/LayoutInflater_Delegate.java b/android/view/LayoutInflater_Delegate.java
new file mode 100644
index 00000000..cec6bb38
--- /dev/null
+++ b/android/view/LayoutInflater_Delegate.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2011 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 com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.util.Xml;
+
+import java.io.IOException;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater}
+ *
+ * Through the layoutlib_create tool, the original methods of LayoutInflater have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class LayoutInflater_Delegate {
+ private static final String TAG_MERGE = "merge";
+
+ private static final String ATTR_LAYOUT = "layout";
+
+ private static final int[] ATTRS_THEME = new int[] {
+ com.android.internal.R.attr.theme };
+
+ public static boolean sIsInInclude = false;
+
+ /**
+ * Recursive method used to descend down the xml hierarchy and instantiate
+ * views, instantiate their children, and then call onFinishInflate().
+ *
+ * This implementation just records the merge status before calling the default implementation.
+ */
+ @LayoutlibDelegate
+ /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
+ View parent, Context context, AttributeSet attrs, boolean finishInflate)
+ throws XmlPullParserException, IOException {
+
+ if (finishInflate == false) {
+ // this is a merge rInflate!
+ if (thisInflater instanceof BridgeInflater) {
+ ((BridgeInflater) thisInflater).setIsInMerge(true);
+ }
+ }
+
+ // ---- START DEFAULT IMPLEMENTATION.
+
+ thisInflater.rInflate_Original(parser, parent, context, attrs, finishInflate);
+
+ // ---- END DEFAULT IMPLEMENTATION.
+
+ if (finishInflate == false) {
+ // this is a merge rInflate!
+ if (thisInflater instanceof BridgeInflater) {
+ ((BridgeInflater) thisInflater).setIsInMerge(false);
+ }
+ }
+ }
+
+ @LayoutlibDelegate
+ public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser,
+ Context context, View parent, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+ int type;
+
+ if (parent instanceof ViewGroup) {
+ // Apply a theme wrapper, if requested. This is sort of a weird
+ // edge case, since developers think the <include> overwrites
+ // values in the AttributeSet of the included View. So, if the
+ // included View has a theme attribute, we'll need to ignore it.
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ final boolean hasThemeOverride = themeResId != 0;
+ if (hasThemeOverride) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+
+ // If the layout is pointing to a theme attribute, we have to
+ // massage the value to get a resource identifier out of it.
+ int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ if (value == null || value.length() <= 0) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
+ + " include tag: <include layout=\"@layout/layoutID\" />", null);
+ LayoutInflater.consumeChildElements(parser);
+ return;
+ }
+
+ // Attempt to resolve the "?attr/name" string to an identifier.
+ layout = context.getResources().getIdentifier(value.substring(1), null, null);
+ }
+
+ // The layout might be referencing a theme attribute.
+ // ---- START CHANGES
+ if (layout != 0) {
+ final TypedValue tempValue = new TypedValue();
+ if (context.getTheme().resolveAttribute(layout, tempValue, true)) {
+ layout = tempValue.resourceId;
+ }
+ }
+ // ---- END CHANGES
+
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ if (value == null) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
+ + " include tag: <include layout=\"@layout/layoutID\" />", null);
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a valid layout "
+ + "reference. The layout ID " + value + " is not valid.", null);
+ }
+ } else {
+ final XmlResourceParser childParser =
+ thisInflater.getContext().getResources().getLayout(layout);
+
+ try {
+ final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
+
+ while ((type = childParser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ childParser.getPositionDescription() + ": No start tag found!",
+ null);
+ LayoutInflater.consumeChildElements(parser);
+ return;
+ }
+
+ final String childName = childParser.getName();
+
+ if (TAG_MERGE.equals(childName)) {
+ // Inflate all children.
+ thisInflater.rInflate(childParser, parent, context, childAttrs, false);
+ } else {
+ final View view = thisInflater.createViewFromTag(parent, childName,
+ context, childAttrs, hasThemeOverride);
+ final ViewGroup group = (ViewGroup) parent;
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Include);
+ final int id = a.getResourceId(
+ com.android.internal.R.styleable.Include_id, View.NO_ID);
+ final int visibility = a.getInt(
+ com.android.internal.R.styleable.Include_visibility, -1);
+ a.recycle();
+
+ // We try to load the layout params set in the <include /> tag. If
+ // they don't exist, we will rely on the layout params set in the
+ // included XML file.
+ // During a layoutparams generation, a runtime exception is thrown
+ // if either layout_width or layout_height is missing. We catch
+ // this exception and set localParams accordingly: true means we
+ // successfully loaded layout params from the <include /> tag,
+ // false means we need to rely on the included layout params.
+ ViewGroup.LayoutParams params = null;
+ try {
+ // ---- START CHANGES
+ sIsInInclude = true;
+ // ---- END CHANGES
+
+ params = group.generateLayoutParams(attrs);
+ } catch (RuntimeException ignored) {
+ // Ignore, just fail over to child attrs.
+ } finally {
+ // ---- START CHANGES
+ sIsInInclude = false;
+ // ---- END CHANGES
+ }
+ if (params == null) {
+ params = group.generateLayoutParams(childAttrs);
+ }
+ view.setLayoutParams(params);
+
+ // Inflate all children.
+ thisInflater.rInflateChildren(childParser, view, childAttrs, true);
+
+ if (id != View.NO_ID) {
+ view.setId(id);
+ }
+
+ switch (visibility) {
+ case 0:
+ view.setVisibility(View.VISIBLE);
+ break;
+ case 1:
+ view.setVisibility(View.INVISIBLE);
+ break;
+ case 2:
+ view.setVisibility(View.GONE);
+ break;
+ }
+
+ group.addView(view);
+ }
+ } finally {
+ childParser.close();
+ }
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "<include /> can only be used inside of a ViewGroup",
+ null);
+ }
+
+ LayoutInflater.consumeChildElements(parser);
+ }
+}
diff --git a/android/view/MagnificationSpec.java b/android/view/MagnificationSpec.java
new file mode 100644
index 00000000..956a2115
--- /dev/null
+++ b/android/view/MagnificationSpec.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools.SynchronizedPool;
+
+/**
+ * This class represents spec for performing screen magnification.
+ *
+ * @hide
+ */
+public class MagnificationSpec implements Parcelable {
+ private static final int MAX_POOL_SIZE = 20;
+ private static final SynchronizedPool<MagnificationSpec> sPool =
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+
+ /** The magnification scaling factor. */
+ public float scale = 1.0f;
+
+ /**
+ * The X coordinate, in unscaled screen-relative pixels, around which
+ * magnification is focused.
+ */
+ public float offsetX;
+
+ /**
+ * The Y coordinate, in unscaled screen-relative pixels, around which
+ * magnification is focused.
+ */
+ public float offsetY;
+
+ private MagnificationSpec() {
+ /* do nothing - reducing visibility */
+ }
+
+ public void initialize(float scale, float offsetX, float offsetY) {
+ if (scale < 1) {
+ throw new IllegalArgumentException("Scale must be greater than or equal to one!");
+ }
+ this.scale = scale;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ }
+
+ public boolean isNop() {
+ return scale == 1.0f && offsetX == 0 && offsetY == 0;
+ }
+
+ public static MagnificationSpec obtain(MagnificationSpec other) {
+ MagnificationSpec info = obtain();
+ info.scale = other.scale;
+ info.offsetX = other.offsetX;
+ info.offsetY = other.offsetY;
+ return info;
+ }
+
+ public static MagnificationSpec obtain() {
+ MagnificationSpec spec = sPool.acquire();
+ return (spec != null) ? spec : new MagnificationSpec();
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ public void clear() {
+ scale = 1.0f;
+ offsetX = 0.0f;
+ offsetY = 0.0f;
+ }
+
+ public void setTo(MagnificationSpec other) {
+ scale = other.scale;
+ offsetX = other.offsetX;
+ offsetY = other.offsetY;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeFloat(scale);
+ parcel.writeFloat(offsetX);
+ parcel.writeFloat(offsetY);
+ recycle();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ final MagnificationSpec s = (MagnificationSpec) other;
+ return scale == s.scale && offsetX == s.offsetX && offsetY == s.offsetY;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0);
+ result = 31 * result + (offsetX != +0.0f ? Float.floatToIntBits(offsetX) : 0);
+ result = 31 * result + (offsetY != +0.0f ? Float.floatToIntBits(offsetY) : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<scale:");
+ builder.append(Float.toString(scale));
+ builder.append(",offsetX:");
+ builder.append(Float.toString(offsetX));
+ builder.append(",offsetY:");
+ builder.append(Float.toString(offsetY));
+ builder.append(">");
+ return builder.toString();
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ scale = parcel.readFloat();
+ offsetX = parcel.readFloat();
+ offsetY = parcel.readFloat();
+ }
+
+ public static final Creator<MagnificationSpec> CREATOR = new Creator<MagnificationSpec>() {
+ @Override
+ public MagnificationSpec[] newArray(int size) {
+ return new MagnificationSpec[size];
+ }
+
+ @Override
+ public MagnificationSpec createFromParcel(Parcel parcel) {
+ MagnificationSpec spec = MagnificationSpec.obtain();
+ spec.initFromParcel(parcel);
+ return spec;
+ }
+ };
+}
diff --git a/android/view/Menu.java b/android/view/Menu.java
new file mode 100644
index 00000000..6d1f740a
--- /dev/null
+++ b/android/view/Menu.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2006 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.StringRes;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+
+/**
+ * Interface for managing the items in a menu.
+ * <p>
+ * By default, every Activity supports an options menu of actions or options.
+ * You can add items to this menu and handle clicks on your additions. The
+ * easiest way of adding menu items is inflating an XML file into the
+ * {@link Menu} via {@link MenuInflater}. The easiest way of attaching code to
+ * clicks is via {@link Activity#onOptionsItemSelected(MenuItem)} and
+ * {@link Activity#onContextItemSelected(MenuItem)}.
+ * <p>
+ * Different menu types support different features:
+ * <ol>
+ * <li><b>Context menus</b>: Do not support item shortcuts and item icons.
+ * <li><b>Options menus</b>: The <b>icon menus</b> do not support item check
+ * marks and only show the item's
+ * {@link MenuItem#setTitleCondensed(CharSequence) condensed title}. The
+ * <b>expanded menus</b> (only available if six or more menu items are visible,
+ * reached via the 'More' item in the icon menu) do not show item icons, and
+ * item check marks are discouraged.
+ * <li><b>Sub menus</b>: Do not support item icons, or nested sub menus.
+ * </ol>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+public interface Menu {
+
+ /**
+ * This is the part of an order integer that the user can provide.
+ * @hide
+ */
+ static final int USER_MASK = 0x0000ffff;
+ /**
+ * Bit shift of the user portion of the order integer.
+ * @hide
+ */
+ static final int USER_SHIFT = 0;
+
+ /**
+ * This is the part of an order integer that supplies the category of the
+ * item.
+ * @hide
+ */
+ static final int CATEGORY_MASK = 0xffff0000;
+ /**
+ * Bit shift of the category portion of the order integer.
+ * @hide
+ */
+ static final int CATEGORY_SHIFT = 16;
+
+ /**
+ * A mask of all supported modifiers for MenuItem's keyboard shortcuts
+ */
+ static final int SUPPORTED_MODIFIERS_MASK = KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON
+ | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON
+ | KeyEvent.META_FUNCTION_ON;
+
+ /**
+ * Value to use for group and item identifier integers when you don't care
+ * about them.
+ */
+ static final int NONE = 0;
+
+ /**
+ * First value for group and item identifier integers.
+ */
+ static final int FIRST = 1;
+
+ // Implementation note: Keep these CATEGORY_* in sync with the category enum
+ // in attrs.xml
+
+ /**
+ * Category code for the order integer for items/groups that are part of a
+ * container -- or/add this with your base value.
+ */
+ static final int CATEGORY_CONTAINER = 0x00010000;
+
+ /**
+ * Category code for the order integer for items/groups that are provided by
+ * the system -- or/add this with your base value.
+ */
+ static final int CATEGORY_SYSTEM = 0x00020000;
+
+ /**
+ * Category code for the order integer for items/groups that are
+ * user-supplied secondary (infrequently used) options -- or/add this with
+ * your base value.
+ */
+ static final int CATEGORY_SECONDARY = 0x00030000;
+
+ /**
+ * Category code for the order integer for items/groups that are
+ * alternative actions on the data that is currently displayed -- or/add
+ * this with your base value.
+ */
+ static final int CATEGORY_ALTERNATIVE = 0x00040000;
+
+ /**
+ * Flag for {@link #addIntentOptions}: if set, do not automatically remove
+ * any existing menu items in the same group.
+ */
+ static final int FLAG_APPEND_TO_GROUP = 0x0001;
+
+ /**
+ * Flag for {@link #performShortcut}: if set, do not close the menu after
+ * executing the shortcut.
+ */
+ static final int FLAG_PERFORM_NO_CLOSE = 0x0001;
+
+ /**
+ * Flag for {@link #performShortcut(int, KeyEvent, int)}: if set, always
+ * close the menu after executing the shortcut. Closing the menu also resets
+ * the prepared state.
+ */
+ static final int FLAG_ALWAYS_PERFORM_CLOSE = 0x0002;
+
+ /**
+ * Add a new item to the menu. This item displays the given title for its
+ * label.
+ *
+ * @param title The text to display for the item.
+ * @return The newly added menu item.
+ */
+ public MenuItem add(CharSequence title);
+
+ /**
+ * Add a new item to the menu. This item displays the given title for its
+ * label.
+ *
+ * @param titleRes Resource identifier of title string.
+ * @return The newly added menu item.
+ */
+ public MenuItem add(@StringRes int titleRes);
+
+ /**
+ * Add a new item to the menu. This item displays the given title for its
+ * label.
+ *
+ * @param groupId The group identifier that this item should be part of.
+ * This can be used to define groups of items for batch state
+ * changes. Normally use {@link #NONE} if an item should not be in a
+ * group.
+ * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+ * unique ID.
+ * @param order The order for the item. Use {@link #NONE} if you do not care
+ * about the order. See {@link MenuItem#getOrder()}.
+ * @param title The text to display for the item.
+ * @return The newly added menu item.
+ */
+ public MenuItem add(int groupId, int itemId, int order, CharSequence title);
+
+ /**
+ * Variation on {@link #add(int, int, int, CharSequence)} that takes a
+ * string resource identifier instead of the string itself.
+ *
+ * @param groupId The group identifier that this item should be part of.
+ * This can also be used to define groups of items for batch state
+ * changes. Normally use {@link #NONE} if an item should not be in a
+ * group.
+ * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+ * unique ID.
+ * @param order The order for the item. Use {@link #NONE} if you do not care
+ * about the order. See {@link MenuItem#getOrder()}.
+ * @param titleRes Resource identifier of title string.
+ * @return The newly added menu item.
+ */
+ public MenuItem add(int groupId, int itemId, int order, @StringRes int titleRes);
+
+ /**
+ * Add a new sub-menu to the menu. This item displays the given title for
+ * its label. To modify other attributes on the submenu's menu item, use
+ * {@link SubMenu#getItem()}.
+ *
+ * @param title The text to display for the item.
+ * @return The newly added sub-menu
+ */
+ SubMenu addSubMenu(final CharSequence title);
+
+ /**
+ * Add a new sub-menu to the menu. This item displays the given title for
+ * its label. To modify other attributes on the submenu's menu item, use
+ * {@link SubMenu#getItem()}.
+ *
+ * @param titleRes Resource identifier of title string.
+ * @return The newly added sub-menu
+ */
+ SubMenu addSubMenu(@StringRes final int titleRes);
+
+ /**
+ * Add a new sub-menu to the menu. This item displays the given
+ * <var>title</var> for its label. To modify other attributes on the
+ * submenu's menu item, use {@link SubMenu#getItem()}.
+ *<p>
+ * Note that you can only have one level of sub-menus, i.e. you cannnot add
+ * a subMenu to a subMenu: An {@link UnsupportedOperationException} will be
+ * thrown if you try.
+ *
+ * @param groupId The group identifier that this item should be part of.
+ * This can also be used to define groups of items for batch state
+ * changes. Normally use {@link #NONE} if an item should not be in a
+ * group.
+ * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+ * unique ID.
+ * @param order The order for the item. Use {@link #NONE} if you do not care
+ * about the order. See {@link MenuItem#getOrder()}.
+ * @param title The text to display for the item.
+ * @return The newly added sub-menu
+ */
+ SubMenu addSubMenu(final int groupId, final int itemId, int order, final CharSequence title);
+
+ /**
+ * Variation on {@link #addSubMenu(int, int, int, CharSequence)} that takes
+ * a string resource identifier for the title instead of the string itself.
+ *
+ * @param groupId The group identifier that this item should be part of.
+ * This can also be used to define groups of items for batch state
+ * changes. Normally use {@link #NONE} if an item should not be in a group.
+ * @param itemId Unique item ID. Use {@link #NONE} if you do not need a unique ID.
+ * @param order The order for the item. Use {@link #NONE} if you do not care about the
+ * order. See {@link MenuItem#getOrder()}.
+ * @param titleRes Resource identifier of title string.
+ * @return The newly added sub-menu
+ */
+ SubMenu addSubMenu(int groupId, int itemId, int order, @StringRes int titleRes);
+
+ /**
+ * Add a group of menu items corresponding to actions that can be performed
+ * for a particular Intent. The Intent is most often configured with a null
+ * action, the data that the current activity is working with, and includes
+ * either the {@link Intent#CATEGORY_ALTERNATIVE} or
+ * {@link Intent#CATEGORY_SELECTED_ALTERNATIVE} to find activities that have
+ * said they would like to be included as optional action. You can, however,
+ * use any Intent you want.
+ *
+ * <p>
+ * See {@link android.content.pm.PackageManager#queryIntentActivityOptions}
+ * for more * details on the <var>caller</var>, <var>specifics</var>, and
+ * <var>intent</var> arguments. The list returned by that function is used
+ * to populate the resulting menu items.
+ *
+ * <p>
+ * All of the menu items of possible options for the intent will be added
+ * with the given group and id. You can use the group to control ordering of
+ * the items in relation to other items in the menu. Normally this function
+ * will automatically remove any existing items in the menu in the same
+ * group and place a divider above and below the added items; this behavior
+ * can be modified with the <var>flags</var> parameter. For each of the
+ * generated items {@link MenuItem#setIntent} is called to associate the
+ * appropriate Intent with the item; this means the activity will
+ * automatically be started for you without having to do anything else.
+ *
+ * @param groupId The group identifier that the items should be part of.
+ * This can also be used to define groups of items for batch state
+ * changes. Normally use {@link #NONE} if the items should not be in
+ * a group.
+ * @param itemId Unique item ID. Use {@link #NONE} if you do not need a
+ * unique ID.
+ * @param order The order for the items. Use {@link #NONE} if you do not
+ * care about the order. See {@link MenuItem#getOrder()}.
+ * @param caller The current activity component name as defined by
+ * queryIntentActivityOptions().
+ * @param specifics Specific items to place first as defined by
+ * queryIntentActivityOptions().
+ * @param intent Intent describing the kinds of items to populate in the
+ * list as defined by queryIntentActivityOptions().
+ * @param flags Additional options controlling how the items are added.
+ * @param outSpecificItems Optional array in which to place the menu items
+ * that were generated for each of the <var>specifics</var> that were
+ * requested. Entries may be null if no activity was found for that
+ * specific action.
+ * @return The number of menu items that were added.
+ *
+ * @see #FLAG_APPEND_TO_GROUP
+ * @see MenuItem#setIntent
+ * @see android.content.pm.PackageManager#queryIntentActivityOptions
+ */
+ public int addIntentOptions(int groupId, int itemId, int order,
+ ComponentName caller, Intent[] specifics,
+ Intent intent, int flags, MenuItem[] outSpecificItems);
+
+ /**
+ * Remove the item with the given identifier.
+ *
+ * @param id The item to be removed. If there is no item with this
+ * identifier, nothing happens.
+ */
+ public void removeItem(int id);
+
+ /**
+ * Remove all items in the given group.
+ *
+ * @param groupId The group to be removed. If there are no items in this
+ * group, nothing happens.
+ */
+ public void removeGroup(int groupId);
+
+ /**
+ * Remove all existing items from the menu, leaving it empty as if it had
+ * just been created.
+ */
+ public void clear();
+
+ /**
+ * Control whether a particular group of items can show a check mark. This
+ * is similar to calling {@link MenuItem#setCheckable} on all of the menu items
+ * with the given group identifier, but in addition you can control whether
+ * this group contains a mutually-exclusive set items. This should be called
+ * after the items of the group have been added to the menu.
+ *
+ * @param group The group of items to operate on.
+ * @param checkable Set to true to allow a check mark, false to
+ * disallow. The default is false.
+ * @param exclusive If set to true, only one item in this group can be
+ * checked at a time; checking an item will automatically
+ * uncheck all others in the group. If set to false, each
+ * item can be checked independently of the others.
+ *
+ * @see MenuItem#setCheckable
+ * @see MenuItem#setChecked
+ */
+ public void setGroupCheckable(int group, boolean checkable, boolean exclusive);
+
+ /**
+ * Show or hide all menu items that are in the given group.
+ *
+ * @param group The group of items to operate on.
+ * @param visible If true the items are visible, else they are hidden.
+ *
+ * @see MenuItem#setVisible
+ */
+ public void setGroupVisible(int group, boolean visible);
+
+ /**
+ * Enable or disable all menu items that are in the given group.
+ *
+ * @param group The group of items to operate on.
+ * @param enabled If true the items will be enabled, else they will be disabled.
+ *
+ * @see MenuItem#setEnabled
+ */
+ public void setGroupEnabled(int group, boolean enabled);
+
+ /**
+ * Return whether the menu currently has item items that are visible.
+ *
+ * @return True if there is one or more item visible,
+ * else false.
+ */
+ public boolean hasVisibleItems();
+
+ /**
+ * Return the menu item with a particular identifier.
+ *
+ * @param id The identifier to find.
+ *
+ * @return The menu item object, or null if there is no item with
+ * this identifier.
+ */
+ public MenuItem findItem(int id);
+
+ /**
+ * Get the number of items in the menu. Note that this will change any
+ * times items are added or removed from the menu.
+ *
+ * @return The item count.
+ */
+ public int size();
+
+ /**
+ * Gets the menu item at the given index.
+ *
+ * @param index The index of the menu item to return.
+ * @return The menu item.
+ * @exception IndexOutOfBoundsException
+ * when {@code index < 0 || >= size()}
+ */
+ public MenuItem getItem(int index);
+
+ /**
+ * Closes the menu, if open.
+ */
+ public void close();
+
+ /**
+ * Execute the menu item action associated with the given shortcut
+ * character.
+ *
+ * @param keyCode The keycode of the shortcut key.
+ * @param event Key event message.
+ * @param flags Additional option flags or 0.
+ *
+ * @return If the given shortcut exists and is shown, returns
+ * true; else returns false.
+ *
+ * @see #FLAG_PERFORM_NO_CLOSE
+ */
+ public boolean performShortcut(int keyCode, KeyEvent event, int flags);
+
+ /**
+ * Is a keypress one of the defined shortcut keys for this window.
+ * @param keyCode the key code from {@link KeyEvent} to check.
+ * @param event the {@link KeyEvent} to use to help check.
+ */
+ boolean isShortcutKey(int keyCode, KeyEvent event);
+
+ /**
+ * Execute the menu item action associated with the given menu identifier.
+ *
+ * @param id Identifier associated with the menu item.
+ * @param flags Additional option flags or 0.
+ *
+ * @return If the given identifier exists and is shown, returns
+ * true; else returns false.
+ *
+ * @see #FLAG_PERFORM_NO_CLOSE
+ */
+ public boolean performIdentifierAction(int id, int flags);
+
+
+ /**
+ * Control whether the menu should be running in qwerty mode (alphabetic
+ * shortcuts) or 12-key mode (numeric shortcuts).
+ *
+ * @param isQwerty If true the menu will use alphabetic shortcuts; else it
+ * will use numeric shortcuts.
+ */
+ public void setQwertyMode(boolean isQwerty);
+
+ /**
+ * Enable or disable the group dividers.
+ */
+ default void setGroupDividerEnabled(boolean groupDividerEnabled) {
+ }
+} \ No newline at end of file
diff --git a/android/view/MenuInflater.java b/android/view/MenuInflater.java
new file mode 100644
index 00000000..a3cd0ba4
--- /dev/null
+++ b/android/view/MenuInflater.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2006 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.MenuRes;
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.view.menu.MenuItemImpl;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * This class is used to instantiate menu XML files into Menu objects.
+ * <p>
+ * For performance reasons, menu inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ */
+public class MenuInflater {
+ private static final String LOG_TAG = "MenuInflater";
+
+ /** Menu tag name in XML. */
+ private static final String XML_MENU = "menu";
+
+ /** Group tag name in XML. */
+ private static final String XML_GROUP = "group";
+
+ /** Item tag name in XML. */
+ private static final String XML_ITEM = "item";
+
+ private static final int NO_ID = 0;
+
+ private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};
+
+ private static final Class<?>[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE = ACTION_VIEW_CONSTRUCTOR_SIGNATURE;
+
+ private final Object[] mActionViewConstructorArguments;
+
+ private final Object[] mActionProviderConstructorArguments;
+
+ private Context mContext;
+ private Object mRealOwner;
+
+ /**
+ * Constructs a menu inflater.
+ *
+ * @see Activity#getMenuInflater()
+ */
+ public MenuInflater(Context context) {
+ mContext = context;
+ mActionViewConstructorArguments = new Object[] {context};
+ mActionProviderConstructorArguments = mActionViewConstructorArguments;
+ }
+
+ /**
+ * Constructs a menu inflater.
+ *
+ * @see Activity#getMenuInflater()
+ * @hide
+ */
+ public MenuInflater(Context context, Object realOwner) {
+ mContext = context;
+ mRealOwner = realOwner;
+ mActionViewConstructorArguments = new Object[] {context};
+ mActionProviderConstructorArguments = mActionViewConstructorArguments;
+ }
+
+ /**
+ * Inflate a menu hierarchy from the specified XML resource. Throws
+ * {@link InflateException} if there is an error.
+ *
+ * @param menuRes Resource ID for an XML layout resource to load (e.g.,
+ * <code>R.menu.main_activity</code>)
+ * @param menu The Menu to inflate into. The items and submenus will be
+ * added to this Menu.
+ */
+ public void inflate(@MenuRes int menuRes, Menu menu) {
+ XmlResourceParser parser = null;
+ try {
+ parser = mContext.getResources().getLayout(menuRes);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ parseMenu(parser, attrs, menu);
+ } catch (XmlPullParserException e) {
+ throw new InflateException("Error inflating menu XML", e);
+ } catch (IOException e) {
+ throw new InflateException("Error inflating menu XML", e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ /**
+ * Called internally to fill the given menu. If a sub menu is seen, it will
+ * call this recursively.
+ */
+ private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
+ throws XmlPullParserException, IOException {
+ MenuState menuState = new MenuState(menu);
+
+ int eventType = parser.getEventType();
+ String tagName;
+ boolean lookingForEndOfUnknownTag = false;
+ String unknownTagName = null;
+
+ // This loop will skip to the menu start tag
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (tagName.equals(XML_MENU)) {
+ // Go to next tag
+ eventType = parser.next();
+ break;
+ }
+
+ throw new RuntimeException("Expecting menu, got " + tagName);
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ boolean reachedEndOfMenu = false;
+ while (!reachedEndOfMenu) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ if (lookingForEndOfUnknownTag) {
+ break;
+ }
+
+ tagName = parser.getName();
+ if (tagName.equals(XML_GROUP)) {
+ menuState.readGroup(attrs);
+ } else if (tagName.equals(XML_ITEM)) {
+ menuState.readItem(attrs);
+ } else if (tagName.equals(XML_MENU)) {
+ // A menu start tag denotes a submenu for an item
+ SubMenu subMenu = menuState.addSubMenuItem();
+ registerMenu(subMenu, attrs);
+
+ // Parse the submenu into returned SubMenu
+ parseMenu(parser, attrs, subMenu);
+ } else {
+ lookingForEndOfUnknownTag = true;
+ unknownTagName = tagName;
+ }
+ break;
+
+ case XmlPullParser.END_TAG:
+ tagName = parser.getName();
+ if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
+ lookingForEndOfUnknownTag = false;
+ unknownTagName = null;
+ } else if (tagName.equals(XML_GROUP)) {
+ menuState.resetGroup();
+ } else if (tagName.equals(XML_ITEM)) {
+ // Add the item if it hasn't been added (if the item was
+ // a submenu, it would have been added already)
+ if (!menuState.hasAddedItem()) {
+ if (menuState.itemActionProvider != null &&
+ menuState.itemActionProvider.hasSubMenu()) {
+ registerMenu(menuState.addSubMenuItem(), attrs);
+ } else {
+ registerMenu(menuState.addItem(), attrs);
+ }
+ }
+ } else if (tagName.equals(XML_MENU)) {
+ reachedEndOfMenu = true;
+ }
+ break;
+
+ case XmlPullParser.END_DOCUMENT:
+ throw new RuntimeException("Unexpected end of document");
+ }
+
+ eventType = parser.next();
+ }
+ }
+
+ /**
+ * The method is a hook for layoutlib to do its magic.
+ * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+ * appears to do nothing.
+ */
+ private void registerMenu(@SuppressWarnings("unused") MenuItem item,
+ @SuppressWarnings("unused") AttributeSet set) {
+ }
+
+ /**
+ * The method is a hook for layoutlib to do its magic.
+ * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+ * appears to do nothing.
+ */
+ private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu,
+ @SuppressWarnings("unused") AttributeSet set) {
+ }
+
+ // Needed by layoutlib.
+ /*package*/ Context getContext() {
+ return mContext;
+ }
+
+ private static class InflatedOnMenuItemClickListener
+ implements MenuItem.OnMenuItemClickListener {
+ private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
+
+ private Object mRealOwner;
+ private Method mMethod;
+
+ public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
+ mRealOwner = realOwner;
+ Class<?> c = realOwner.getClass();
+ try {
+ mMethod = c.getMethod(methodName, PARAM_TYPES);
+ } catch (Exception e) {
+ InflateException ex = new InflateException(
+ "Couldn't resolve menu item onClick handler " + methodName +
+ " in class " + c.getName());
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ public boolean onMenuItemClick(MenuItem item) {
+ try {
+ if (mMethod.getReturnType() == Boolean.TYPE) {
+ return (Boolean) mMethod.invoke(mRealOwner, item);
+ } else {
+ mMethod.invoke(mRealOwner, item);
+ return true;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private Object getRealOwner() {
+ if (mRealOwner == null) {
+ mRealOwner = findRealOwner(mContext);
+ }
+ return mRealOwner;
+ }
+
+ private Object findRealOwner(Object owner) {
+ if (owner instanceof Activity) {
+ return owner;
+ }
+ if (owner instanceof ContextWrapper) {
+ return findRealOwner(((ContextWrapper) owner).getBaseContext());
+ }
+ return owner;
+ }
+
+ /**
+ * State for the current menu.
+ * <p>
+ * Groups can not be nested unless there is another menu (which will have
+ * its state class).
+ */
+ private class MenuState {
+ private Menu menu;
+
+ /*
+ * Group state is set on items as they are added, allowing an item to
+ * override its group state. (As opposed to set on items at the group end tag.)
+ */
+ private int groupId;
+ private int groupCategory;
+ private int groupOrder;
+ private int groupCheckable;
+ private boolean groupVisible;
+ private boolean groupEnabled;
+
+ private boolean itemAdded;
+ private int itemId;
+ private int itemCategoryOrder;
+ private CharSequence itemTitle;
+ private CharSequence itemTitleCondensed;
+ private int itemIconResId;
+ private ColorStateList itemIconTintList = null;
+ private PorterDuff.Mode itemIconTintMode = null;
+ private char itemAlphabeticShortcut;
+ private int itemAlphabeticModifiers;
+ private char itemNumericShortcut;
+ private int itemNumericModifiers;
+ /**
+ * Sync to attrs.xml enum:
+ * - 0: none
+ * - 1: all
+ * - 2: exclusive
+ */
+ private int itemCheckable;
+ private boolean itemChecked;
+ private boolean itemVisible;
+ private boolean itemEnabled;
+
+ /**
+ * Sync to attrs.xml enum, values in MenuItem:
+ * - 0: never
+ * - 1: ifRoom
+ * - 2: always
+ * - -1: Safe sentinel for "no value".
+ */
+ private int itemShowAsAction;
+
+ private int itemActionViewLayout;
+ private String itemActionViewClassName;
+ private String itemActionProviderClassName;
+
+ private String itemListenerMethodName;
+
+ private ActionProvider itemActionProvider;
+
+ private CharSequence itemContentDescription;
+ private CharSequence itemTooltipText;
+
+ private static final int defaultGroupId = NO_ID;
+ private static final int defaultItemId = NO_ID;
+ private static final int defaultItemCategory = 0;
+ private static final int defaultItemOrder = 0;
+ private static final int defaultItemCheckable = 0;
+ private static final boolean defaultItemChecked = false;
+ private static final boolean defaultItemVisible = true;
+ private static final boolean defaultItemEnabled = true;
+
+ public MenuState(final Menu menu) {
+ this.menu = menu;
+
+ resetGroup();
+ }
+
+ public void resetGroup() {
+ groupId = defaultGroupId;
+ groupCategory = defaultItemCategory;
+ groupOrder = defaultItemOrder;
+ groupCheckable = defaultItemCheckable;
+ groupVisible = defaultItemVisible;
+ groupEnabled = defaultItemEnabled;
+ }
+
+ /**
+ * Called when the parser is pointing to a group tag.
+ */
+ public void readGroup(AttributeSet attrs) {
+ TypedArray a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.MenuGroup);
+
+ groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
+ groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
+ groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
+ groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
+ groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
+ groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
+
+ a.recycle();
+ }
+
+ /**
+ * Called when the parser is pointing to an item tag.
+ */
+ public void readItem(AttributeSet attrs) {
+ TypedArray a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.MenuItem);
+
+ // Inherit attributes from the group as default value
+ itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
+ final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);
+ final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);
+ itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
+ itemTitle = a.getText(com.android.internal.R.styleable.MenuItem_title);
+ itemTitleCondensed = a.getText(com.android.internal.R.styleable.MenuItem_titleCondensed);
+ itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
+ if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTintMode)) {
+ itemIconTintMode = Drawable.parseTintMode(a.getInt(
+ com.android.internal.R.styleable.MenuItem_iconTintMode, -1),
+ itemIconTintMode);
+ } else {
+ // Reset to null so that it's not carried over to the next item
+ itemIconTintMode = null;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTint)) {
+ itemIconTintList = a.getColorStateList(
+ com.android.internal.R.styleable.MenuItem_iconTint);
+ } else {
+ // Reset to null so that it's not carried over to the next item
+ itemIconTintList = null;
+ }
+
+ itemAlphabeticShortcut =
+ getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
+ itemAlphabeticModifiers =
+ a.getInt(com.android.internal.R.styleable.MenuItem_alphabeticModifiers,
+ KeyEvent.META_CTRL_ON);
+ itemNumericShortcut =
+ getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));
+ itemNumericModifiers =
+ a.getInt(com.android.internal.R.styleable.MenuItem_numericModifiers,
+ KeyEvent.META_CTRL_ON);
+ if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
+ // Item has attribute checkable, use it
+ itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
+ } else {
+ // Item does not have attribute, use the group's (group can have one more state
+ // for checkable that represents the exclusive checkable)
+ itemCheckable = groupCheckable;
+ }
+ itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
+ itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
+ itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
+ itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, -1);
+ itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick);
+ itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0);
+ itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass);
+ itemActionProviderClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionProviderClass);
+
+ final boolean hasActionProvider = itemActionProviderClassName != null;
+ if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) {
+ itemActionProvider = newInstance(itemActionProviderClassName,
+ ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE,
+ mActionProviderConstructorArguments);
+ } else {
+ if (hasActionProvider) {
+ Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'."
+ + " Action view already specified.");
+ }
+ itemActionProvider = null;
+ }
+
+ itemContentDescription =
+ a.getText(com.android.internal.R.styleable.MenuItem_contentDescription);
+ itemTooltipText = a.getText(com.android.internal.R.styleable.MenuItem_tooltipText);
+
+ a.recycle();
+
+ itemAdded = false;
+ }
+
+ private char getShortcut(String shortcutString) {
+ if (shortcutString == null) {
+ return 0;
+ } else {
+ return shortcutString.charAt(0);
+ }
+ }
+
+ private void setItem(MenuItem item) {
+ item.setChecked(itemChecked)
+ .setVisible(itemVisible)
+ .setEnabled(itemEnabled)
+ .setCheckable(itemCheckable >= 1)
+ .setTitleCondensed(itemTitleCondensed)
+ .setIcon(itemIconResId)
+ .setAlphabeticShortcut(itemAlphabeticShortcut, itemAlphabeticModifiers)
+ .setNumericShortcut(itemNumericShortcut, itemNumericModifiers);
+
+ if (itemShowAsAction >= 0) {
+ item.setShowAsAction(itemShowAsAction);
+ }
+
+ if (itemIconTintMode != null) {
+ item.setIconTintMode(itemIconTintMode);
+ }
+
+ if (itemIconTintList != null) {
+ item.setIconTintList(itemIconTintList);
+ }
+
+ if (itemListenerMethodName != null) {
+ if (mContext.isRestricted()) {
+ throw new IllegalStateException("The android:onClick attribute cannot "
+ + "be used within a restricted context");
+ }
+ item.setOnMenuItemClickListener(
+ new InflatedOnMenuItemClickListener(getRealOwner(), itemListenerMethodName));
+ }
+
+ if (item instanceof MenuItemImpl) {
+ MenuItemImpl impl = (MenuItemImpl) item;
+ if (itemCheckable >= 2) {
+ impl.setExclusiveCheckable(true);
+ }
+ }
+
+ boolean actionViewSpecified = false;
+ if (itemActionViewClassName != null) {
+ View actionView = (View) newInstance(itemActionViewClassName,
+ ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments);
+ item.setActionView(actionView);
+ actionViewSpecified = true;
+ }
+ if (itemActionViewLayout > 0) {
+ if (!actionViewSpecified) {
+ item.setActionView(itemActionViewLayout);
+ actionViewSpecified = true;
+ } else {
+ Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'."
+ + " Action view already specified.");
+ }
+ }
+ if (itemActionProvider != null) {
+ item.setActionProvider(itemActionProvider);
+ }
+
+ item.setContentDescription(itemContentDescription);
+ item.setTooltipText(itemTooltipText);
+ }
+
+ public MenuItem addItem() {
+ itemAdded = true;
+ MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle);
+ setItem(item);
+ return item;
+ }
+
+ public SubMenu addSubMenuItem() {
+ itemAdded = true;
+ SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
+ setItem(subMenu.getItem());
+ return subMenu;
+ }
+
+ public boolean hasAddedItem() {
+ return itemAdded;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T newInstance(String className, Class<?>[] constructorSignature,
+ Object[] arguments) {
+ try {
+ Class<?> clazz = mContext.getClassLoader().loadClass(className);
+ Constructor<?> constructor = clazz.getConstructor(constructorSignature);
+ constructor.setAccessible(true);
+ return (T) constructor.newInstance(arguments);
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);
+ }
+ return null;
+ }
+ }
+}
diff --git a/android/view/MenuInflater_Delegate.java b/android/view/MenuInflater_Delegate.java
new file mode 100644
index 00000000..08a97d64
--- /dev/null
+++ b/android/view/MenuInflater_Delegate.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 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.content.Context;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.MenuView;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.AttributeSet;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuInflater}
+ * <p/>
+ * Through the layoutlib_create tool, the original methods of MenuInflater have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p/>
+ * The main purpose of the class is to get the view key from the menu xml parser and add it to
+ * the menu item. The view key is used by the IDE to match the individual view elements to the
+ * corresponding xml tag in the menu/layout file.
+ * <p/>
+ * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the
+ * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link
+ * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
+ */
+public class MenuInflater_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
+ AttributeSet attrs) {
+ if (menuItem instanceof BridgeMenuItemImpl) {
+ Context context = thisInflater.getContext();
+ context = BridgeContext.getBaseContext(context);
+ if (context instanceof BridgeContext) {
+ Object viewKey = BridgeInflater.getViewKeyFromParser(
+ attrs, ((BridgeContext) context), null, false);
+ ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey);
+ return;
+ }
+ }
+ // This means that Bridge did not take over the instantiation of some object properly.
+ // This is most likely a bug in the LayoutLib code.
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Action Bar Menu rendering may be incorrect.", null);
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void registerMenu(MenuInflater thisInflater, SubMenu subMenu,
+ AttributeSet parser) {
+ registerMenu(thisInflater, subMenu.getItem(), parser);
+ }
+
+}
diff --git a/android/view/MenuItem.java b/android/view/MenuItem.java
new file mode 100644
index 00000000..88b9c0d3
--- /dev/null
+++ b/android/view/MenuItem.java
@@ -0,0 +1,817 @@
+/*
+ * Copyright (C) 2008 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.DrawableRes;
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+
+/**
+ * Interface for direct access to a previously created menu item.
+ * <p>
+ * An Item is returned by calling one of the {@link android.view.Menu#add}
+ * methods.
+ * <p>
+ * For a feature set of specific menu types, see {@link Menu}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+public interface MenuItem {
+ /*
+ * These should be kept in sync with attrs.xml enum constants for showAsAction
+ */
+ /** Never show this item as a button in an Action Bar. */
+ public static final int SHOW_AS_ACTION_NEVER = 0;
+ /** Show this item as a button in an Action Bar if the system decides there is room for it. */
+ public static final int SHOW_AS_ACTION_IF_ROOM = 1;
+ /**
+ * Always show this item as a button in an Action Bar.
+ * Use sparingly! If too many items are set to always show in the Action Bar it can
+ * crowd the Action Bar and degrade the user experience on devices with smaller screens.
+ * A good rule of thumb is to have no more than 2 items set to always show at a time.
+ */
+ public static final int SHOW_AS_ACTION_ALWAYS = 2;
+
+ /**
+ * When this item is in the action bar, always show it with a text label even if
+ * it also has an icon specified.
+ */
+ public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
+
+ /**
+ * This item's action view collapses to a normal menu item.
+ * When expanded, the action view temporarily takes over
+ * a larger segment of its container.
+ */
+ public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
+
+ /**
+ * @hide
+ */
+ int SHOW_AS_OVERFLOW_ALWAYS = 1 << 31;
+
+ /**
+ * Interface definition for a callback to be invoked when a menu item is
+ * clicked.
+ *
+ * @see Activity#onContextItemSelected(MenuItem)
+ * @see Activity#onOptionsItemSelected(MenuItem)
+ */
+ public interface OnMenuItemClickListener {
+ /**
+ * Called when a menu item has been invoked. This is the first code
+ * that is executed; if it returns true, no other callbacks will be
+ * executed.
+ *
+ * @param item The menu item that was invoked.
+ *
+ * @return Return true to consume this click and prevent others from
+ * executing.
+ */
+ public boolean onMenuItemClick(MenuItem item);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a menu item
+ * marked with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is
+ * expanded or collapsed.
+ *
+ * @see MenuItem#expandActionView()
+ * @see MenuItem#collapseActionView()
+ * @see MenuItem#setShowAsActionFlags(int)
+ */
+ public interface OnActionExpandListener {
+ /**
+ * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+ * is expanded.
+ * @param item Item that was expanded
+ * @return true if the item should expand, false if expansion should be suppressed.
+ */
+ public boolean onMenuItemActionExpand(MenuItem item);
+
+ /**
+ * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
+ * is collapsed.
+ * @param item Item that was collapsed
+ * @return true if the item should collapse, false if collapsing should be suppressed.
+ */
+ public boolean onMenuItemActionCollapse(MenuItem item);
+ }
+
+ /**
+ * Return the identifier for this menu item. The identifier can not
+ * be changed after the menu is created.
+ *
+ * @return The menu item's identifier.
+ */
+ public int getItemId();
+
+ /**
+ * Return the group identifier that this menu item is part of. The group
+ * identifier can not be changed after the menu is created.
+ *
+ * @return The menu item's group identifier.
+ */
+ public int getGroupId();
+
+ /**
+ * Return the category and order within the category of this item. This
+ * item will be shown before all items (within its category) that have
+ * order greater than this value.
+ * <p>
+ * An order integer contains the item's category (the upper bits of the
+ * integer; set by or/add the category with the order within the
+ * category) and the ordering of the item within that category (the
+ * lower bits). Example categories are {@link Menu#CATEGORY_SYSTEM},
+ * {@link Menu#CATEGORY_SECONDARY}, {@link Menu#CATEGORY_ALTERNATIVE},
+ * {@link Menu#CATEGORY_CONTAINER}. See {@link Menu} for a full list.
+ *
+ * @return The order of this item.
+ */
+ public int getOrder();
+
+ /**
+ * Change the title associated with this item.
+ *
+ * @param title The new text to be displayed.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setTitle(CharSequence title);
+
+ /**
+ * Change the title associated with this item.
+ * <p>
+ * Some menu types do not sufficient space to show the full title, and
+ * instead a condensed title is preferred. See {@link Menu} for more
+ * information.
+ *
+ * @param title The resource id of the new text to be displayed.
+ * @return This Item so additional setters can be called.
+ * @see #setTitleCondensed(CharSequence)
+ */
+
+ public MenuItem setTitle(@StringRes int title);
+
+ /**
+ * Retrieve the current title of the item.
+ *
+ * @return The title.
+ */
+ public CharSequence getTitle();
+
+ /**
+ * Change the condensed title associated with this item. The condensed
+ * title is used in situations where the normal title may be too long to
+ * be displayed.
+ *
+ * @param title The new text to be displayed as the condensed title.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setTitleCondensed(CharSequence title);
+
+ /**
+ * Retrieve the current condensed title of the item. If a condensed
+ * title was never set, it will return the normal title.
+ *
+ * @return The condensed title, if it exists.
+ * Otherwise the normal title.
+ */
+ public CharSequence getTitleCondensed();
+
+ /**
+ * Change the icon associated with this item. This icon will not always be
+ * shown, so the title should be sufficient in describing this item. See
+ * {@link Menu} for the menu types that support icons.
+ *
+ * @param icon The new icon (as a Drawable) to be displayed.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setIcon(Drawable icon);
+
+ /**
+ * Change the icon associated with this item. This icon will not always be
+ * shown, so the title should be sufficient in describing this item. See
+ * {@link Menu} for the menu types that support icons.
+ * <p>
+ * This method will set the resource ID of the icon which will be used to
+ * lazily get the Drawable when this item is being shown.
+ *
+ * @param iconRes The new icon (as a resource ID) to be displayed.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setIcon(@DrawableRes int iconRes);
+
+ /**
+ * Returns the icon for this item as a Drawable (getting it from resources if it hasn't been
+ * loaded before). Note that if you call {@link #setIconTintList(ColorStateList)} or
+ * {@link #setIconTintMode(PorterDuff.Mode)} on this item, and you use a custom menu presenter
+ * in your application, you have to apply the tinting explicitly on the {@link Drawable}
+ * returned by this method.
+ *
+ * @return The icon as a Drawable.
+ */
+ public Drawable getIcon();
+
+ /**
+ * Applies a tint to this item's icon. Does not modify the
+ * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setIcon(Drawable)} or {@link #setIcon(int)} will
+ * automatically mutate the icon and apply the specified tint and
+ * tint mode using
+ * {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#MenuItem_iconTint
+ * @see #getIconTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public default MenuItem setIconTintList(@Nullable ColorStateList tint) { return this; }
+
+ /**
+ * @return the tint applied to this item's icon
+ * @attr ref android.R.styleable#MenuItem_iconTint
+ * @see #setIconTintList(ColorStateList)
+ */
+ @Nullable
+ public default ColorStateList getIconTintList() { return null; }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setIconTintList(ColorStateList)} to this item's icon. The default mode is
+ * {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#MenuItem_iconTintMode
+ * @see #setIconTintList(ColorStateList)
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public default MenuItem setIconTintMode(@Nullable PorterDuff.Mode tintMode) { return this; }
+
+ /**
+ * Returns the blending mode used to apply the tint to this item's icon, if specified.
+ *
+ * @return the blending mode used to apply the tint to this item's icon
+ * @attr ref android.R.styleable#MenuItem_iconTintMode
+ * @see #setIconTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public default PorterDuff.Mode getIconTintMode() { return null; }
+
+ /**
+ * Change the Intent associated with this item. By default there is no
+ * Intent associated with a menu item. If you set one, and nothing
+ * else handles the item, then the default behavior will be to call
+ * {@link android.content.Context#startActivity} with the given Intent.
+ *
+ * <p>Note that setIntent() can not be used with the versions of
+ * {@link Menu#add} that take a Runnable, because {@link Runnable#run}
+ * does not return a value so there is no way to tell if it handled the
+ * item. In this case it is assumed that the Runnable always handles
+ * the item, and the intent will never be started.
+ *
+ * @see #getIntent
+ * @param intent The Intent to associated with the item. This Intent
+ * object is <em>not</em> copied, so be careful not to
+ * modify it later.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setIntent(Intent intent);
+
+ /**
+ * Return the Intent associated with this item. This returns a
+ * reference to the Intent which you can change as desired to modify
+ * what the Item is holding.
+ *
+ * @see #setIntent
+ * @return Returns the last value supplied to {@link #setIntent}, or
+ * null.
+ */
+ public Intent getIntent();
+
+ /**
+ * Change both the numeric and alphabetic shortcut associated with this
+ * item. Note that the shortcut will be triggered when the key that
+ * generates the given character is pressed along with the corresponding
+ * modifier key. The default modifier is {@link KeyEvent#META_CTRL_ON} in
+ * case nothing is specified. Also note that case is not significant and
+ * that alphabetic shortcut characters will be handled in lower case.
+ * <p>
+ * See {@link Menu} for the menu types that support shortcuts.
+ *
+ * @param numericChar The numeric shortcut key. This is the shortcut when
+ * using a numeric (e.g., 12-key) keyboard.
+ * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+ * using a keyboard with alphabetic keys.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setShortcut(char numericChar, char alphaChar);
+
+ /**
+ * Change both the numeric and alphabetic shortcut associated with this
+ * item. Note that the shortcut will be triggered when the key that
+ * generates the given character is pressed along with the corresponding
+ * modifier key. Also note that case is not significant and that alphabetic
+ * shortcut characters will be handled in lower case.
+ * <p>
+ * See {@link Menu} for the menu types that support shortcuts.
+ *
+ * @param numericChar The numeric shortcut key. This is the shortcut when
+ * using a numeric (e.g., 12-key) keyboard.
+ * @param numericModifiers The numeric modifier associated with the shortcut. It should
+ * be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+ * {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+ * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+ * using a keyboard with alphabetic keys.
+ * @param alphaModifiers The alphabetic modifier associated with the shortcut. It should
+ * be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+ * {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+ * @return This Item so additional setters can be called.
+ */
+ default public MenuItem setShortcut(char numericChar, char alphaChar, int numericModifiers,
+ int alphaModifiers) {
+ if ((alphaModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON
+ && (numericModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON) {
+ return setShortcut(numericChar, alphaChar);
+ } else {
+ return this;
+ }
+ }
+
+ /**
+ * Change the numeric shortcut associated with this item.
+ * <p>
+ * See {@link Menu} for the menu types that support shortcuts.
+ *
+ * @param numericChar The numeric shortcut key. This is the shortcut when
+ * using a 12-key (numeric) keyboard.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setNumericShortcut(char numericChar);
+
+ /**
+ * Change the numeric shortcut and modifiers associated with this item.
+ * <p>
+ * See {@link Menu} for the menu types that support shortcuts.
+ *
+ * @param numericChar The numeric shortcut key. This is the shortcut when
+ * using a 12-key (numeric) keyboard.
+ * @param numericModifiers The modifier associated with the shortcut. It should
+ * be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+ * {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+ * @return This Item so additional setters can be called.
+ */
+ default public MenuItem setNumericShortcut(char numericChar, int numericModifiers) {
+ if ((numericModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON) {
+ return setNumericShortcut(numericChar);
+ } else {
+ return this;
+ }
+ }
+
+ /**
+ * Return the char for this menu item's numeric (12-key) shortcut.
+ *
+ * @return Numeric character to use as a shortcut.
+ */
+ public char getNumericShortcut();
+
+ /**
+ * Return the modifiers for this menu item's numeric (12-key) shortcut.
+ * The modifier is a combination of {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_ALT_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_SYM_ON},
+ * {@link KeyEvent#META_FUNCTION_ON}.
+ * For example, {@link KeyEvent#META_FUNCTION_ON}|{@link KeyEvent#META_CTRL_ON}
+ *
+ * @return Modifier associated with the numeric shortcut.
+ */
+ default public int getNumericModifiers() {
+ return KeyEvent.META_CTRL_ON;
+ }
+
+ /**
+ * Change the alphabetic shortcut associated with this item. The shortcut
+ * will be triggered when the key that generates the given character is
+ * pressed along with the corresponding modifier key. The default modifier
+ * is {@link KeyEvent#META_CTRL_ON} in case nothing is specified. Case is
+ * not significant and shortcut characters will be displayed in lower case.
+ * Note that menu items with the characters '\b' or '\n' as shortcuts will
+ * get triggered by the Delete key or Carriage Return key, respectively.
+ * <p>
+ * See {@link Menu} for the menu types that support shortcuts.
+ *
+ * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+ * using a keyboard with alphabetic keys.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setAlphabeticShortcut(char alphaChar);
+
+ /**
+ * Change the alphabetic shortcut associated with this item. The shortcut
+ * will be triggered when the key that generates the given character is
+ * pressed along with the modifier keys. Case is not significant and shortcut
+ * characters will be displayed in lower case. Note that menu items with
+ * the characters '\b' or '\n' as shortcuts will get triggered by the
+ * Delete key or Carriage Return key, respectively.
+ * <p>
+ * See {@link Menu} for the menu types that support shortcuts.
+ *
+ * @param alphaChar The alphabetic shortcut key. This is the shortcut when
+ * using a keyboard with alphabetic keys.
+ * @param alphaModifiers The modifier associated with the shortcut. It should
+ * be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
+ * {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
+ * @return This Item so additional setters can be called.
+ */
+ default public MenuItem setAlphabeticShortcut(char alphaChar, int alphaModifiers) {
+ if ((alphaModifiers & Menu.SUPPORTED_MODIFIERS_MASK) == KeyEvent.META_CTRL_ON) {
+ return setAlphabeticShortcut(alphaChar);
+ } else {
+ return this;
+ }
+ }
+
+ /**
+ * Return the char for this menu item's alphabetic shortcut.
+ *
+ * @return Alphabetic character to use as a shortcut.
+ */
+ public char getAlphabeticShortcut();
+
+ /**
+ * Return the modifier for this menu item's alphabetic shortcut.
+ * The modifier is a combination of {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_ALT_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_SYM_ON},
+ * {@link KeyEvent#META_FUNCTION_ON}.
+ * For example, {@link KeyEvent#META_FUNCTION_ON}|{@link KeyEvent#META_CTRL_ON}
+ *
+ * @return Modifier associated with the keyboard shortcut.
+ */
+ default public int getAlphabeticModifiers() {
+ return KeyEvent.META_CTRL_ON;
+ }
+
+ /**
+ * Control whether this item can display a check mark. Setting this does
+ * not actually display a check mark (see {@link #setChecked} for that);
+ * rather, it ensures there is room in the item in which to display a
+ * check mark.
+ * <p>
+ * See {@link Menu} for the menu types that support check marks.
+ *
+ * @param checkable Set to true to allow a check mark, false to
+ * disallow. The default is false.
+ * @see #setChecked
+ * @see #isCheckable
+ * @see Menu#setGroupCheckable
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setCheckable(boolean checkable);
+
+ /**
+ * Return whether the item can currently display a check mark.
+ *
+ * @return If a check mark can be displayed, returns true.
+ *
+ * @see #setCheckable
+ */
+ public boolean isCheckable();
+
+ /**
+ * Control whether this item is shown with a check mark. Note that you
+ * must first have enabled checking with {@link #setCheckable} or else
+ * the check mark will not appear. If this item is a member of a group that contains
+ * mutually-exclusive items (set via {@link Menu#setGroupCheckable(int, boolean, boolean)},
+ * the other items in the group will be unchecked.
+ * <p>
+ * See {@link Menu} for the menu types that support check marks.
+ *
+ * @see #setCheckable
+ * @see #isChecked
+ * @see Menu#setGroupCheckable
+ * @param checked Set to true to display a check mark, false to hide
+ * it. The default value is false.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setChecked(boolean checked);
+
+ /**
+ * Return whether the item is currently displaying a check mark.
+ *
+ * @return If a check mark is displayed, returns true.
+ *
+ * @see #setChecked
+ */
+ public boolean isChecked();
+
+ /**
+ * Sets the visibility of the menu item. Even if a menu item is not visible,
+ * it may still be invoked via its shortcut (to completely disable an item,
+ * set it to invisible and {@link #setEnabled(boolean) disabled}).
+ *
+ * @param visible If true then the item will be visible; if false it is
+ * hidden.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setVisible(boolean visible);
+
+ /**
+ * Return the visibility of the menu item.
+ *
+ * @return If true the item is visible; else it is hidden.
+ */
+ public boolean isVisible();
+
+ /**
+ * Sets whether the menu item is enabled. Disabling a menu item will not
+ * allow it to be invoked via its shortcut. The menu item will still be
+ * visible.
+ *
+ * @param enabled If true then the item will be invokable; if false it is
+ * won't be invokable.
+ * @return This Item so additional setters can be called.
+ */
+ public MenuItem setEnabled(boolean enabled);
+
+ /**
+ * Return the enabled state of the menu item.
+ *
+ * @return If true the item is enabled and hence invokable; else it is not.
+ */
+ public boolean isEnabled();
+
+ /**
+ * Check whether this item has an associated sub-menu. I.e. it is a
+ * sub-menu of another menu.
+ *
+ * @return If true this item has a menu; else it is a
+ * normal item.
+ */
+ public boolean hasSubMenu();
+
+ /**
+ * Get the sub-menu to be invoked when this item is selected, if it has
+ * one. See {@link #hasSubMenu()}.
+ *
+ * @return The associated menu if there is one, else null
+ */
+ public SubMenu getSubMenu();
+
+ /**
+ * Set a custom listener for invocation of this menu item. In most
+ * situations, it is more efficient and easier to use
+ * {@link Activity#onOptionsItemSelected(MenuItem)} or
+ * {@link Activity#onContextItemSelected(MenuItem)}.
+ *
+ * @param menuItemClickListener The object to receive invokations.
+ * @return This Item so additional setters can be called.
+ * @see Activity#onOptionsItemSelected(MenuItem)
+ * @see Activity#onContextItemSelected(MenuItem)
+ */
+ public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener);
+
+ /**
+ * Gets the extra information linked to this menu item. This extra
+ * information is set by the View that added this menu item to the
+ * menu.
+ *
+ * @see OnCreateContextMenuListener
+ * @return The extra information linked to the View that added this
+ * menu item to the menu. This can be null.
+ */
+ public ContextMenuInfo getMenuInfo();
+
+ /**
+ * Sets how this item should display in the presence of an Action Bar.
+ * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS},
+ * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should
+ * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}.
+ * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action,
+ * it should be shown with a text label.
+ *
+ * @param actionEnum How the item should display. One of
+ * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+ * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+ *
+ * @see android.app.ActionBar
+ * @see #setActionView(View)
+ */
+ public void setShowAsAction(int actionEnum);
+
+ /**
+ * Sets how this item should display in the presence of an Action Bar.
+ * The parameter actionEnum is a flag set. One of {@link #SHOW_AS_ACTION_ALWAYS},
+ * {@link #SHOW_AS_ACTION_IF_ROOM}, or {@link #SHOW_AS_ACTION_NEVER} should
+ * be used, and you may optionally OR the value with {@link #SHOW_AS_ACTION_WITH_TEXT}.
+ * SHOW_AS_ACTION_WITH_TEXT requests that when the item is shown as an action,
+ * it should be shown with a text label.
+ *
+ * <p>Note: This method differs from {@link #setShowAsAction(int)} only in that it
+ * returns the current MenuItem instance for call chaining.
+ *
+ * @param actionEnum How the item should display. One of
+ * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+ * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+ *
+ * @see android.app.ActionBar
+ * @see #setActionView(View)
+ * @return This MenuItem instance for call chaining.
+ */
+ public MenuItem setShowAsActionFlags(int actionEnum);
+
+ /**
+ * Set an action view for this menu item. An action view will be displayed in place
+ * of an automatically generated menu item element in the UI when this item is shown
+ * as an action within a parent.
+ * <p>
+ * <strong>Note:</strong> Setting an action view overrides the action provider
+ * set via {@link #setActionProvider(ActionProvider)}.
+ * </p>
+ *
+ * @param view View to use for presenting this item to the user.
+ * @return This Item so additional setters can be called.
+ *
+ * @see #setShowAsAction(int)
+ */
+ public MenuItem setActionView(View view);
+
+ /**
+ * Set an action view for this menu item. An action view will be displayed in place
+ * of an automatically generated menu item element in the UI when this item is shown
+ * as an action within a parent.
+ * <p>
+ * <strong>Note:</strong> Setting an action view overrides the action provider
+ * set via {@link #setActionProvider(ActionProvider)}.
+ * </p>
+ *
+ * @param resId Layout resource to use for presenting this item to the user.
+ * @return This Item so additional setters can be called.
+ *
+ * @see #setShowAsAction(int)
+ */
+ public MenuItem setActionView(@LayoutRes int resId);
+
+ /**
+ * Returns the currently set action view for this menu item.
+ *
+ * @return This item's action view
+ *
+ * @see #setActionView(View)
+ * @see #setShowAsAction(int)
+ */
+ public View getActionView();
+
+ /**
+ * Sets the {@link ActionProvider} responsible for creating an action view if
+ * the item is placed on the action bar. The provider also provides a default
+ * action invoked if the item is placed in the overflow menu.
+ * <p>
+ * <strong>Note:</strong> Setting an action provider overrides the action view
+ * set via {@link #setActionView(int)} or {@link #setActionView(View)}.
+ * </p>
+ *
+ * @param actionProvider The action provider.
+ * @return This Item so additional setters can be called.
+ *
+ * @see ActionProvider
+ */
+ public MenuItem setActionProvider(ActionProvider actionProvider);
+
+ /**
+ * Gets the {@link ActionProvider}.
+ *
+ * @return The action provider.
+ *
+ * @see ActionProvider
+ * @see #setActionProvider(ActionProvider)
+ */
+ public ActionProvider getActionProvider();
+
+ /**
+ * Expand the action view associated with this menu item.
+ * The menu item must have an action view set, as well as
+ * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+ * If a listener has been set using {@link #setOnActionExpandListener(OnActionExpandListener)}
+ * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)}
+ * method invoked. The listener may return false from this method to prevent expanding
+ * the action view.
+ *
+ * @return true if the action view was expanded, false otherwise.
+ */
+ public boolean expandActionView();
+
+ /**
+ * Collapse the action view associated with this menu item.
+ * The menu item must have an action view set, as well as the showAsAction flag
+ * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. If a listener has been set using
+ * {@link #setOnActionExpandListener(OnActionExpandListener)} it will have its
+ * {@link OnActionExpandListener#onMenuItemActionCollapse(MenuItem)} method invoked.
+ * The listener may return false from this method to prevent collapsing the action view.
+ *
+ * @return true if the action view was collapsed, false otherwise.
+ */
+ public boolean collapseActionView();
+
+ /**
+ * Returns true if this menu item's action view has been expanded.
+ *
+ * @return true if the item's action view is expanded, false otherwise.
+ *
+ * @see #expandActionView()
+ * @see #collapseActionView()
+ * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
+ * @see OnActionExpandListener
+ */
+ public boolean isActionViewExpanded();
+
+ /**
+ * Set an {@link OnActionExpandListener} on this menu item to be notified when
+ * the associated action view is expanded or collapsed. The menu item must
+ * be configured to expand or collapse its action view using the flag
+ * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
+ *
+ * @param listener Listener that will respond to expand/collapse events
+ * @return This menu item instance for call chaining
+ */
+ public MenuItem setOnActionExpandListener(OnActionExpandListener listener);
+
+ /**
+ * Change the content description associated with this menu item.
+ *
+ * @param contentDescription The new content description.
+ */
+ default MenuItem setContentDescription(CharSequence contentDescription) {
+ return this;
+ }
+
+ /**
+ * Retrieve the content description associated with this menu item.
+ *
+ * @return The content description.
+ */
+ default CharSequence getContentDescription() {
+ return null;
+ }
+
+ /**
+ * Change the tooltip text associated with this menu item.
+ *
+ * @param tooltipText The new tooltip text.
+ */
+ default MenuItem setTooltipText(CharSequence tooltipText) {
+ return this;
+ }
+
+ /**
+ * Retrieve the tooltip text associated with this menu item.
+ *
+ * @return The tooltip text.
+ */
+ default CharSequence getTooltipText() {
+ return null;
+ }
+
+ /**
+ * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_OVERFLOW_ALWAYS}.
+ * Default value if {@code false}.
+ *
+ * @hide
+ */
+ default boolean requiresOverflow() {
+ return false;
+ }
+}
diff --git a/android/view/MotionEvent.java b/android/view/MotionEvent.java
new file mode 100644
index 00000000..04fa637b
--- /dev/null
+++ b/android/view/MotionEvent.java
@@ -0,0 +1,3827 @@
+/*
+ * Copyright (C) 2007 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.TestApi;
+import android.graphics.Matrix;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * Object used to report movement (mouse, pen, finger, trackball) events.
+ * Motion events may hold either absolute or relative movements and other data,
+ * depending on the type of device.
+ *
+ * <h3>Overview</h3>
+ * <p>
+ * Motion events describe movements in terms of an action code and a set of axis values.
+ * The action code specifies the state change that occurred such as a pointer going
+ * down or up. The axis values describe the position and other movement properties.
+ * </p><p>
+ * For example, when the user first touches the screen, the system delivers a touch
+ * event to the appropriate {@link View} with the action code {@link #ACTION_DOWN}
+ * and a set of axis values that include the X and Y coordinates of the touch and
+ * information about the pressure, size and orientation of the contact area.
+ * </p><p>
+ * Some devices can report multiple movement traces at the same time. Multi-touch
+ * screens emit one movement trace for each finger. The individual fingers or
+ * other objects that generate movement traces are referred to as <em>pointers</em>.
+ * Motion events contain information about all of the pointers that are currently active
+ * even if some of them have not moved since the last event was delivered.
+ * </p><p>
+ * The number of pointers only ever changes by one as individual pointers go up and down,
+ * except when the gesture is canceled.
+ * </p><p>
+ * Each pointer has a unique id that is assigned when it first goes down
+ * (indicated by {@link #ACTION_DOWN} or {@link #ACTION_POINTER_DOWN}). A pointer id
+ * remains valid until the pointer eventually goes up (indicated by {@link #ACTION_UP}
+ * or {@link #ACTION_POINTER_UP}) or when the gesture is canceled (indicated by
+ * {@link #ACTION_CANCEL}).
+ * </p><p>
+ * The MotionEvent class provides many methods to query the position and other properties of
+ * pointers, such as {@link #getX(int)}, {@link #getY(int)}, {@link #getAxisValue},
+ * {@link #getPointerId(int)}, {@link #getToolType(int)}, and many others. Most of these
+ * methods accept the pointer index as a parameter rather than the pointer id.
+ * The pointer index of each pointer in the event ranges from 0 to one less than the value
+ * returned by {@link #getPointerCount()}.
+ * </p><p>
+ * The order in which individual pointers appear within a motion event is undefined.
+ * Thus the pointer index of a pointer can change from one event to the next but
+ * the pointer id of a pointer is guaranteed to remain constant as long as the pointer
+ * remains active. Use the {@link #getPointerId(int)} method to obtain the
+ * pointer id of a pointer to track it across all subsequent motion events in a gesture.
+ * Then for successive motion events, use the {@link #findPointerIndex(int)} method
+ * to obtain the pointer index for a given pointer id in that motion event.
+ * </p><p>
+ * Mouse and stylus buttons can be retrieved using {@link #getButtonState()}. It is a
+ * good idea to check the button state while handling {@link #ACTION_DOWN} as part
+ * of a touch event. The application may choose to perform some different action
+ * if the touch event starts due to a secondary button click, such as presenting a
+ * context menu.
+ * </p>
+ *
+ * <h3>Batching</h3>
+ * <p>
+ * For efficiency, motion events with {@link #ACTION_MOVE} may batch together
+ * multiple movement samples within a single object. The most current
+ * pointer coordinates are available using {@link #getX(int)} and {@link #getY(int)}.
+ * Earlier coordinates within the batch are accessed using {@link #getHistoricalX(int, int)}
+ * and {@link #getHistoricalY(int, int)}. The coordinates are "historical" only
+ * insofar as they are older than the current coordinates in the batch; however,
+ * they are still distinct from any other coordinates reported in prior motion events.
+ * To process all coordinates in the batch in time order, first consume the historical
+ * coordinates then consume the current coordinates.
+ * </p><p>
+ * Example: Consuming all samples for all pointers in a motion event in time order.
+ * </p><p><pre><code>
+ * void printSamples(MotionEvent ev) {
+ * final int historySize = ev.getHistorySize();
+ * final int pointerCount = ev.getPointerCount();
+ * for (int h = 0; h &lt; historySize; h++) {
+ * System.out.printf("At time %d:", ev.getHistoricalEventTime(h));
+ * for (int p = 0; p &lt; pointerCount; p++) {
+ * System.out.printf(" pointer %d: (%f,%f)",
+ * ev.getPointerId(p), ev.getHistoricalX(p, h), ev.getHistoricalY(p, h));
+ * }
+ * }
+ * System.out.printf("At time %d:", ev.getEventTime());
+ * for (int p = 0; p &lt; pointerCount; p++) {
+ * System.out.printf(" pointer %d: (%f,%f)",
+ * ev.getPointerId(p), ev.getX(p), ev.getY(p));
+ * }
+ * }
+ * </code></pre></p>
+ *
+ * <h3>Device Types</h3>
+ * <p>
+ * The interpretation of the contents of a MotionEvent varies significantly depending
+ * on the source class of the device.
+ * </p><p>
+ * On pointing devices with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+ * such as touch screens, the pointer coordinates specify absolute
+ * positions such as view X/Y coordinates. Each complete gesture is represented
+ * by a sequence of motion events with actions that describe pointer state transitions
+ * and movements. A gesture starts with a motion event with {@link #ACTION_DOWN}
+ * that provides the location of the first pointer down. As each additional
+ * pointer that goes down or up, the framework will generate a motion event with
+ * {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP} accordingly.
+ * Pointer movements are described by motion events with {@link #ACTION_MOVE}.
+ * Finally, a gesture end either when the final pointer goes up as represented
+ * by a motion event with {@link #ACTION_UP} or when gesture is canceled
+ * with {@link #ACTION_CANCEL}.
+ * </p><p>
+ * Some pointing devices such as mice may support vertical and/or horizontal scrolling.
+ * A scroll event is reported as a generic motion event with {@link #ACTION_SCROLL} that
+ * includes the relative scroll offset in the {@link #AXIS_VSCROLL} and
+ * {@link #AXIS_HSCROLL} axes. See {@link #getAxisValue(int)} for information
+ * about retrieving these additional axes.
+ * </p><p>
+ * On trackball devices with source class {@link InputDevice#SOURCE_CLASS_TRACKBALL},
+ * the pointer coordinates specify relative movements as X/Y deltas.
+ * A trackball gesture consists of a sequence of movements described by motion
+ * events with {@link #ACTION_MOVE} interspersed with occasional {@link #ACTION_DOWN}
+ * or {@link #ACTION_UP} motion events when the trackball button is pressed or released.
+ * </p><p>
+ * On joystick devices with source class {@link InputDevice#SOURCE_CLASS_JOYSTICK},
+ * the pointer coordinates specify the absolute position of the joystick axes.
+ * The joystick axis values are normalized to a range of -1.0 to 1.0 where 0.0 corresponds
+ * to the center position. More information about the set of available axes and the
+ * range of motion can be obtained using {@link InputDevice#getMotionRange}.
+ * Some common joystick axes are {@link #AXIS_X}, {@link #AXIS_Y},
+ * {@link #AXIS_HAT_X}, {@link #AXIS_HAT_Y}, {@link #AXIS_Z} and {@link #AXIS_RZ}.
+ * </p><p>
+ * Refer to {@link InputDevice} for more information about how different kinds of
+ * input devices and sources represent pointer coordinates.
+ * </p>
+ *
+ * <h3>Consistency Guarantees</h3>
+ * <p>
+ * Motion events are always delivered to views as a consistent stream of events.
+ * What constitutes a consistent stream varies depending on the type of device.
+ * For touch events, consistency implies that pointers go down one at a time,
+ * move around as a group and then go up one at a time or are canceled.
+ * </p><p>
+ * While the framework tries to deliver consistent streams of motion events to
+ * views, it cannot guarantee it. Some events may be dropped or modified by
+ * containing views in the application before they are delivered thereby making
+ * the stream of events inconsistent. Views should always be prepared to
+ * handle {@link #ACTION_CANCEL} and should tolerate anomalous
+ * situations such as receiving a new {@link #ACTION_DOWN} without first having
+ * received an {@link #ACTION_UP} for the prior gesture.
+ * </p>
+ */
+public final class MotionEvent extends InputEvent implements Parcelable {
+ private static final long NS_PER_MS = 1000000;
+ private static final String LABEL_PREFIX = "AXIS_";
+
+ /**
+ * An invalid pointer id.
+ *
+ * This value (-1) can be used as a placeholder to indicate that a pointer id
+ * has not been assigned or is not available. It cannot appear as
+ * a pointer id inside a {@link MotionEvent}.
+ */
+ public static final int INVALID_POINTER_ID = -1;
+
+ /**
+ * Bit mask of the parts of the action code that are the action itself.
+ */
+ public static final int ACTION_MASK = 0xff;
+
+ /**
+ * Constant for {@link #getActionMasked}: A pressed gesture has started, the
+ * motion contains the initial starting location.
+ * <p>
+ * This is also a good time to check the button state to distinguish
+ * secondary and tertiary button clicks and handle them appropriately.
+ * Use {@link #getButtonState} to retrieve the button state.
+ * </p>
+ */
+ public static final int ACTION_DOWN = 0;
+
+ /**
+ * Constant for {@link #getActionMasked}: A pressed gesture has finished, the
+ * motion contains the final release location as well as any intermediate
+ * points since the last down or move event.
+ */
+ public static final int ACTION_UP = 1;
+
+ /**
+ * Constant for {@link #getActionMasked}: A change has happened during a
+ * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
+ * The motion contains the most recent point, as well as any intermediate
+ * points since the last down or move event.
+ */
+ public static final int ACTION_MOVE = 2;
+
+ /**
+ * Constant for {@link #getActionMasked}: The current gesture has been aborted.
+ * You will not receive any more points in it. You should treat this as
+ * an up event, but not perform any action that you normally would.
+ */
+ public static final int ACTION_CANCEL = 3;
+
+ /**
+ * Constant for {@link #getActionMasked}: A movement has happened outside of the
+ * normal bounds of the UI element. This does not provide a full gesture,
+ * but only the initial location of the movement/touch.
+ * <p>
+ * Note: Because the location of any event will be outside the
+ * bounds of the view hierarchy, it will not get dispatched to
+ * any children of a ViewGroup by default. Therefore,
+ * movements with ACTION_OUTSIDE should be handled in either the
+ * root {@link View} or in the appropriate {@link Window.Callback}
+ * (e.g. {@link android.app.Activity} or {@link android.app.Dialog}).
+ * </p>
+ */
+ public static final int ACTION_OUTSIDE = 4;
+
+ /**
+ * Constant for {@link #getActionMasked}: A non-primary pointer has gone down.
+ * <p>
+ * Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
+ * </p><p>
+ * The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
+ * unmasked action returned by {@link #getAction}.
+ * </p>
+ */
+ public static final int ACTION_POINTER_DOWN = 5;
+
+ /**
+ * Constant for {@link #getActionMasked}: A non-primary pointer has gone up.
+ * <p>
+ * Use {@link #getActionIndex} to retrieve the index of the pointer that changed.
+ * </p><p>
+ * The index is encoded in the {@link #ACTION_POINTER_INDEX_MASK} bits of the
+ * unmasked action returned by {@link #getAction}.
+ * </p>
+ */
+ public static final int ACTION_POINTER_UP = 6;
+
+ /**
+ * Constant for {@link #getActionMasked}: A change happened but the pointer
+ * is not down (unlike {@link #ACTION_MOVE}). The motion contains the most
+ * recent point, as well as any intermediate points since the last
+ * hover move event.
+ * <p>
+ * This action is always delivered to the window or view under the pointer.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_HOVER_MOVE = 7;
+
+ /**
+ * Constant for {@link #getActionMasked}: The motion event contains relative
+ * vertical and/or horizontal scroll offsets. Use {@link #getAxisValue(int)}
+ * to retrieve the information from {@link #AXIS_VSCROLL} and {@link #AXIS_HSCROLL}.
+ * The pointer may or may not be down when this event is dispatched.
+ * <p>
+ * This action is always delivered to the window or view under the pointer, which
+ * may not be the window or view currently touched.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_SCROLL = 8;
+
+ /**
+ * Constant for {@link #getActionMasked}: The pointer is not down but has entered the
+ * boundaries of a window or view.
+ * <p>
+ * This action is always delivered to the window or view under the pointer.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_HOVER_ENTER = 9;
+
+ /**
+ * Constant for {@link #getActionMasked}: The pointer is not down but has exited the
+ * boundaries of a window or view.
+ * <p>
+ * This action is always delivered to the window or view that was previously under the pointer.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_HOVER_EXIT = 10;
+
+ /**
+ * Constant for {@link #getActionMasked}: A button has been pressed.
+ *
+ * <p>
+ * Use {@link #getActionButton()} to get which button was pressed.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_BUTTON_PRESS = 11;
+
+ /**
+ * Constant for {@link #getActionMasked}: A button has been released.
+ *
+ * <p>
+ * Use {@link #getActionButton()} to get which button was released.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_BUTTON_RELEASE = 12;
+
+ /**
+ * Bits in the action code that represent a pointer index, used with
+ * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Shifting
+ * down by {@link #ACTION_POINTER_INDEX_SHIFT} provides the actual pointer
+ * index where the data for the pointer going up or down can be found; you can
+ * get its identifier with {@link #getPointerId(int)} and the actual
+ * data with {@link #getX(int)} etc.
+ *
+ * @see #getActionIndex
+ */
+ public static final int ACTION_POINTER_INDEX_MASK = 0xff00;
+
+ /**
+ * Bit shift for the action bits holding the pointer index as
+ * defined by {@link #ACTION_POINTER_INDEX_MASK}.
+ *
+ * @see #getActionIndex
+ */
+ public static final int ACTION_POINTER_INDEX_SHIFT = 8;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_DOWN}.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_1_DOWN = ACTION_POINTER_DOWN | 0x0000;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_DOWN}.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_2_DOWN = ACTION_POINTER_DOWN | 0x0100;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_DOWN}.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_3_DOWN = ACTION_POINTER_DOWN | 0x0200;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_UP}.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_1_UP = ACTION_POINTER_UP | 0x0000;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_UP}.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_2_UP = ACTION_POINTER_UP | 0x0100;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_UP}.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_3_UP = ACTION_POINTER_UP | 0x0200;
+
+ /**
+ * @deprecated Renamed to {@link #ACTION_POINTER_INDEX_MASK} to match
+ * the actual data contained in these bits.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_ID_MASK = 0xff00;
+
+ /**
+ * @deprecated Renamed to {@link #ACTION_POINTER_INDEX_SHIFT} to match
+ * the actual data contained in these bits.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_ID_SHIFT = 8;
+
+ /**
+ * This flag indicates that the window that received this motion event is partly
+ * or wholly obscured by another visible window above it. This flag is set to true
+ * even if the event did not directly pass through the obscured area.
+ * A security sensitive application can check this flag to identify situations in which
+ * a malicious application may have covered up part of its content for the purpose
+ * of misleading the user or hijacking touches. An appropriate response might be
+ * to drop the suspect touches or to take additional precautions to confirm the user's
+ * actual intent.
+ */
+ public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
+
+ /**
+ * This flag indicates that the window that received this motion event is partly
+ * or wholly obscured by another visible window above it. This flag is set to true
+ * even if the event did not directly pass through the obscured area.
+ * A security sensitive application can check this flag to identify situations in which
+ * a malicious application may have covered up part of its content for the purpose
+ * of misleading the user or hijacking touches. An appropriate response might be
+ * to drop the suspect touches or to take additional precautions to confirm the user's
+ * actual intent.
+ *
+ * Unlike FLAG_WINDOW_IS_OBSCURED, this is actually true.
+ * @hide
+ */
+ public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
+
+ /**
+ * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+ * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+ * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+ * @hide
+ */
+ public static final int FLAG_HOVER_EXIT_PENDING = 0x4;
+
+ /**
+ * This flag indicates that the event has been generated by a gesture generator. It
+ * provides a hint to the GestureDector to not apply any touch slop.
+ *
+ * @hide
+ */
+ public static final int FLAG_IS_GENERATED_GESTURE = 0x8;
+
+ /**
+ * Private flag that indicates when the system has detected that this motion event
+ * may be inconsistent with respect to the sequence of previously delivered motion events,
+ * such as when a pointer move event is sent but the pointer is not down.
+ *
+ * @hide
+ * @see #isTainted
+ * @see #setTainted
+ */
+ public static final int FLAG_TAINTED = 0x80000000;
+
+ /**
+ * Private flag indicating that this event was synthesized by the system and
+ * should be delivered to the accessibility focused view first. When being
+ * dispatched such an event is not handled by predecessors of the accessibility
+ * focused view and after the event reaches that view the flag is cleared and
+ * normal event dispatch is performed. This ensures that the platform can click
+ * on any view that has accessibility focus which is semantically equivalent to
+ * asking the view to perform a click accessibility action but more generic as
+ * views not implementing click action correctly can still be activated.
+ *
+ * @hide
+ * @see #isTargetAccessibilityFocus()
+ * @see #setTargetAccessibilityFocus(boolean)
+ */
+ public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
+
+
+ /**
+ * Flag indicating the motion event intersected the top edge of the screen.
+ */
+ public static final int EDGE_TOP = 0x00000001;
+
+ /**
+ * Flag indicating the motion event intersected the bottom edge of the screen.
+ */
+ public static final int EDGE_BOTTOM = 0x00000002;
+
+ /**
+ * Flag indicating the motion event intersected the left edge of the screen.
+ */
+ public static final int EDGE_LEFT = 0x00000004;
+
+ /**
+ * Flag indicating the motion event intersected the right edge of the screen.
+ */
+ public static final int EDGE_RIGHT = 0x00000008;
+
+ /**
+ * Axis constant: X axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen, reports the absolute X screen position of the center of
+ * the touch contact area. The units are display pixels.
+ * <li>For a touch pad, reports the absolute X surface position of the center of the touch
+ * contact area. The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+ * to query the effective range of values.
+ * <li>For a mouse, reports the absolute X screen position of the mouse pointer.
+ * The units are display pixels.
+ * <li>For a trackball, reports the relative horizontal displacement of the trackball.
+ * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+ * <li>For a joystick, reports the absolute X position of the joystick.
+ * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+ * </ul>
+ * </p>
+ *
+ * @see #getX(int)
+ * @see #getHistoricalX(int, int)
+ * @see MotionEvent.PointerCoords#x
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_X = 0;
+
+ /**
+ * Axis constant: Y axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen, reports the absolute Y screen position of the center of
+ * the touch contact area. The units are display pixels.
+ * <li>For a touch pad, reports the absolute Y surface position of the center of the touch
+ * contact area. The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+ * to query the effective range of values.
+ * <li>For a mouse, reports the absolute Y screen position of the mouse pointer.
+ * The units are display pixels.
+ * <li>For a trackball, reports the relative vertical displacement of the trackball.
+ * The value is normalized to a range from -1.0 (up) to 1.0 (down).
+ * <li>For a joystick, reports the absolute Y position of the joystick.
+ * The value is normalized to a range from -1.0 (up or far) to 1.0 (down or near).
+ * </ul>
+ * </p>
+ *
+ * @see #getY(int)
+ * @see #getHistoricalY(int, int)
+ * @see MotionEvent.PointerCoords#y
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_Y = 1;
+
+ /**
+ * Axis constant: Pressure axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen or touch pad, reports the approximate pressure applied to the surface
+ * by a finger or other tool. The value is normalized to a range from
+ * 0 (no pressure at all) to 1 (normal pressure), although values higher than 1
+ * may be generated depending on the calibration of the input device.
+ * <li>For a trackball, the value is set to 1 if the trackball button is pressed
+ * or 0 otherwise.
+ * <li>For a mouse, the value is set to 1 if the primary mouse button is pressed
+ * or 0 otherwise.
+ * </ul>
+ * </p>
+ *
+ * @see #getPressure(int)
+ * @see #getHistoricalPressure(int, int)
+ * @see MotionEvent.PointerCoords#pressure
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_PRESSURE = 2;
+
+ /**
+ * Axis constant: Size axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen or touch pad, reports the approximate size of the contact area in
+ * relation to the maximum detectable size for the device. The value is normalized
+ * to a range from 0 (smallest detectable size) to 1 (largest detectable size),
+ * although it is not a linear scale. This value is of limited use.
+ * To obtain calibrated size information, use
+ * {@link #AXIS_TOUCH_MAJOR} or {@link #AXIS_TOOL_MAJOR}.
+ * </ul>
+ * </p>
+ *
+ * @see #getSize(int)
+ * @see #getHistoricalSize(int, int)
+ * @see MotionEvent.PointerCoords#size
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_SIZE = 3;
+
+ /**
+ * Axis constant: TouchMajor axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen, reports the length of the major axis of an ellipse that
+ * represents the touch area at the point of contact.
+ * The units are display pixels.
+ * <li>For a touch pad, reports the length of the major axis of an ellipse that
+ * represents the touch area at the point of contact.
+ * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+ * to query the effective range of values.
+ * </ul>
+ * </p>
+ *
+ * @see #getTouchMajor(int)
+ * @see #getHistoricalTouchMajor(int, int)
+ * @see MotionEvent.PointerCoords#touchMajor
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_TOUCH_MAJOR = 4;
+
+ /**
+ * Axis constant: TouchMinor axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen, reports the length of the minor axis of an ellipse that
+ * represents the touch area at the point of contact.
+ * The units are display pixels.
+ * <li>For a touch pad, reports the length of the minor axis of an ellipse that
+ * represents the touch area at the point of contact.
+ * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+ * to query the effective range of values.
+ * </ul>
+ * </p><p>
+ * When the touch is circular, the major and minor axis lengths will be equal to one another.
+ * </p>
+ *
+ * @see #getTouchMinor(int)
+ * @see #getHistoricalTouchMinor(int, int)
+ * @see MotionEvent.PointerCoords#touchMinor
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_TOUCH_MINOR = 5;
+
+ /**
+ * Axis constant: ToolMajor axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen, reports the length of the major axis of an ellipse that
+ * represents the size of the approaching finger or tool used to make contact.
+ * <li>For a touch pad, reports the length of the major axis of an ellipse that
+ * represents the size of the approaching finger or tool used to make contact.
+ * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+ * to query the effective range of values.
+ * </ul>
+ * </p><p>
+ * When the touch is circular, the major and minor axis lengths will be equal to one another.
+ * </p><p>
+ * The tool size may be larger than the touch size since the tool may not be fully
+ * in contact with the touch sensor.
+ * </p>
+ *
+ * @see #getToolMajor(int)
+ * @see #getHistoricalToolMajor(int, int)
+ * @see MotionEvent.PointerCoords#toolMajor
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_TOOL_MAJOR = 6;
+
+ /**
+ * Axis constant: ToolMinor axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen, reports the length of the minor axis of an ellipse that
+ * represents the size of the approaching finger or tool used to make contact.
+ * <li>For a touch pad, reports the length of the minor axis of an ellipse that
+ * represents the size of the approaching finger or tool used to make contact.
+ * The units are device-dependent; use {@link InputDevice#getMotionRange(int)}
+ * to query the effective range of values.
+ * </ul>
+ * </p><p>
+ * When the touch is circular, the major and minor axis lengths will be equal to one another.
+ * </p><p>
+ * The tool size may be larger than the touch size since the tool may not be fully
+ * in contact with the touch sensor.
+ * </p>
+ *
+ * @see #getToolMinor(int)
+ * @see #getHistoricalToolMinor(int, int)
+ * @see MotionEvent.PointerCoords#toolMinor
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_TOOL_MINOR = 7;
+
+ /**
+ * Axis constant: Orientation axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a touch screen or touch pad, reports the orientation of the finger
+ * or tool in radians relative to the vertical plane of the device.
+ * An angle of 0 radians indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right).
+ * <li>For a stylus, the orientation indicates the direction in which the stylus
+ * is pointing in relation to the vertical axis of the current orientation of the screen.
+ * The range is from -PI radians to PI radians, where 0 is pointing up,
+ * -PI/2 radians is pointing left, -PI or PI radians is pointing down, and PI/2 radians
+ * is pointing right. See also {@link #AXIS_TILT}.
+ * </ul>
+ * </p>
+ *
+ * @see #getOrientation(int)
+ * @see #getHistoricalOrientation(int, int)
+ * @see MotionEvent.PointerCoords#orientation
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_ORIENTATION = 8;
+
+ /**
+ * Axis constant: Vertical Scroll axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a mouse, reports the relative movement of the vertical scroll wheel.
+ * The value is normalized to a range from -1.0 (down) to 1.0 (up).
+ * </ul>
+ * </p><p>
+ * This axis should be used to scroll views vertically.
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_VSCROLL = 9;
+
+ /**
+ * Axis constant: Horizontal Scroll axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a mouse, reports the relative movement of the horizontal scroll wheel.
+ * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+ * </ul>
+ * </p><p>
+ * This axis should be used to scroll views horizontally.
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_HSCROLL = 10;
+
+ /**
+ * Axis constant: Z axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute Z position of the joystick.
+ * The value is normalized to a range from -1.0 (high) to 1.0 (low).
+ * <em>On game pads with two analog joysticks, this axis is often reinterpreted
+ * to report the absolute X position of the second joystick instead.</em>
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_Z = 11;
+
+ /**
+ * Axis constant: X Rotation axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute rotation angle about the X axis.
+ * The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_RX = 12;
+
+ /**
+ * Axis constant: Y Rotation axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute rotation angle about the Y axis.
+ * The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_RY = 13;
+
+ /**
+ * Axis constant: Z Rotation axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute rotation angle about the Z axis.
+ * The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
+ * <em>On game pads with two analog joysticks, this axis is often reinterpreted
+ * to report the absolute Y position of the second joystick instead.</em>
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_RZ = 14;
+
+ /**
+ * Axis constant: Hat X axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute X position of the directional hat control.
+ * The value is normalized to a range from -1.0 (left) to 1.0 (right).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_HAT_X = 15;
+
+ /**
+ * Axis constant: Hat Y axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute Y position of the directional hat control.
+ * The value is normalized to a range from -1.0 (up) to 1.0 (down).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_HAT_Y = 16;
+
+ /**
+ * Axis constant: Left Trigger axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute position of the left trigger control.
+ * The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_LTRIGGER = 17;
+
+ /**
+ * Axis constant: Right Trigger axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute position of the right trigger control.
+ * The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_RTRIGGER = 18;
+
+ /**
+ * Axis constant: Throttle axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute position of the throttle control.
+ * The value is normalized to a range from 0.0 (fully open) to 1.0 (fully closed).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_THROTTLE = 19;
+
+ /**
+ * Axis constant: Rudder axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute position of the rudder control.
+ * The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_RUDDER = 20;
+
+ /**
+ * Axis constant: Wheel axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute position of the steering wheel control.
+ * The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_WHEEL = 21;
+
+ /**
+ * Axis constant: Gas axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute position of the gas (accelerator) control.
+ * The value is normalized to a range from 0.0 (no acceleration)
+ * to 1.0 (maximum acceleration).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GAS = 22;
+
+ /**
+ * Axis constant: Brake axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a joystick, reports the absolute position of the brake control.
+ * The value is normalized to a range from 0.0 (no braking) to 1.0 (maximum braking).
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_BRAKE = 23;
+
+ /**
+ * Axis constant: Distance axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a stylus, reports the distance of the stylus from the screen.
+ * A value of 0.0 indicates direct contact and larger values indicate increasing
+ * distance from the surface.
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_DISTANCE = 24;
+
+ /**
+ * Axis constant: Tilt axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a stylus, reports the tilt angle of the stylus in radians where
+ * 0 radians indicates that the stylus is being held perpendicular to the
+ * surface, and PI/2 radians indicates that the stylus is being held flat
+ * against the surface.
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int, int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_TILT = 25;
+
+ /**
+ * Axis constant: Generic scroll axis of a motion event.
+ * <p>
+ * <ul>
+ * <li>Reports the relative movement of the generic scrolling device.
+ * </ul>
+ * </p><p>
+ * This axis should be used for scroll events that are neither strictly vertical nor horizontal.
+ * A good example would be the rotation of a rotary encoder input device.
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ */
+ public static final int AXIS_SCROLL = 26;
+
+ /**
+ * Axis constant: The movement of x position of a motion event.
+ * <p>
+ * <ul>
+ * <li>For a mouse, reports a difference of x position between the previous position.
+ * This is useful when pointer is captured, in that case the mouse pointer doesn't change
+ * the location but this axis reports the difference which allows the app to see
+ * how the mouse is moved.
+ * </ul>
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int, int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_RELATIVE_X = 27;
+
+ /**
+ * Axis constant: The movement of y position of a motion event.
+ * <p>
+ * This is similar to {@link #AXIS_RELATIVE_X} but for y-axis.
+ * </p>
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int, int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_RELATIVE_Y = 28;
+
+ /**
+ * Axis constant: Generic 1 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_1 = 32;
+
+ /**
+ * Axis constant: Generic 2 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_2 = 33;
+
+ /**
+ * Axis constant: Generic 3 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_3 = 34;
+
+ /**
+ * Axis constant: Generic 4 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_4 = 35;
+
+ /**
+ * Axis constant: Generic 5 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_5 = 36;
+
+ /**
+ * Axis constant: Generic 6 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_6 = 37;
+
+ /**
+ * Axis constant: Generic 7 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_7 = 38;
+
+ /**
+ * Axis constant: Generic 8 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_8 = 39;
+
+ /**
+ * Axis constant: Generic 9 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_9 = 40;
+
+ /**
+ * Axis constant: Generic 10 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_10 = 41;
+
+ /**
+ * Axis constant: Generic 11 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_11 = 42;
+
+ /**
+ * Axis constant: Generic 12 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_12 = 43;
+
+ /**
+ * Axis constant: Generic 13 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_13 = 44;
+
+ /**
+ * Axis constant: Generic 14 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_14 = 45;
+
+ /**
+ * Axis constant: Generic 15 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_15 = 46;
+
+ /**
+ * Axis constant: Generic 16 axis of a motion event.
+ * The interpretation of a generic axis is device-specific.
+ *
+ * @see #getAxisValue(int, int)
+ * @see #getHistoricalAxisValue(int, int, int)
+ * @see MotionEvent.PointerCoords#getAxisValue(int)
+ * @see InputDevice#getMotionRange
+ */
+ public static final int AXIS_GENERIC_16 = 47;
+
+ // NOTE: If you add a new axis here you must also add it to:
+ // native/include/android/input.h
+ // frameworks/base/include/ui/KeycodeLabels.h
+
+ // Symbolic names of all axes.
+ private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
+ static {
+ SparseArray<String> names = AXIS_SYMBOLIC_NAMES;
+ names.append(AXIS_X, "AXIS_X");
+ names.append(AXIS_Y, "AXIS_Y");
+ names.append(AXIS_PRESSURE, "AXIS_PRESSURE");
+ names.append(AXIS_SIZE, "AXIS_SIZE");
+ names.append(AXIS_TOUCH_MAJOR, "AXIS_TOUCH_MAJOR");
+ names.append(AXIS_TOUCH_MINOR, "AXIS_TOUCH_MINOR");
+ names.append(AXIS_TOOL_MAJOR, "AXIS_TOOL_MAJOR");
+ names.append(AXIS_TOOL_MINOR, "AXIS_TOOL_MINOR");
+ names.append(AXIS_ORIENTATION, "AXIS_ORIENTATION");
+ names.append(AXIS_VSCROLL, "AXIS_VSCROLL");
+ names.append(AXIS_HSCROLL, "AXIS_HSCROLL");
+ names.append(AXIS_Z, "AXIS_Z");
+ names.append(AXIS_RX, "AXIS_RX");
+ names.append(AXIS_RY, "AXIS_RY");
+ names.append(AXIS_RZ, "AXIS_RZ");
+ names.append(AXIS_HAT_X, "AXIS_HAT_X");
+ names.append(AXIS_HAT_Y, "AXIS_HAT_Y");
+ names.append(AXIS_LTRIGGER, "AXIS_LTRIGGER");
+ names.append(AXIS_RTRIGGER, "AXIS_RTRIGGER");
+ names.append(AXIS_THROTTLE, "AXIS_THROTTLE");
+ names.append(AXIS_RUDDER, "AXIS_RUDDER");
+ names.append(AXIS_WHEEL, "AXIS_WHEEL");
+ names.append(AXIS_GAS, "AXIS_GAS");
+ names.append(AXIS_BRAKE, "AXIS_BRAKE");
+ names.append(AXIS_DISTANCE, "AXIS_DISTANCE");
+ names.append(AXIS_TILT, "AXIS_TILT");
+ names.append(AXIS_SCROLL, "AXIS_SCROLL");
+ names.append(AXIS_RELATIVE_X, "AXIS_REALTIVE_X");
+ names.append(AXIS_RELATIVE_Y, "AXIS_REALTIVE_Y");
+ names.append(AXIS_GENERIC_1, "AXIS_GENERIC_1");
+ names.append(AXIS_GENERIC_2, "AXIS_GENERIC_2");
+ names.append(AXIS_GENERIC_3, "AXIS_GENERIC_3");
+ names.append(AXIS_GENERIC_4, "AXIS_GENERIC_4");
+ names.append(AXIS_GENERIC_5, "AXIS_GENERIC_5");
+ names.append(AXIS_GENERIC_6, "AXIS_GENERIC_6");
+ names.append(AXIS_GENERIC_7, "AXIS_GENERIC_7");
+ names.append(AXIS_GENERIC_8, "AXIS_GENERIC_8");
+ names.append(AXIS_GENERIC_9, "AXIS_GENERIC_9");
+ names.append(AXIS_GENERIC_10, "AXIS_GENERIC_10");
+ names.append(AXIS_GENERIC_11, "AXIS_GENERIC_11");
+ names.append(AXIS_GENERIC_12, "AXIS_GENERIC_12");
+ names.append(AXIS_GENERIC_13, "AXIS_GENERIC_13");
+ names.append(AXIS_GENERIC_14, "AXIS_GENERIC_14");
+ names.append(AXIS_GENERIC_15, "AXIS_GENERIC_15");
+ names.append(AXIS_GENERIC_16, "AXIS_GENERIC_16");
+ }
+
+ /**
+ * Button constant: Primary button (left mouse button).
+ *
+ * This button constant is not set in response to simple touches with a finger
+ * or stylus tip. The user must actually push a button.
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_PRIMARY = 1 << 0;
+
+ /**
+ * Button constant: Secondary button (right mouse button).
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_SECONDARY = 1 << 1;
+
+ /**
+ * Button constant: Tertiary button (middle mouse button).
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_TERTIARY = 1 << 2;
+
+ /**
+ * Button constant: Back button pressed (mouse back button).
+ * <p>
+ * The system may send a {@link KeyEvent#KEYCODE_BACK} key press to the application
+ * when this button is pressed.
+ * </p>
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_BACK = 1 << 3;
+
+ /**
+ * Button constant: Forward button pressed (mouse forward button).
+ * <p>
+ * The system may send a {@link KeyEvent#KEYCODE_FORWARD} key press to the application
+ * when this button is pressed.
+ * </p>
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_FORWARD = 1 << 4;
+
+ /**
+ * Button constant: Primary stylus button pressed.
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_STYLUS_PRIMARY = 1 << 5;
+
+ /**
+ * Button constant: Secondary stylus button pressed.
+ *
+ * @see #getButtonState
+ */
+ public static final int BUTTON_STYLUS_SECONDARY = 1 << 6;
+
+ // NOTE: If you add a new axis here you must also add it to:
+ // native/include/android/input.h
+
+ // Symbolic names of all button states in bit order from least significant
+ // to most significant.
+ private static final String[] BUTTON_SYMBOLIC_NAMES = new String[] {
+ "BUTTON_PRIMARY",
+ "BUTTON_SECONDARY",
+ "BUTTON_TERTIARY",
+ "BUTTON_BACK",
+ "BUTTON_FORWARD",
+ "BUTTON_STYLUS_PRIMARY",
+ "BUTTON_STYLUS_SECONDARY",
+ "0x00000080",
+ "0x00000100",
+ "0x00000200",
+ "0x00000400",
+ "0x00000800",
+ "0x00001000",
+ "0x00002000",
+ "0x00004000",
+ "0x00008000",
+ "0x00010000",
+ "0x00020000",
+ "0x00040000",
+ "0x00080000",
+ "0x00100000",
+ "0x00200000",
+ "0x00400000",
+ "0x00800000",
+ "0x01000000",
+ "0x02000000",
+ "0x04000000",
+ "0x08000000",
+ "0x10000000",
+ "0x20000000",
+ "0x40000000",
+ "0x80000000",
+ };
+
+ /**
+ * Tool type constant: Unknown tool type.
+ * This constant is used when the tool type is not known or is not relevant,
+ * such as for a trackball or other non-pointing device.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_UNKNOWN = 0;
+
+ /**
+ * Tool type constant: The tool is a finger.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_FINGER = 1;
+
+ /**
+ * Tool type constant: The tool is a stylus.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_STYLUS = 2;
+
+ /**
+ * Tool type constant: The tool is a mouse or trackpad.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_MOUSE = 3;
+
+ /**
+ * Tool type constant: The tool is an eraser or a stylus being used in an inverted posture.
+ *
+ * @see #getToolType
+ */
+ public static final int TOOL_TYPE_ERASER = 4;
+
+ // NOTE: If you add a new tool type here you must also add it to:
+ // native/include/android/input.h
+
+ // Symbolic names of all tool types.
+ private static final SparseArray<String> TOOL_TYPE_SYMBOLIC_NAMES = new SparseArray<String>();
+ static {
+ SparseArray<String> names = TOOL_TYPE_SYMBOLIC_NAMES;
+ names.append(TOOL_TYPE_UNKNOWN, "TOOL_TYPE_UNKNOWN");
+ names.append(TOOL_TYPE_FINGER, "TOOL_TYPE_FINGER");
+ names.append(TOOL_TYPE_STYLUS, "TOOL_TYPE_STYLUS");
+ names.append(TOOL_TYPE_MOUSE, "TOOL_TYPE_MOUSE");
+ names.append(TOOL_TYPE_ERASER, "TOOL_TYPE_ERASER");
+ }
+
+ // Private value for history pos that obtains the current sample.
+ private static final int HISTORY_CURRENT = -0x80000000;
+
+ private static final int MAX_RECYCLED = 10;
+ private static final Object gRecyclerLock = new Object();
+ private static int gRecyclerUsed;
+ private static MotionEvent gRecyclerTop;
+
+ // Shared temporary objects used when translating coordinates supplied by
+ // the caller into single element PointerCoords and pointer id arrays.
+ private static final Object gSharedTempLock = new Object();
+ private static PointerCoords[] gSharedTempPointerCoords;
+ private static PointerProperties[] gSharedTempPointerProperties;
+ private static int[] gSharedTempPointerIndexMap;
+
+ private static final void ensureSharedTempPointerCapacity(int desiredCapacity) {
+ if (gSharedTempPointerCoords == null
+ || gSharedTempPointerCoords.length < desiredCapacity) {
+ int capacity = gSharedTempPointerCoords != null ? gSharedTempPointerCoords.length : 8;
+ while (capacity < desiredCapacity) {
+ capacity *= 2;
+ }
+ gSharedTempPointerCoords = PointerCoords.createArray(capacity);
+ gSharedTempPointerProperties = PointerProperties.createArray(capacity);
+ gSharedTempPointerIndexMap = new int[capacity];
+ }
+ }
+
+ // Pointer to the native MotionEvent object that contains the actual data.
+ private long mNativePtr;
+
+ private MotionEvent mNext;
+
+ private static native long nativeInitialize(long nativePtr,
+ int deviceId, int source, int action, int flags, int edgeFlags,
+ int metaState, int buttonState,
+ float xOffset, float yOffset, float xPrecision, float yPrecision,
+ long downTimeNanos, long eventTimeNanos,
+ int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords);
+ private static native void nativeDispose(long nativePtr);
+ private static native void nativeAddBatch(long nativePtr, long eventTimeNanos,
+ PointerCoords[] pointerCoords, int metaState);
+ private static native void nativeGetPointerCoords(long nativePtr,
+ int pointerIndex, int historyPos, PointerCoords outPointerCoords);
+ private static native void nativeGetPointerProperties(long nativePtr,
+ int pointerIndex, PointerProperties outPointerProperties);
+
+ private static native long nativeReadFromParcel(long nativePtr, Parcel parcel);
+ private static native void nativeWriteToParcel(long nativePtr, Parcel parcel);
+
+ private static native String nativeAxisToString(int axis);
+ private static native int nativeAxisFromString(String label);
+
+ // -------------- @FastNative -------------------------
+
+ @FastNative
+ private static native int nativeGetPointerId(long nativePtr, int pointerIndex);
+ @FastNative
+ private static native int nativeGetToolType(long nativePtr, int pointerIndex);
+ @FastNative
+ private static native long nativeGetEventTimeNanos(long nativePtr, int historyPos);
+ @FastNative
+ private static native float nativeGetRawAxisValue(long nativePtr,
+ int axis, int pointerIndex, int historyPos);
+ @FastNative
+ private static native float nativeGetAxisValue(long nativePtr,
+ int axis, int pointerIndex, int historyPos);
+
+ // -------------- @CriticalNative ----------------------
+
+ @CriticalNative
+ private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
+ boolean keepHistory);
+ @CriticalNative
+ private static native int nativeGetDeviceId(long nativePtr);
+ @CriticalNative
+ private static native int nativeGetSource(long nativePtr);
+ @CriticalNative
+ private static native int nativeSetSource(long nativePtr, int source);
+ @CriticalNative
+ private static native int nativeGetAction(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetAction(long nativePtr, int action);
+ @CriticalNative
+ private static native boolean nativeIsTouchEvent(long nativePtr);
+ @CriticalNative
+ private static native int nativeGetFlags(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetFlags(long nativePtr, int flags);
+ @CriticalNative
+ private static native int nativeGetEdgeFlags(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetEdgeFlags(long nativePtr, int action);
+ @CriticalNative
+ private static native int nativeGetMetaState(long nativePtr);
+ @CriticalNative
+ private static native int nativeGetButtonState(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetButtonState(long nativePtr, int buttonState);
+ @CriticalNative
+ private static native int nativeGetActionButton(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetActionButton(long nativePtr, int actionButton);
+ @CriticalNative
+ private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
+ @CriticalNative
+ private static native float nativeGetXOffset(long nativePtr);
+ @CriticalNative
+ private static native float nativeGetYOffset(long nativePtr);
+ @CriticalNative
+ private static native float nativeGetXPrecision(long nativePtr);
+ @CriticalNative
+ private static native float nativeGetYPrecision(long nativePtr);
+ @CriticalNative
+ private static native long nativeGetDownTimeNanos(long nativePtr);
+ @CriticalNative
+ private static native void nativeSetDownTimeNanos(long nativePtr, long downTime);
+
+ @CriticalNative
+ private static native int nativeGetPointerCount(long nativePtr);
+ @CriticalNative
+ private static native int nativeFindPointerIndex(long nativePtr, int pointerId);
+
+ @CriticalNative
+ private static native int nativeGetHistorySize(long nativePtr);
+
+ @CriticalNative
+ private static native void nativeScale(long nativePtr, float scale);
+ @CriticalNative
+ private static native void nativeTransform(long nativePtr, long matrix);
+
+ private MotionEvent() {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mNativePtr != 0) {
+ nativeDispose(mNativePtr);
+ mNativePtr = 0;
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ static private MotionEvent obtain() {
+ final MotionEvent ev;
+ synchronized (gRecyclerLock) {
+ ev = gRecyclerTop;
+ if (ev == null) {
+ return new MotionEvent();
+ }
+ gRecyclerTop = ev.mNext;
+ gRecyclerUsed -= 1;
+ }
+ ev.mNext = null;
+ ev.prepareForReuse();
+ return ev;
+ }
+
+ /**
+ * Create a new MotionEvent, filling in all of the basic values that
+ * define the motion.
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+ * @param pointerCount The number of pointers that will be in this event.
+ * @param pointerProperties An array of <em>pointerCount</em> values providing
+ * a {@link PointerProperties} property object for each pointer, which must
+ * include the pointer identifier.
+ * @param pointerCoords An array of <em>pointerCount</em> values providing
+ * a {@link PointerCoords} coordinate object for each pointer.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ * @param buttonState The state of buttons that are pressed.
+ * @param xPrecision The precision of the X coordinate being reported.
+ * @param yPrecision The precision of the Y coordinate being reported.
+ * @param deviceId The id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device; other
+ * numbers are arbitrary and you shouldn't depend on the values.
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+ * MotionEvent.
+ * @param source The source of this event.
+ * @param flags The motion event flags.
+ */
+ static public MotionEvent obtain(long downTime, long eventTime,
+ int action, int pointerCount, PointerProperties[] pointerProperties,
+ PointerCoords[] pointerCoords, int metaState, int buttonState,
+ float xPrecision, float yPrecision, int deviceId,
+ int edgeFlags, int source, int flags) {
+ MotionEvent ev = obtain();
+ ev.mNativePtr = nativeInitialize(ev.mNativePtr,
+ deviceId, source, action, flags, edgeFlags, metaState, buttonState,
+ 0, 0, xPrecision, yPrecision,
+ downTime * NS_PER_MS, eventTime * NS_PER_MS,
+ pointerCount, pointerProperties, pointerCoords);
+ return ev;
+ }
+
+ /**
+ * Create a new MotionEvent, filling in all of the basic values that
+ * define the motion.
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+ * @param pointerCount The number of pointers that will be in this event.
+ * @param pointerIds An array of <em>pointerCount</em> values providing
+ * an identifier for each pointer.
+ * @param pointerCoords An array of <em>pointerCount</em> values providing
+ * a {@link PointerCoords} coordinate object for each pointer.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ * @param xPrecision The precision of the X coordinate being reported.
+ * @param yPrecision The precision of the Y coordinate being reported.
+ * @param deviceId The id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device; other
+ * numbers are arbitrary and you shouldn't depend on the values.
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+ * MotionEvent.
+ * @param source The source of this event.
+ * @param flags The motion event flags.
+ *
+ * @deprecated Use {@link #obtain(long, long, int, int, PointerProperties[], PointerCoords[], int, int, float, float, int, int, int, int)}
+ * instead.
+ */
+ @Deprecated
+ static public MotionEvent obtain(long downTime, long eventTime,
+ int action, int pointerCount, int[] pointerIds, PointerCoords[] pointerCoords,
+ int metaState, float xPrecision, float yPrecision, int deviceId,
+ int edgeFlags, int source, int flags) {
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(pointerCount);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ for (int i = 0; i < pointerCount; i++) {
+ pp[i].clear();
+ pp[i].id = pointerIds[i];
+ }
+ return obtain(downTime, eventTime, action, pointerCount, pp,
+ pointerCoords, metaState, 0, xPrecision, yPrecision, deviceId,
+ edgeFlags, source, flags);
+ }
+ }
+
+ /**
+ * Create a new MotionEvent, filling in all of the basic values that
+ * define the motion.
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+ * @param x The X coordinate of this event.
+ * @param y The Y coordinate of this event.
+ * @param pressure The current pressure of this event. The pressure generally
+ * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+ * values higher than 1 may be generated depending on the calibration of
+ * the input device.
+ * @param size A scaled value of the approximate size of the area being pressed when
+ * touched with the finger. The actual value in pixels corresponding to the finger
+ * touch is normalized with a device specific range of values
+ * and scaled to a value between 0 and 1.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ * @param xPrecision The precision of the X coordinate being reported.
+ * @param yPrecision The precision of the Y coordinate being reported.
+ * @param deviceId The id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device; other
+ * numbers are arbitrary and you shouldn't depend on the values.
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+ * MotionEvent.
+ */
+ static public MotionEvent obtain(long downTime, long eventTime, int action,
+ float x, float y, float pressure, float size, int metaState,
+ float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
+ MotionEvent ev = obtain();
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(1);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ pp[0].clear();
+ pp[0].id = 0;
+
+ final PointerCoords pc[] = gSharedTempPointerCoords;
+ pc[0].clear();
+ pc[0].x = x;
+ pc[0].y = y;
+ pc[0].pressure = pressure;
+ pc[0].size = size;
+
+ ev.mNativePtr = nativeInitialize(ev.mNativePtr,
+ deviceId, InputDevice.SOURCE_UNKNOWN, action, 0, edgeFlags, metaState, 0,
+ 0, 0, xPrecision, yPrecision,
+ downTime * NS_PER_MS, eventTime * NS_PER_MS,
+ 1, pp, pc);
+ return ev;
+ }
+ }
+
+ /**
+ * Create a new MotionEvent, filling in all of the basic values that
+ * define the motion.
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+ * @param pointerCount The number of pointers that are active in this event.
+ * @param x The X coordinate of this event.
+ * @param y The Y coordinate of this event.
+ * @param pressure The current pressure of this event. The pressure generally
+ * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+ * values higher than 1 may be generated depending on the calibration of
+ * the input device.
+ * @param size A scaled value of the approximate size of the area being pressed when
+ * touched with the finger. The actual value in pixels corresponding to the finger
+ * touch is normalized with a device specific range of values
+ * and scaled to a value between 0 and 1.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ * @param xPrecision The precision of the X coordinate being reported.
+ * @param yPrecision The precision of the Y coordinate being reported.
+ * @param deviceId The id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device; other
+ * numbers are arbitrary and you shouldn't depend on the values.
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+ * MotionEvent.
+ *
+ * @deprecated Use {@link #obtain(long, long, int, float, float, float, float, int, float, float, int, int)}
+ * instead.
+ */
+ @Deprecated
+ static public MotionEvent obtain(long downTime, long eventTime, int action,
+ int pointerCount, float x, float y, float pressure, float size, int metaState,
+ float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
+ return obtain(downTime, eventTime, action, x, y, pressure, size,
+ metaState, xPrecision, yPrecision, deviceId, edgeFlags);
+ }
+
+ /**
+ * Create a new MotionEvent, filling in a subset of the basic motion
+ * values. Those not specified here are: device id (always 0), pressure
+ * and size (always 1), x and y precision (always 1), and edgeFlags (always 0).
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+ * @param x The X coordinate of this event.
+ * @param y The Y coordinate of this event.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ */
+ static public MotionEvent obtain(long downTime, long eventTime, int action,
+ float x, float y, int metaState) {
+ return obtain(downTime, eventTime, action, x, y, 1.0f, 1.0f,
+ metaState, 1.0f, 1.0f, 0, 0);
+ }
+
+ /**
+ * Create a new MotionEvent, copying from an existing one.
+ */
+ static public MotionEvent obtain(MotionEvent other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other motion event must not be null");
+ }
+
+ MotionEvent ev = obtain();
+ ev.mNativePtr = nativeCopy(ev.mNativePtr, other.mNativePtr, true /*keepHistory*/);
+ return ev;
+ }
+
+ /**
+ * Create a new MotionEvent, copying from an existing one, but not including
+ * any historical point information.
+ */
+ static public MotionEvent obtainNoHistory(MotionEvent other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other motion event must not be null");
+ }
+
+ MotionEvent ev = obtain();
+ ev.mNativePtr = nativeCopy(ev.mNativePtr, other.mNativePtr, false /*keepHistory*/);
+ return ev;
+ }
+
+ /** @hide */
+ @Override
+ public MotionEvent copy() {
+ return obtain(this);
+ }
+
+ /**
+ * Recycle the MotionEvent, to be re-used by a later caller. After calling
+ * this function you must not ever touch the event again.
+ */
+ @Override
+ public final void recycle() {
+ super.recycle();
+
+ synchronized (gRecyclerLock) {
+ if (gRecyclerUsed < MAX_RECYCLED) {
+ gRecyclerUsed++;
+ mNext = gRecyclerTop;
+ gRecyclerTop = this;
+ }
+ }
+ }
+
+ /**
+ * Applies a scale factor to all points within this event.
+ *
+ * This method is used to adjust touch events to simulate different density
+ * displays for compatibility mode. The values returned by {@link #getRawX()},
+ * {@link #getRawY()}, {@link #getXPrecision()} and {@link #getYPrecision()}
+ * are also affected by the scale factor.
+ *
+ * @param scale The scale factor to apply.
+ * @hide
+ */
+ public final void scale(float scale) {
+ if (scale != 1.0f) {
+ nativeScale(mNativePtr, scale);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final int getDeviceId() {
+ return nativeGetDeviceId(mNativePtr);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final int getSource() {
+ return nativeGetSource(mNativePtr);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final void setSource(int source) {
+ nativeSetSource(mNativePtr, source);
+ }
+
+ /**
+ * Return the kind of action being performed.
+ * Consider using {@link #getActionMasked} and {@link #getActionIndex} to retrieve
+ * the separate masked action and pointer index.
+ * @return The action, such as {@link #ACTION_DOWN} or
+ * the combination of {@link #ACTION_POINTER_DOWN} with a shifted pointer index.
+ */
+ public final int getAction() {
+ return nativeGetAction(mNativePtr);
+ }
+
+ /**
+ * Return the masked action being performed, without pointer index information.
+ * Use {@link #getActionIndex} to return the index associated with pointer actions.
+ * @return The action, such as {@link #ACTION_DOWN} or {@link #ACTION_POINTER_DOWN}.
+ */
+ public final int getActionMasked() {
+ return nativeGetAction(mNativePtr) & ACTION_MASK;
+ }
+
+ /**
+ * For {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP}
+ * as returned by {@link #getActionMasked}, this returns the associated
+ * pointer index.
+ * The index may be used with {@link #getPointerId(int)},
+ * {@link #getX(int)}, {@link #getY(int)}, {@link #getPressure(int)},
+ * and {@link #getSize(int)} to get information about the pointer that has
+ * gone down or up.
+ * @return The index associated with the action.
+ */
+ public final int getActionIndex() {
+ return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
+ >> ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ /**
+ * Returns true if this motion event is a touch event.
+ * <p>
+ * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE},
+ * {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_EXIT}, or {@link #ACTION_SCROLL}
+ * because they are not actually touch events (the pointer is not down).
+ * </p>
+ * @return True if this motion event is a touch event.
+ * @hide
+ */
+ public final boolean isTouchEvent() {
+ return nativeIsTouchEvent(mNativePtr);
+ }
+
+ /**
+ * Gets the motion event flags.
+ *
+ * @see #FLAG_WINDOW_IS_OBSCURED
+ */
+ public final int getFlags() {
+ return nativeGetFlags(mNativePtr);
+ }
+
+ /** @hide */
+ @Override
+ public final boolean isTainted() {
+ final int flags = getFlags();
+ return (flags & FLAG_TAINTED) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public final void setTainted(boolean tainted) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
+ }
+
+ /** @hide */
+ public final boolean isTargetAccessibilityFocus() {
+ final int flags = getFlags();
+ return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0;
+ }
+
+ /** @hide */
+ public final void setTargetAccessibilityFocus(boolean targetsFocus) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, targetsFocus
+ ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS
+ : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
+ }
+
+ /** @hide */
+ public final boolean isHoverExitPending() {
+ final int flags = getFlags();
+ return (flags & FLAG_HOVER_EXIT_PENDING) != 0;
+ }
+
+ /** @hide */
+ public void setHoverExitPending(boolean hoverExitPending) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, hoverExitPending
+ ? flags | FLAG_HOVER_EXIT_PENDING
+ : flags & ~FLAG_HOVER_EXIT_PENDING);
+ }
+
+ /**
+ * Returns the time (in ms) when the user originally pressed down to start
+ * a stream of position events.
+ */
+ public final long getDownTime() {
+ return nativeGetDownTimeNanos(mNativePtr) / NS_PER_MS;
+ }
+
+ /**
+ * Sets the time (in ms) when the user originally pressed down to start
+ * a stream of position events.
+ *
+ * @hide
+ */
+ public final void setDownTime(long downTime) {
+ nativeSetDownTimeNanos(mNativePtr, downTime * NS_PER_MS);
+ }
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ */
+ @Override
+ public final long getEventTime() {
+ return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT) / NS_PER_MS;
+ }
+
+ /**
+ * Retrieve the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond precision.
+ * <p>
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ * </p>
+ *
+ * @return Returns the time this event occurred,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond precision.
+ *
+ * @hide
+ */
+ @Override
+ public final long getEventTimeNano() {
+ return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getX(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_X
+ */
+ public final float getX() {
+ return nativeGetAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getY(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_Y
+ */
+ public final float getY() {
+ return nativeGetAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getPressure(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_PRESSURE
+ */
+ public final float getPressure() {
+ return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getSize(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_SIZE
+ */
+ public final float getSize() {
+ return nativeGetAxisValue(mNativePtr, AXIS_SIZE, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getTouchMajor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_TOUCH_MAJOR
+ */
+ public final float getTouchMajor() {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getTouchMinor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_TOUCH_MINOR
+ */
+ public final float getTouchMinor() {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getToolMajor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_TOOL_MAJOR
+ */
+ public final float getToolMajor() {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getToolMinor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_TOOL_MINOR
+ */
+ public final float getToolMinor() {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getOrientation(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @see #AXIS_ORIENTATION
+ */
+ public final float getOrientation() {
+ return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * {@link #getAxisValue(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param axis The axis identifier for the axis value to retrieve.
+ *
+ * @see #AXIS_X
+ * @see #AXIS_Y
+ */
+ public final float getAxisValue(int axis) {
+ return nativeGetAxisValue(mNativePtr, axis, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * The number of pointers of data contained in this event. Always
+ * >= 1.
+ */
+ public final int getPointerCount() {
+ return nativeGetPointerCount(mNativePtr);
+ }
+
+ /**
+ * Return the pointer identifier associated with a particular pointer
+ * data index in this event. The identifier tells you the actual pointer
+ * number associated with the data, accounting for individual pointers
+ * going up and down since the start of the current gesture.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ */
+ public final int getPointerId(int pointerIndex) {
+ return nativeGetPointerId(mNativePtr, pointerIndex);
+ }
+
+ /**
+ * Gets the tool type of a pointer for the given pointer index.
+ * The tool type indicates the type of tool used to make contact such
+ * as a finger or stylus, if known.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @return The tool type of the pointer.
+ *
+ * @see #TOOL_TYPE_UNKNOWN
+ * @see #TOOL_TYPE_FINGER
+ * @see #TOOL_TYPE_STYLUS
+ * @see #TOOL_TYPE_MOUSE
+ */
+ public final int getToolType(int pointerIndex) {
+ return nativeGetToolType(mNativePtr, pointerIndex);
+ }
+
+ /**
+ * Given a pointer identifier, find the index of its data in the event.
+ *
+ * @param pointerId The identifier of the pointer to be found.
+ * @return Returns either the index of the pointer (for use with
+ * {@link #getX(int)} et al.), or -1 if there is no data available for
+ * that pointer identifier.
+ */
+ public final int findPointerIndex(int pointerId) {
+ return nativeFindPointerIndex(mNativePtr, pointerId);
+ }
+
+ /**
+ * Returns the X coordinate of this event for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * Whole numbers are pixels; the
+ * value may have a fraction for input devices that are sub-pixel precise.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_X
+ */
+ public final float getX(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the Y coordinate of this event for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * Whole numbers are pixels; the
+ * value may have a fraction for input devices that are sub-pixel precise.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_Y
+ */
+ public final float getY(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_Y, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the current pressure of this event for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * The pressure generally
+ * ranges from 0 (no pressure at all) to 1 (normal pressure), however
+ * values higher than 1 may be generated depending on the calibration of
+ * the input device.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_PRESSURE
+ */
+ public final float getPressure(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns a scaled value of the approximate size for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1. The value of size can be used to
+ * determine fat touch events.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_SIZE
+ */
+ public final float getSize(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_SIZE, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the length of the major axis of an ellipse that describes the touch
+ * area at the point of contact for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_TOUCH_MAJOR
+ */
+ public final float getTouchMajor(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the length of the minor axis of an ellipse that describes the touch
+ * area at the point of contact for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_TOUCH_MINOR
+ */
+ public final float getTouchMinor(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the length of the major axis of an ellipse that describes the size of
+ * the approaching tool for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_TOOL_MAJOR
+ */
+ public final float getToolMajor(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the length of the minor axis of an ellipse that describes the size of
+ * the approaching tool for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_TOOL_MINOR
+ */
+ public final float getToolMinor(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the orientation of the touch area and tool area in radians clockwise from vertical
+ * for the given pointer <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * An angle of 0 radians indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right).
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ *
+ * @see #AXIS_ORIENTATION
+ */
+ public final float getOrientation(int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the value of the requested axis for the given pointer <em>index</em>
+ * (use {@link #getPointerId(int)} to find the pointer identifier for this index).
+ *
+ * @param axis The axis identifier for the axis value to retrieve.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @return The value of the axis, or 0 if the axis is not available.
+ *
+ * @see #AXIS_X
+ * @see #AXIS_Y
+ */
+ public final float getAxisValue(int axis, int pointerIndex) {
+ return nativeGetAxisValue(mNativePtr, axis, pointerIndex, HISTORY_CURRENT);
+ }
+
+ /**
+ * Populates a {@link PointerCoords} object with pointer coordinate data for
+ * the specified pointer index.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param outPointerCoords The pointer coordinate object to populate.
+ *
+ * @see PointerCoords
+ */
+ public final void getPointerCoords(int pointerIndex, PointerCoords outPointerCoords) {
+ nativeGetPointerCoords(mNativePtr, pointerIndex, HISTORY_CURRENT, outPointerCoords);
+ }
+
+ /**
+ * Populates a {@link PointerProperties} object with pointer properties for
+ * the specified pointer index.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param outPointerProperties The pointer properties object to populate.
+ *
+ * @see PointerProperties
+ */
+ public final void getPointerProperties(int pointerIndex,
+ PointerProperties outPointerProperties) {
+ nativeGetPointerProperties(mNativePtr, pointerIndex, outPointerProperties);
+ }
+
+ /**
+ * Returns the state of any meta / modifier keys that were in effect when
+ * the event was generated. This is the same values as those
+ * returned by {@link KeyEvent#getMetaState() KeyEvent.getMetaState}.
+ *
+ * @return an integer in which each bit set to 1 represents a pressed
+ * meta key
+ *
+ * @see KeyEvent#getMetaState()
+ */
+ public final int getMetaState() {
+ return nativeGetMetaState(mNativePtr);
+ }
+
+ /**
+ * Gets the state of all buttons that are pressed such as a mouse or stylus button.
+ *
+ * @return The button state.
+ *
+ * @see #BUTTON_PRIMARY
+ * @see #BUTTON_SECONDARY
+ * @see #BUTTON_TERTIARY
+ * @see #BUTTON_FORWARD
+ * @see #BUTTON_BACK
+ * @see #BUTTON_STYLUS_PRIMARY
+ * @see #BUTTON_STYLUS_SECONDARY
+ */
+ public final int getButtonState() {
+ return nativeGetButtonState(mNativePtr);
+ }
+
+ /**
+ * Sets the bitfield indicating which buttons are pressed.
+ *
+ * @see #getButtonState()
+ * @hide
+ */
+ @TestApi
+ public final void setButtonState(int buttonState) {
+ nativeSetButtonState(mNativePtr, buttonState);
+ }
+
+ /**
+ * Gets which button has been modified during a press or release action.
+ *
+ * For actions other than {@link #ACTION_BUTTON_PRESS} and {@link #ACTION_BUTTON_RELEASE}
+ * the returned value is undefined.
+ *
+ * @see #getButtonState()
+ */
+ public final int getActionButton() {
+ return nativeGetActionButton(mNativePtr);
+ }
+
+ /**
+ * Sets the action button for the event.
+ *
+ * @see #getActionButton()
+ * @hide
+ */
+ @TestApi
+ public final void setActionButton(int button) {
+ nativeSetActionButton(mNativePtr, button);
+ }
+
+ /**
+ * Returns the original raw X coordinate of this event. For touch
+ * events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ *
+ * @see #getX(int)
+ * @see #AXIS_X
+ */
+ public final float getRawX() {
+ return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * Returns the original raw Y coordinate of this event. For touch
+ * events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ *
+ * @see #getY(int)
+ * @see #AXIS_Y
+ */
+ public final float getRawY() {
+ return nativeGetRawAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
+ }
+
+ /**
+ * Return the precision of the X coordinates being reported. You can
+ * multiply this number with {@link #getX} to find the actual hardware
+ * value of the X coordinate.
+ * @return Returns the precision of X coordinates being reported.
+ *
+ * @see #AXIS_X
+ */
+ public final float getXPrecision() {
+ return nativeGetXPrecision(mNativePtr);
+ }
+
+ /**
+ * Return the precision of the Y coordinates being reported. You can
+ * multiply this number with {@link #getY} to find the actual hardware
+ * value of the Y coordinate.
+ * @return Returns the precision of Y coordinates being reported.
+ *
+ * @see #AXIS_Y
+ */
+ public final float getYPrecision() {
+ return nativeGetYPrecision(mNativePtr);
+ }
+
+ /**
+ * Returns the number of historical points in this event. These are
+ * movements that have occurred between this event and the previous event.
+ * This only applies to ACTION_MOVE events -- all other actions will have
+ * a size of 0.
+ *
+ * @return Returns the number of historical points in the event.
+ */
+ public final int getHistorySize() {
+ return nativeGetHistorySize(mNativePtr);
+ }
+
+ /**
+ * Returns the time that a historical movement occurred between this event
+ * and the previous event, in the {@link android.os.SystemClock#uptimeMillis} time base.
+ * <p>
+ * This only applies to ACTION_MOVE events.
+ * </p>
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ * @return Returns the time that a historical movement occurred between this
+ * event and the previous event,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base.
+ *
+ * @see #getHistorySize
+ * @see #getEventTime
+ */
+ public final long getHistoricalEventTime(int pos) {
+ return nativeGetEventTimeNanos(mNativePtr, pos) / NS_PER_MS;
+ }
+
+ /**
+ * Returns the time that a historical movement occurred between this event
+ * and the previous event, in the {@link android.os.SystemClock#uptimeMillis} time base
+ * but with nanosecond (instead of millisecond) precision.
+ * <p>
+ * This only applies to ACTION_MOVE events.
+ * </p><p>
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ * </p>
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ * @return Returns the time that a historical movement occurred between this
+ * event and the previous event,
+ * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+ * nanosecond (instead of millisecond) precision.
+ *
+ * @see #getHistorySize
+ * @see #getEventTime
+ *
+ * @hide
+ */
+ public final long getHistoricalEventTimeNano(int pos) {
+ return nativeGetEventTimeNanos(mNativePtr, pos);
+ }
+
+ /**
+ * {@link #getHistoricalX(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getX()
+ * @see #AXIS_X
+ */
+ public final float getHistoricalX(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_X, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalY(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getY()
+ * @see #AXIS_Y
+ */
+ public final float getHistoricalY(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_Y, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalPressure(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getPressure()
+ * @see #AXIS_PRESSURE
+ */
+ public final float getHistoricalPressure(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalSize(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getSize()
+ * @see #AXIS_SIZE
+ */
+ public final float getHistoricalSize(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_SIZE, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalTouchMajor(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getTouchMajor()
+ * @see #AXIS_TOUCH_MAJOR
+ */
+ public final float getHistoricalTouchMajor(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalTouchMinor(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getTouchMinor()
+ * @see #AXIS_TOUCH_MINOR
+ */
+ public final float getHistoricalTouchMinor(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalToolMajor(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getToolMajor()
+ * @see #AXIS_TOOL_MAJOR
+ */
+ public final float getHistoricalToolMajor(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalToolMinor(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getToolMinor()
+ * @see #AXIS_TOOL_MINOR
+ */
+ public final float getHistoricalToolMinor(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalOrientation(int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getOrientation()
+ * @see #AXIS_ORIENTATION
+ */
+ public final float getHistoricalOrientation(int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, 0, pos);
+ }
+
+ /**
+ * {@link #getHistoricalAxisValue(int, int, int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ *
+ * @param axis The axis identifier for the axis value to retrieve.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getAxisValue(int)
+ * @see #AXIS_X
+ * @see #AXIS_Y
+ */
+ public final float getHistoricalAxisValue(int axis, int pos) {
+ return nativeGetAxisValue(mNativePtr, axis, 0, pos);
+ }
+
+ /**
+ * Returns a historical X coordinate, as per {@link #getX(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getX(int)
+ * @see #AXIS_X
+ */
+ public final float getHistoricalX(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_X, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical Y coordinate, as per {@link #getY(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getY(int)
+ * @see #AXIS_Y
+ */
+ public final float getHistoricalY(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_Y, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical pressure coordinate, as per {@link #getPressure(int)},
+ * that occurred between this event and the previous event for the given
+ * pointer. Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getPressure(int)
+ * @see #AXIS_PRESSURE
+ */
+ public final float getHistoricalPressure(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_PRESSURE, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical size coordinate, as per {@link #getSize(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getSize(int)
+ * @see #AXIS_SIZE
+ */
+ public final float getHistoricalSize(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_SIZE, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical touch major axis coordinate, as per {@link #getTouchMajor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getTouchMajor(int)
+ * @see #AXIS_TOUCH_MAJOR
+ */
+ public final float getHistoricalTouchMajor(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MAJOR, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical touch minor axis coordinate, as per {@link #getTouchMinor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getTouchMinor(int)
+ * @see #AXIS_TOUCH_MINOR
+ */
+ public final float getHistoricalTouchMinor(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOUCH_MINOR, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical tool major axis coordinate, as per {@link #getToolMajor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getToolMajor(int)
+ * @see #AXIS_TOOL_MAJOR
+ */
+ public final float getHistoricalToolMajor(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MAJOR, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical tool minor axis coordinate, as per {@link #getToolMinor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getToolMinor(int)
+ * @see #AXIS_TOOL_MINOR
+ */
+ public final float getHistoricalToolMinor(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_TOOL_MINOR, pointerIndex, pos);
+ }
+
+ /**
+ * Returns a historical orientation coordinate, as per {@link #getOrientation(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getOrientation(int)
+ * @see #AXIS_ORIENTATION
+ */
+ public final float getHistoricalOrientation(int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, AXIS_ORIENTATION, pointerIndex, pos);
+ }
+
+ /**
+ * Returns the historical value of the requested axis, as per {@link #getAxisValue(int, int)},
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param axis The axis identifier for the axis value to retrieve.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ * @return The value of the axis, or 0 if the axis is not available.
+ *
+ * @see #AXIS_X
+ * @see #AXIS_Y
+ */
+ public final float getHistoricalAxisValue(int axis, int pointerIndex, int pos) {
+ return nativeGetAxisValue(mNativePtr, axis, pointerIndex, pos);
+ }
+
+ /**
+ * Populates a {@link PointerCoords} object with historical pointer coordinate data,
+ * as per {@link #getPointerCoords}, that occurred between this event and the previous
+ * event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ * @param outPointerCoords The pointer coordinate object to populate.
+ *
+ * @see #getHistorySize
+ * @see #getPointerCoords
+ * @see PointerCoords
+ */
+ public final void getHistoricalPointerCoords(int pointerIndex, int pos,
+ PointerCoords outPointerCoords) {
+ nativeGetPointerCoords(mNativePtr, pointerIndex, pos, outPointerCoords);
+ }
+
+ /**
+ * Returns a bitfield indicating which edges, if any, were touched by this
+ * MotionEvent. For touch events, clients can use this to determine if the
+ * user's finger was touching the edge of the display.
+ *
+ * This property is only set for {@link #ACTION_DOWN} events.
+ *
+ * @see #EDGE_LEFT
+ * @see #EDGE_TOP
+ * @see #EDGE_RIGHT
+ * @see #EDGE_BOTTOM
+ */
+ public final int getEdgeFlags() {
+ return nativeGetEdgeFlags(mNativePtr);
+ }
+
+ /**
+ * Sets the bitfield indicating which edges, if any, were touched by this
+ * MotionEvent.
+ *
+ * @see #getEdgeFlags()
+ */
+ public final void setEdgeFlags(int flags) {
+ nativeSetEdgeFlags(mNativePtr, flags);
+ }
+
+ /**
+ * Sets this event's action.
+ */
+ public final void setAction(int action) {
+ nativeSetAction(mNativePtr, action);
+ }
+
+ /**
+ * Adjust this event's location.
+ * @param deltaX Amount to add to the current X coordinate of the event.
+ * @param deltaY Amount to add to the current Y coordinate of the event.
+ */
+ public final void offsetLocation(float deltaX, float deltaY) {
+ if (deltaX != 0.0f || deltaY != 0.0f) {
+ nativeOffsetLocation(mNativePtr, deltaX, deltaY);
+ }
+ }
+
+ /**
+ * Set this event's location. Applies {@link #offsetLocation} with a
+ * delta from the current location to the given new location.
+ *
+ * @param x New absolute X location.
+ * @param y New absolute Y location.
+ */
+ public final void setLocation(float x, float y) {
+ float oldX = getX();
+ float oldY = getY();
+ offsetLocation(x - oldX, y - oldY);
+ }
+
+ /**
+ * Applies a transformation matrix to all of the points in the event.
+ *
+ * @param matrix The transformation matrix to apply.
+ */
+ public final void transform(Matrix matrix) {
+ if (matrix == null) {
+ throw new IllegalArgumentException("matrix must not be null");
+ }
+
+ nativeTransform(mNativePtr, matrix.native_instance);
+ }
+
+ /**
+ * Add a new movement to the batch of movements in this event. The event's
+ * current location, position and size is updated to the new values.
+ * The current values in the event are added to a list of historical values.
+ *
+ * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+ *
+ * @param eventTime The time stamp (in ms) for this data.
+ * @param x The new X position.
+ * @param y The new Y position.
+ * @param pressure The new pressure.
+ * @param size The new size.
+ * @param metaState Meta key state.
+ */
+ public final void addBatch(long eventTime, float x, float y,
+ float pressure, float size, int metaState) {
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(1);
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+ pc[0].clear();
+ pc[0].x = x;
+ pc[0].y = y;
+ pc[0].pressure = pressure;
+ pc[0].size = size;
+
+ nativeAddBatch(mNativePtr, eventTime * NS_PER_MS, pc, metaState);
+ }
+ }
+
+ /**
+ * Add a new movement to the batch of movements in this event. The event's
+ * current location, position and size is updated to the new values.
+ * The current values in the event are added to a list of historical values.
+ *
+ * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+ *
+ * @param eventTime The time stamp (in ms) for this data.
+ * @param pointerCoords The new pointer coordinates.
+ * @param metaState Meta key state.
+ */
+ public final void addBatch(long eventTime, PointerCoords[] pointerCoords, int metaState) {
+ nativeAddBatch(mNativePtr, eventTime * NS_PER_MS, pointerCoords, metaState);
+ }
+
+ /**
+ * Adds all of the movement samples of the specified event to this one if
+ * it is compatible. To be compatible, the event must have the same device id,
+ * source, action, flags, pointer count, pointer properties.
+ *
+ * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+ *
+ * @param event The event whose movements samples should be added to this one
+ * if possible.
+ * @return True if batching was performed or false if batching was not possible.
+ * @hide
+ */
+ public final boolean addBatch(MotionEvent event) {
+ final int action = nativeGetAction(mNativePtr);
+ if (action != ACTION_MOVE && action != ACTION_HOVER_MOVE) {
+ return false;
+ }
+ if (action != nativeGetAction(event.mNativePtr)) {
+ return false;
+ }
+
+ if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
+ || nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
+ || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) {
+ return false;
+ }
+
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+ if (pointerCount != nativeGetPointerCount(event.mNativePtr)) {
+ return false;
+ }
+
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(Math.max(pointerCount, 2));
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerProperties(mNativePtr, i, pp[0]);
+ nativeGetPointerProperties(event.mNativePtr, i, pp[1]);
+ if (!pp[0].equals(pp[1])) {
+ return false;
+ }
+ }
+
+ final int metaState = nativeGetMetaState(event.mNativePtr);
+ final int historySize = nativeGetHistorySize(event.mNativePtr);
+ for (int h = 0; h <= historySize; h++) {
+ final int historyPos = (h == historySize ? HISTORY_CURRENT : h);
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerCoords(event.mNativePtr, i, historyPos, pc[i]);
+ }
+
+ final long eventTimeNanos = nativeGetEventTimeNanos(event.mNativePtr, historyPos);
+ nativeAddBatch(mNativePtr, eventTimeNanos, pc, metaState);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if all points in the motion event are completely within the specified bounds.
+ * @hide
+ */
+ public final boolean isWithinBoundsNoHistory(float left, float top,
+ float right, float bottom) {
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+ for (int i = 0; i < pointerCount; i++) {
+ final float x = nativeGetAxisValue(mNativePtr, AXIS_X, i, HISTORY_CURRENT);
+ final float y = nativeGetAxisValue(mNativePtr, AXIS_Y, i, HISTORY_CURRENT);
+ if (x < left || x > right || y < top || y > bottom) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static final float clamp(float value, float low, float high) {
+ if (value < low) {
+ return low;
+ } else if (value > high) {
+ return high;
+ }
+ return value;
+ }
+
+ /**
+ * Returns a new motion events whose points have been clamped to the specified bounds.
+ * @hide
+ */
+ public final MotionEvent clampNoHistory(float left, float top, float right, float bottom) {
+ MotionEvent ev = obtain();
+ synchronized (gSharedTempLock) {
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+
+ ensureSharedTempPointerCapacity(pointerCount);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerProperties(mNativePtr, i, pp[i]);
+ nativeGetPointerCoords(mNativePtr, i, HISTORY_CURRENT, pc[i]);
+ pc[i].x = clamp(pc[i].x, left, right);
+ pc[i].y = clamp(pc[i].y, top, bottom);
+ }
+ ev.mNativePtr = nativeInitialize(ev.mNativePtr,
+ nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
+ nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
+ nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
+ nativeGetButtonState(mNativePtr),
+ nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+ nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
+ nativeGetDownTimeNanos(mNativePtr),
+ nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
+ pointerCount, pp, pc);
+ return ev;
+ }
+ }
+
+ /**
+ * Gets an integer where each pointer id present in the event is marked as a bit.
+ * @hide
+ */
+ public final int getPointerIdBits() {
+ int idBits = 0;
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+ for (int i = 0; i < pointerCount; i++) {
+ idBits |= 1 << nativeGetPointerId(mNativePtr, i);
+ }
+ return idBits;
+ }
+
+ /**
+ * Splits a motion event such that it includes only a subset of pointer ids.
+ * @hide
+ */
+ public final MotionEvent split(int idBits) {
+ MotionEvent ev = obtain();
+ synchronized (gSharedTempLock) {
+ final int oldPointerCount = nativeGetPointerCount(mNativePtr);
+ ensureSharedTempPointerCapacity(oldPointerCount);
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+ final int[] map = gSharedTempPointerIndexMap;
+
+ final int oldAction = nativeGetAction(mNativePtr);
+ final int oldActionMasked = oldAction & ACTION_MASK;
+ final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
+ >> ACTION_POINTER_INDEX_SHIFT;
+ int newActionPointerIndex = -1;
+ int newPointerCount = 0;
+ int newIdBits = 0;
+ for (int i = 0; i < oldPointerCount; i++) {
+ nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
+ final int idBit = 1 << pp[newPointerCount].id;
+ if ((idBit & idBits) != 0) {
+ if (i == oldActionPointerIndex) {
+ newActionPointerIndex = newPointerCount;
+ }
+ map[newPointerCount] = i;
+ newPointerCount += 1;
+ newIdBits |= idBit;
+ }
+ }
+
+ if (newPointerCount == 0) {
+ throw new IllegalArgumentException("idBits did not match any ids in the event");
+ }
+
+ final int newAction;
+ if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
+ if (newActionPointerIndex < 0) {
+ // An unrelated pointer changed.
+ newAction = ACTION_MOVE;
+ } else if (newPointerCount == 1) {
+ // The first/last pointer went down/up.
+ newAction = oldActionMasked == ACTION_POINTER_DOWN
+ ? ACTION_DOWN : ACTION_UP;
+ } else {
+ // A secondary pointer went down/up.
+ newAction = oldActionMasked
+ | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
+ }
+ } else {
+ // Simple up/down/cancel/move or other motion action.
+ newAction = oldAction;
+ }
+
+ final int historySize = nativeGetHistorySize(mNativePtr);
+ for (int h = 0; h <= historySize; h++) {
+ final int historyPos = h == historySize ? HISTORY_CURRENT : h;
+
+ for (int i = 0; i < newPointerCount; i++) {
+ nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
+ }
+
+ final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
+ if (h == 0) {
+ ev.mNativePtr = nativeInitialize(ev.mNativePtr,
+ nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
+ newAction, nativeGetFlags(mNativePtr),
+ nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
+ nativeGetButtonState(mNativePtr),
+ nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+ nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
+ nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
+ newPointerCount, pp, pc);
+ } else {
+ nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
+ }
+ }
+ return ev;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder msg = new StringBuilder();
+ msg.append("MotionEvent { action=").append(actionToString(getAction()));
+ msg.append(", actionButton=").append(buttonStateToString(getActionButton()));
+
+ final int pointerCount = getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ msg.append(", id[").append(i).append("]=").append(getPointerId(i));
+ msg.append(", x[").append(i).append("]=").append(getX(i));
+ msg.append(", y[").append(i).append("]=").append(getY(i));
+ msg.append(", toolType[").append(i).append("]=").append(
+ toolTypeToString(getToolType(i)));
+ }
+
+ msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState()));
+ msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState()));
+ msg.append(", flags=0x").append(Integer.toHexString(getFlags()));
+ msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags()));
+ msg.append(", pointerCount=").append(pointerCount);
+ msg.append(", historySize=").append(getHistorySize());
+ msg.append(", eventTime=").append(getEventTime());
+ msg.append(", downTime=").append(getDownTime());
+ msg.append(", deviceId=").append(getDeviceId());
+ msg.append(", source=0x").append(Integer.toHexString(getSource()));
+ msg.append(" }");
+ return msg.toString();
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified unmasked action
+ * such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant
+ * such as "35" if unknown.
+ *
+ * @param action The unmasked action.
+ * @return The symbolic name of the specified action.
+ * @see #getAction()
+ */
+ public static String actionToString(int action) {
+ switch (action) {
+ case ACTION_DOWN:
+ return "ACTION_DOWN";
+ case ACTION_UP:
+ return "ACTION_UP";
+ case ACTION_CANCEL:
+ return "ACTION_CANCEL";
+ case ACTION_OUTSIDE:
+ return "ACTION_OUTSIDE";
+ case ACTION_MOVE:
+ return "ACTION_MOVE";
+ case ACTION_HOVER_MOVE:
+ return "ACTION_HOVER_MOVE";
+ case ACTION_SCROLL:
+ return "ACTION_SCROLL";
+ case ACTION_HOVER_ENTER:
+ return "ACTION_HOVER_ENTER";
+ case ACTION_HOVER_EXIT:
+ return "ACTION_HOVER_EXIT";
+ case ACTION_BUTTON_PRESS:
+ return "ACTION_BUTTON_PRESS";
+ case ACTION_BUTTON_RELEASE:
+ return "ACTION_BUTTON_RELEASE";
+ }
+ int index = (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
+ switch (action & ACTION_MASK) {
+ case ACTION_POINTER_DOWN:
+ return "ACTION_POINTER_DOWN(" + index + ")";
+ case ACTION_POINTER_UP:
+ return "ACTION_POINTER_UP(" + index + ")";
+ default:
+ return Integer.toString(action);
+ }
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified axis
+ * such as "AXIS_X" or an equivalent numeric constant such as "42" if unknown.
+ *
+ * @param axis The axis.
+ * @return The symbolic name of the specified axis.
+ */
+ public static String axisToString(int axis) {
+ String symbolicName = nativeAxisToString(axis);
+ return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(axis);
+ }
+
+ /**
+ * Gets an axis by its symbolic name such as "AXIS_X" or an
+ * equivalent numeric constant such as "42".
+ *
+ * @param symbolicName The symbolic name of the axis.
+ * @return The axis or -1 if not found.
+ * @see KeyEvent#keyCodeToString(int)
+ */
+ public static int axisFromString(String symbolicName) {
+ if (symbolicName.startsWith(LABEL_PREFIX)) {
+ symbolicName = symbolicName.substring(LABEL_PREFIX.length());
+ int axis = nativeAxisFromString(symbolicName);
+ if (axis >= 0) {
+ return axis;
+ }
+ }
+ try {
+ return Integer.parseInt(symbolicName, 10);
+ } catch (NumberFormatException ex) {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified combined
+ * button state flags such as "0", "BUTTON_PRIMARY",
+ * "BUTTON_PRIMARY|BUTTON_SECONDARY" or an equivalent numeric constant such as "0x10000000"
+ * if unknown.
+ *
+ * @param buttonState The button state.
+ * @return The symbolic name of the specified combined button state flags.
+ * @hide
+ */
+ public static String buttonStateToString(int buttonState) {
+ if (buttonState == 0) {
+ return "0";
+ }
+ StringBuilder result = null;
+ int i = 0;
+ while (buttonState != 0) {
+ final boolean isSet = (buttonState & 1) != 0;
+ buttonState >>>= 1; // unsigned shift!
+ if (isSet) {
+ final String name = BUTTON_SYMBOLIC_NAMES[i];
+ if (result == null) {
+ if (buttonState == 0) {
+ return name;
+ }
+ result = new StringBuilder(name);
+ } else {
+ result.append('|');
+ result.append(name);
+ }
+ }
+ i += 1;
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a string that represents the symbolic name of the specified tool type
+ * such as "TOOL_TYPE_FINGER" or an equivalent numeric constant such as "42" if unknown.
+ *
+ * @param toolType The tool type.
+ * @return The symbolic name of the specified tool type.
+ * @hide
+ */
+ public static String toolTypeToString(int toolType) {
+ String symbolicName = TOOL_TYPE_SYMBOLIC_NAMES.get(toolType);
+ return symbolicName != null ? symbolicName : Integer.toString(toolType);
+ }
+
+ /**
+ * Checks if a mouse or stylus button (or combination of buttons) is pressed.
+ * @param button Button (or combination of buttons).
+ * @return True if specified buttons are pressed.
+ *
+ * @see #BUTTON_PRIMARY
+ * @see #BUTTON_SECONDARY
+ * @see #BUTTON_TERTIARY
+ * @see #BUTTON_FORWARD
+ * @see #BUTTON_BACK
+ * @see #BUTTON_STYLUS_PRIMARY
+ * @see #BUTTON_STYLUS_SECONDARY
+ */
+ public final boolean isButtonPressed(int button) {
+ if (button == 0) {
+ return false;
+ }
+ return (getButtonState() & button) == button;
+ }
+
+ public static final Parcelable.Creator<MotionEvent> CREATOR
+ = new Parcelable.Creator<MotionEvent>() {
+ public MotionEvent createFromParcel(Parcel in) {
+ in.readInt(); // skip token, we already know this is a MotionEvent
+ return MotionEvent.createFromParcelBody(in);
+ }
+
+ public MotionEvent[] newArray(int size) {
+ return new MotionEvent[size];
+ }
+ };
+
+ /** @hide */
+ public static MotionEvent createFromParcelBody(Parcel in) {
+ MotionEvent ev = obtain();
+ ev.mNativePtr = nativeReadFromParcel(ev.mNativePtr, in);
+ return ev;
+ }
+
+ /** @hide */
+ @Override
+ public final void cancel() {
+ setAction(ACTION_CANCEL);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_MOTION_EVENT);
+ nativeWriteToParcel(mNativePtr, out);
+ }
+
+ /**
+ * Transfer object for pointer coordinates.
+ *
+ * Objects of this type can be used to specify the pointer coordinates when
+ * creating new {@link MotionEvent} objects and to query pointer coordinates
+ * in bulk.
+ *
+ * Refer to {@link InputDevice} for information about how different kinds of
+ * input devices and sources represent pointer coordinates.
+ */
+ public static final class PointerCoords {
+ private static final int INITIAL_PACKED_AXIS_VALUES = 8;
+ private long mPackedAxisBits;
+ private float[] mPackedAxisValues;
+
+ /**
+ * Creates a pointer coords object with all axes initialized to zero.
+ */
+ public PointerCoords() {
+ }
+
+ /**
+ * Creates a pointer coords object as a copy of the
+ * contents of another pointer coords object.
+ *
+ * @param other The pointer coords object to copy.
+ */
+ public PointerCoords(PointerCoords other) {
+ copyFrom(other);
+ }
+
+ /** @hide */
+ public static PointerCoords[] createArray(int size) {
+ PointerCoords[] array = new PointerCoords[size];
+ for (int i = 0; i < size; i++) {
+ array[i] = new PointerCoords();
+ }
+ return array;
+ }
+
+ /**
+ * The X component of the pointer movement.
+ *
+ * @see MotionEvent#AXIS_X
+ */
+ public float x;
+
+ /**
+ * The Y component of the pointer movement.
+ *
+ * @see MotionEvent#AXIS_Y
+ */
+ public float y;
+
+ /**
+ * A normalized value that describes the pressure applied to the device
+ * by a finger or other tool.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * although values higher than 1 may be generated depending on the calibration of
+ * the input device.
+ *
+ * @see MotionEvent#AXIS_PRESSURE
+ */
+ public float pressure;
+
+ /**
+ * A normalized value that describes the approximate size of the pointer touch area
+ * in relation to the maximum detectable size of the device.
+ * It represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1. The value of size can be used to
+ * determine fat touch events.
+ *
+ * @see MotionEvent#AXIS_SIZE
+ */
+ public float size;
+
+ /**
+ * The length of the major axis of an ellipse that describes the touch area at
+ * the point of contact.
+ * If the device is a touch screen, the length is reported in pixels, otherwise it is
+ * reported in device-specific units.
+ *
+ * @see MotionEvent#AXIS_TOUCH_MAJOR
+ */
+ public float touchMajor;
+
+ /**
+ * The length of the minor axis of an ellipse that describes the touch area at
+ * the point of contact.
+ * If the device is a touch screen, the length is reported in pixels, otherwise it is
+ * reported in device-specific units.
+ *
+ * @see MotionEvent#AXIS_TOUCH_MINOR
+ */
+ public float touchMinor;
+
+ /**
+ * The length of the major axis of an ellipse that describes the size of
+ * the approaching tool.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ * If the device is a touch screen, the length is reported in pixels, otherwise it is
+ * reported in device-specific units.
+ *
+ * @see MotionEvent#AXIS_TOOL_MAJOR
+ */
+ public float toolMajor;
+
+ /**
+ * The length of the minor axis of an ellipse that describes the size of
+ * the approaching tool.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ * If the device is a touch screen, the length is reported in pixels, otherwise it is
+ * reported in device-specific units.
+ *
+ * @see MotionEvent#AXIS_TOOL_MINOR
+ */
+ public float toolMinor;
+
+ /**
+ * The orientation of the touch area and tool area in radians clockwise from vertical.
+ * An angle of 0 radians indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right).
+ *
+ * @see MotionEvent#AXIS_ORIENTATION
+ */
+ public float orientation;
+
+ /**
+ * Clears the contents of this object.
+ * Resets all axes to zero.
+ */
+ public void clear() {
+ mPackedAxisBits = 0;
+
+ x = 0;
+ y = 0;
+ pressure = 0;
+ size = 0;
+ touchMajor = 0;
+ touchMinor = 0;
+ toolMajor = 0;
+ toolMinor = 0;
+ orientation = 0;
+ }
+
+ /**
+ * Copies the contents of another pointer coords object.
+ *
+ * @param other The pointer coords object to copy.
+ */
+ public void copyFrom(PointerCoords other) {
+ final long bits = other.mPackedAxisBits;
+ mPackedAxisBits = bits;
+ if (bits != 0) {
+ final float[] otherValues = other.mPackedAxisValues;
+ final int count = Long.bitCount(bits);
+ float[] values = mPackedAxisValues;
+ if (values == null || count > values.length) {
+ values = new float[otherValues.length];
+ mPackedAxisValues = values;
+ }
+ System.arraycopy(otherValues, 0, values, 0, count);
+ }
+
+ x = other.x;
+ y = other.y;
+ pressure = other.pressure;
+ size = other.size;
+ touchMajor = other.touchMajor;
+ touchMinor = other.touchMinor;
+ toolMajor = other.toolMajor;
+ toolMinor = other.toolMinor;
+ orientation = other.orientation;
+ }
+
+ /**
+ * Gets the value associated with the specified axis.
+ *
+ * @param axis The axis identifier for the axis value to retrieve.
+ * @return The value associated with the axis, or 0 if none.
+ *
+ * @see MotionEvent#AXIS_X
+ * @see MotionEvent#AXIS_Y
+ */
+ public float getAxisValue(int axis) {
+ switch (axis) {
+ case AXIS_X:
+ return x;
+ case AXIS_Y:
+ return y;
+ case AXIS_PRESSURE:
+ return pressure;
+ case AXIS_SIZE:
+ return size;
+ case AXIS_TOUCH_MAJOR:
+ return touchMajor;
+ case AXIS_TOUCH_MINOR:
+ return touchMinor;
+ case AXIS_TOOL_MAJOR:
+ return toolMajor;
+ case AXIS_TOOL_MINOR:
+ return toolMinor;
+ case AXIS_ORIENTATION:
+ return orientation;
+ default: {
+ if (axis < 0 || axis > 63) {
+ throw new IllegalArgumentException("Axis out of range.");
+ }
+ final long bits = mPackedAxisBits;
+ final long axisBit = 0x8000000000000000L >>> axis;
+ if ((bits & axisBit) == 0) {
+ return 0;
+ }
+ final int index = Long.bitCount(bits & ~(0xFFFFFFFFFFFFFFFFL >>> axis));
+ return mPackedAxisValues[index];
+ }
+ }
+ }
+
+ /**
+ * Sets the value associated with the specified axis.
+ *
+ * @param axis The axis identifier for the axis value to assign.
+ * @param value The value to set.
+ *
+ * @see MotionEvent#AXIS_X
+ * @see MotionEvent#AXIS_Y
+ */
+ public void setAxisValue(int axis, float value) {
+ switch (axis) {
+ case AXIS_X:
+ x = value;
+ break;
+ case AXIS_Y:
+ y = value;
+ break;
+ case AXIS_PRESSURE:
+ pressure = value;
+ break;
+ case AXIS_SIZE:
+ size = value;
+ break;
+ case AXIS_TOUCH_MAJOR:
+ touchMajor = value;
+ break;
+ case AXIS_TOUCH_MINOR:
+ touchMinor = value;
+ break;
+ case AXIS_TOOL_MAJOR:
+ toolMajor = value;
+ break;
+ case AXIS_TOOL_MINOR:
+ toolMinor = value;
+ break;
+ case AXIS_ORIENTATION:
+ orientation = value;
+ break;
+ default: {
+ if (axis < 0 || axis > 63) {
+ throw new IllegalArgumentException("Axis out of range.");
+ }
+ final long bits = mPackedAxisBits;
+ final long axisBit = 0x8000000000000000L >>> axis;
+ final int index = Long.bitCount(bits & ~(0xFFFFFFFFFFFFFFFFL >>> axis));
+ float[] values = mPackedAxisValues;
+ if ((bits & axisBit) == 0) {
+ if (values == null) {
+ values = new float[INITIAL_PACKED_AXIS_VALUES];
+ mPackedAxisValues = values;
+ } else {
+ final int count = Long.bitCount(bits);
+ if (count < values.length) {
+ if (index != count) {
+ System.arraycopy(values, index, values, index + 1,
+ count - index);
+ }
+ } else {
+ float[] newValues = new float[count * 2];
+ System.arraycopy(values, 0, newValues, 0, index);
+ System.arraycopy(values, index, newValues, index + 1,
+ count - index);
+ values = newValues;
+ mPackedAxisValues = values;
+ }
+ }
+ mPackedAxisBits = bits | axisBit;
+ }
+ values[index] = value;
+ }
+ }
+ }
+ }
+
+ /**
+ * Transfer object for pointer properties.
+ *
+ * Objects of this type can be used to specify the pointer id and tool type
+ * when creating new {@link MotionEvent} objects and to query pointer properties in bulk.
+ */
+ public static final class PointerProperties {
+ /**
+ * Creates a pointer properties object with an invalid pointer id.
+ */
+ public PointerProperties() {
+ clear();
+ }
+
+ /**
+ * Creates a pointer properties object as a copy of the contents of
+ * another pointer properties object.
+ * @param other
+ */
+ public PointerProperties(PointerProperties other) {
+ copyFrom(other);
+ }
+
+ /** @hide */
+ public static PointerProperties[] createArray(int size) {
+ PointerProperties[] array = new PointerProperties[size];
+ for (int i = 0; i < size; i++) {
+ array[i] = new PointerProperties();
+ }
+ return array;
+ }
+
+ /**
+ * The pointer id.
+ * Initially set to {@link #INVALID_POINTER_ID} (-1).
+ *
+ * @see MotionEvent#getPointerId(int)
+ */
+ public int id;
+
+ /**
+ * The pointer tool type.
+ * Initially set to 0.
+ *
+ * @see MotionEvent#getToolType(int)
+ */
+ public int toolType;
+
+ /**
+ * Resets the pointer properties to their initial values.
+ */
+ public void clear() {
+ id = INVALID_POINTER_ID;
+ toolType = TOOL_TYPE_UNKNOWN;
+ }
+
+ /**
+ * Copies the contents of another pointer properties object.
+ *
+ * @param other The pointer properties object to copy.
+ */
+ public void copyFrom(PointerProperties other) {
+ id = other.id;
+ toolType = other.toolType;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof PointerProperties) {
+ return equals((PointerProperties)other);
+ }
+ return false;
+ }
+
+ private boolean equals(PointerProperties other) {
+ return other != null && id == other.id && toolType == other.toolType;
+ }
+
+ @Override
+ public int hashCode() {
+ return id | (toolType << 8);
+ }
+ }
+}
diff --git a/android/view/NotificationHeaderView.java b/android/view/NotificationHeaderView.java
new file mode 100644
index 00000000..58045602
--- /dev/null
+++ b/android/view/NotificationHeaderView.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2015 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.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.widget.CachingIconView;
+
+import java.util.ArrayList;
+
+/**
+ * A header of a notification view
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationHeaderView extends ViewGroup {
+ public static final int NO_COLOR = Notification.COLOR_INVALID;
+ private final int mChildMinWidth;
+ private final int mContentEndMargin;
+ private View mAppName;
+ private View mHeaderText;
+ private OnClickListener mExpandClickListener;
+ private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+ private ImageView mExpandButton;
+ private CachingIconView mIcon;
+ private View mProfileBadge;
+ private View mInfo;
+ private int mIconColor;
+ private int mOriginalNotificationColor;
+ private boolean mExpanded;
+ private boolean mShowExpandButtonAtEnd;
+ private boolean mShowWorkBadgeAtEnd;
+ private Drawable mBackground;
+ private int mHeaderBackgroundHeight;
+ private boolean mEntireHeaderClickable;
+ private boolean mExpandOnlyOnButton;
+ private boolean mAcceptAllTouches;
+
+ ViewOutlineProvider mProvider = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (mBackground != null) {
+ outline.setRect(0, 0, getWidth(), mHeaderBackgroundHeight);
+ outline.setAlpha(1f);
+ }
+ }
+ };
+
+ public NotificationHeaderView(Context context) {
+ this(context, null);
+ }
+
+ public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ Resources res = getResources();
+ mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+ mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
+ mHeaderBackgroundHeight = res.getDimensionPixelSize(
+ R.dimen.notification_header_background_height);
+ mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAppName = findViewById(com.android.internal.R.id.app_name_text);
+ mHeaderText = findViewById(com.android.internal.R.id.header_text);
+ mExpandButton = findViewById(com.android.internal.R.id.expand_button);
+ mIcon = findViewById(com.android.internal.R.id.icon);
+ mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
+ int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
+ MeasureSpec.AT_MOST);
+ int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
+ MeasureSpec.AT_MOST);
+ int totalWidth = getPaddingStart() + getPaddingEnd();
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ // We'll give it the rest of the space in the end
+ continue;
+ }
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
+ lp.leftMargin + lp.rightMargin, lp.width);
+ int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+ lp.topMargin + lp.bottomMargin, lp.height);
+ child.measure(childWidthSpec, childHeightSpec);
+ totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+ }
+ if (totalWidth > givenWidth) {
+ int overFlow = totalWidth - givenWidth;
+ // We are overflowing, lets shrink the app name first
+ final int appWidth = mAppName.getMeasuredWidth();
+ if (overFlow > 0 && mAppName.getVisibility() != GONE && appWidth > mChildMinWidth) {
+ int newSize = appWidth - Math.min(appWidth - mChildMinWidth, overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ mAppName.measure(childWidthSpec, wrapContentHeightSpec);
+ overFlow -= appWidth - newSize;
+ }
+ // still overflowing, finaly we shrink the header text
+ if (overFlow > 0 && mHeaderText.getVisibility() != GONE) {
+ // we're still too big
+ final int textWidth = mHeaderText.getMeasuredWidth();
+ int newSize = Math.max(0, textWidth - overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ mHeaderText.measure(childWidthSpec, wrapContentHeightSpec);
+ }
+ }
+ setMeasuredDimension(givenWidth, givenHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int left = getPaddingStart();
+ int end = getMeasuredWidth();
+ int childCount = getChildCount();
+ int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ int childHeight = child.getMeasuredHeight();
+ MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
+ left += params.getMarginStart();
+ int right = left + child.getMeasuredWidth();
+ int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
+ int bottom = top + childHeight;
+ int layoutLeft = left;
+ int layoutRight = right;
+ if (child == mExpandButton && mShowExpandButtonAtEnd) {
+ layoutRight = end - mContentEndMargin;
+ end = layoutLeft = layoutRight - child.getMeasuredWidth();
+ }
+ if (child == mProfileBadge) {
+ int paddingEnd = getPaddingEnd();
+ if (mShowWorkBadgeAtEnd) {
+ paddingEnd = mContentEndMargin;
+ }
+ layoutRight = end - paddingEnd;
+ end = layoutLeft = layoutRight - child.getMeasuredWidth();
+ }
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ int ltrLeft = layoutLeft;
+ layoutLeft = getWidth() - layoutRight;
+ layoutRight = getWidth() - ltrLeft;
+ }
+ child.layout(layoutLeft, top, layoutRight, bottom);
+ left = right + params.getMarginEnd();
+ }
+ updateTouchListener();
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new ViewGroup.MarginLayoutParams(getContext(), attrs);
+ }
+
+ /**
+ * Set a {@link Drawable} to be displayed as a background on the header.
+ */
+ public void setHeaderBackgroundDrawable(Drawable drawable) {
+ if (drawable != null) {
+ setWillNotDraw(false);
+ mBackground = drawable;
+ mBackground.setCallback(this);
+ setOutlineProvider(mProvider);
+ } else {
+ setWillNotDraw(true);
+ mBackground = null;
+ setOutlineProvider(null);
+ }
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mBackground != null) {
+ mBackground.setBounds(0, 0, getWidth(), mHeaderBackgroundHeight);
+ mBackground.draw(canvas);
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mBackground;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ if (mBackground != null && mBackground.isStateful()) {
+ mBackground.setState(getDrawableState());
+ }
+ }
+
+ private void updateTouchListener() {
+ if (mExpandClickListener != null) {
+ mTouchListener.bindTouchRects();
+ }
+ }
+
+ @Override
+ public void setOnClickListener(@Nullable OnClickListener l) {
+ mExpandClickListener = l;
+ setOnTouchListener(mExpandClickListener != null ? mTouchListener : null);
+ mExpandButton.setOnClickListener(mExpandClickListener);
+ updateTouchListener();
+ }
+
+ @RemotableViewMethod
+ public void setOriginalIconColor(int color) {
+ mIconColor = color;
+ }
+
+ public int getOriginalIconColor() {
+ return mIconColor;
+ }
+
+ @RemotableViewMethod
+ public void setOriginalNotificationColor(int color) {
+ mOriginalNotificationColor = color;
+ }
+
+ public int getOriginalNotificationColor() {
+ return mOriginalNotificationColor;
+ }
+
+ @RemotableViewMethod
+ public void setExpanded(boolean expanded) {
+ mExpanded = expanded;
+ updateExpandButton();
+ }
+
+ private void updateExpandButton() {
+ int drawableId;
+ int contentDescriptionId;
+ if (mExpanded) {
+ drawableId = R.drawable.ic_collapse_notification;
+ contentDescriptionId = R.string.expand_button_content_description_expanded;
+ } else {
+ drawableId = R.drawable.ic_expand_notification;
+ contentDescriptionId = R.string.expand_button_content_description_collapsed;
+ }
+ mExpandButton.setImageDrawable(getContext().getDrawable(drawableId));
+ mExpandButton.setColorFilter(mOriginalNotificationColor);
+ mExpandButton.setContentDescription(mContext.getText(contentDescriptionId));
+ }
+
+ public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) {
+ if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) {
+ setClipToPadding(!showWorkBadgeAtEnd);
+ mShowWorkBadgeAtEnd = showWorkBadgeAtEnd;
+ }
+ }
+
+ /**
+ * Sets whether or not the expand button appears at the end of the NotificationHeaderView. If
+ * both this and {@link #setShowWorkBadgeAtEnd(boolean)} have been set to true, then the
+ * expand button will appear closer to the end than the work badge.
+ */
+ public void setShowExpandButtonAtEnd(boolean showExpandButtonAtEnd) {
+ if (showExpandButtonAtEnd != mShowExpandButtonAtEnd) {
+ setClipToPadding(!showExpandButtonAtEnd);
+ mShowExpandButtonAtEnd = showExpandButtonAtEnd;
+ }
+ }
+
+ public View getWorkProfileIcon() {
+ return mProfileBadge;
+ }
+
+ public CachingIconView getIcon() {
+ return mIcon;
+ }
+
+ public class HeaderTouchListener implements View.OnTouchListener {
+
+ private final ArrayList<Rect> mTouchRects = new ArrayList<>();
+ private Rect mExpandButtonRect;
+ private int mTouchSlop;
+ private boolean mTrackGesture;
+ private float mDownX;
+ private float mDownY;
+
+ public HeaderTouchListener() {
+ }
+
+ public void bindTouchRects() {
+ mTouchRects.clear();
+ addRectAroundView(mIcon);
+ mExpandButtonRect = addRectAroundView(mExpandButton);
+ addWidthRect();
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ private void addWidthRect() {
+ Rect r = new Rect();
+ r.top = 0;
+ r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
+ r.left = 0;
+ r.right = getWidth();
+ mTouchRects.add(r);
+ }
+
+ private Rect addRectAroundView(View view) {
+ final Rect r = getRectAroundView(view);
+ mTouchRects.add(r);
+ return r;
+ }
+
+ private Rect getRectAroundView(View view) {
+ float size = 48 * getResources().getDisplayMetrics().density;
+ final Rect r = new Rect();
+ if (view.getVisibility() == GONE) {
+ view = getFirstChildNotGone();
+ r.left = (int) (view.getLeft() - size / 2.0f);
+ } else {
+ r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - size / 2.0f);
+ }
+ r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - size / 2.0f);
+ r.bottom = (int) (r.top + size);
+ r.right = (int) (r.left + size);
+ return r;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mTrackGesture = false;
+ if (isInside(x, y)) {
+ mDownX = x;
+ mDownY = y;
+ mTrackGesture = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackGesture) {
+ if (Math.abs(mDownX - x) > mTouchSlop
+ || Math.abs(mDownY - y) > mTouchSlop) {
+ mTrackGesture = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTrackGesture) {
+ mExpandButton.performClick();
+ }
+ break;
+ }
+ return mTrackGesture;
+ }
+
+ private boolean isInside(float x, float y) {
+ if (mAcceptAllTouches) {
+ return true;
+ }
+ if (mExpandOnlyOnButton) {
+ return mExpandButtonRect.contains((int) x, (int) y);
+ }
+ for (int i = 0; i < mTouchRects.size(); i++) {
+ Rect r = mTouchRects.get(i);
+ if (r.contains((int) x, (int) y)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private View getFirstChildNotGone() {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ return child;
+ }
+ }
+ return this;
+ }
+
+ public ImageView getExpandButton() {
+ return mExpandButton;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ public boolean isInTouchRect(float x, float y) {
+ if (mExpandClickListener == null) {
+ return false;
+ }
+ return mTouchListener.isInside(x, y);
+ }
+
+ /**
+ * Sets whether or not all touches to this header view will register as a click. Note that
+ * if the config value for {@code config_notificationHeaderClickableForExpand} is {@code true},
+ * then calling this method with {@code false} will not override that configuration.
+ */
+ @RemotableViewMethod
+ public void setAcceptAllTouches(boolean acceptAllTouches) {
+ mAcceptAllTouches = mEntireHeaderClickable || acceptAllTouches;
+ }
+
+ /**
+ * Sets whether only the expand icon itself should serve as the expand target.
+ */
+ @RemotableViewMethod
+ public void setExpandOnlyOnButton(boolean expandOnlyOnButton) {
+ mExpandOnlyOnButton = expandOnlyOnButton;
+ }
+}
diff --git a/android/view/OrientationEventListener.java b/android/view/OrientationEventListener.java
new file mode 100644
index 00000000..cd48a4f8
--- /dev/null
+++ b/android/view/OrientationEventListener.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008 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.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ */
+public abstract class OrientationEventListener {
+ private static final String TAG = "OrientationEventListener";
+ private static final boolean DEBUG = false;
+ private static final boolean localLOGV = false;
+ private int mOrientation = ORIENTATION_UNKNOWN;
+ private SensorManager mSensorManager;
+ private boolean mEnabled = false;
+ private int mRate;
+ private Sensor mSensor;
+ private SensorEventListener mSensorEventListener;
+ private OrientationListener mOldListener;
+
+ /**
+ * Returned from onOrientationChanged when the device orientation cannot be determined
+ * (typically when the device is in a close to flat position).
+ *
+ * @see #onOrientationChanged
+ */
+ public static final int ORIENTATION_UNKNOWN = -1;
+
+ /**
+ * Creates a new OrientationEventListener.
+ *
+ * @param context for the OrientationEventListener.
+ */
+ public OrientationEventListener(Context context) {
+ this(context, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ /**
+ * Creates a new OrientationEventListener.
+ *
+ * @param context for the OrientationEventListener.
+ * @param rate at which sensor events are processed (see also
+ * {@link android.hardware.SensorManager SensorManager}). Use the default
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ */
+ public OrientationEventListener(Context context, int rate) {
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = rate;
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (mSensor != null) {
+ // Create listener only if sensors do exist
+ mSensorEventListener = new SensorEventListenerImpl();
+ }
+ }
+
+ void registerListener(OrientationListener lis) {
+ mOldListener = lis;
+ }
+
+ /**
+ * Enables the OrientationEventListener so it will monitor the sensor and call
+ * {@link #onOrientationChanged} when the device orientation changes.
+ */
+ public void enable() {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Not enabled");
+ return;
+ }
+ if (mEnabled == false) {
+ if (localLOGV) Log.d(TAG, "OrientationEventListener enabled");
+ mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+ mEnabled = true;
+ }
+ }
+
+ /**
+ * Disables the OrientationEventListener.
+ */
+ public void disable() {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Invalid disable");
+ return;
+ }
+ if (mEnabled == true) {
+ if (localLOGV) Log.d(TAG, "OrientationEventListener disabled");
+ mSensorManager.unregisterListener(mSensorEventListener);
+ mEnabled = false;
+ }
+ }
+
+ class SensorEventListenerImpl implements SensorEventListener {
+ private static final int _DATA_X = 0;
+ private static final int _DATA_Y = 1;
+ private static final int _DATA_Z = 2;
+
+ public void onSensorChanged(SensorEvent event) {
+ float[] values = event.values;
+ int orientation = ORIENTATION_UNKNOWN;
+ float X = -values[_DATA_X];
+ float Y = -values[_DATA_Y];
+ float Z = -values[_DATA_Z];
+ float magnitude = X*X + Y*Y;
+ // Don't trust the angle if the magnitude is small compared to the y value
+ if (magnitude * 4 >= Z*Z) {
+ float OneEightyOverPi = 57.29577957855f;
+ float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
+ orientation = 90 - (int)Math.round(angle);
+ // normalize to 0 - 359 range
+ while (orientation >= 360) {
+ orientation -= 360;
+ }
+ while (orientation < 0) {
+ orientation += 360;
+ }
+ }
+ if (mOldListener != null) {
+ mOldListener.onSensorChanged(Sensor.TYPE_ACCELEROMETER, event.values);
+ }
+ if (orientation != mOrientation) {
+ mOrientation = orientation;
+ onOrientationChanged(orientation);
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+ }
+
+ /*
+ * Returns true if sensor is enabled and false otherwise
+ */
+ public boolean canDetectOrientation() {
+ return mSensor != null;
+ }
+
+ /**
+ * Called when the orientation of the device has changed.
+ * orientation parameter is in degrees, ranging from 0 to 359.
+ * orientation is 0 degrees when the device is oriented in its natural position,
+ * 90 degrees when its left side is at the top, 180 degrees when it is upside down,
+ * and 270 degrees when its right side is to the top.
+ * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
+ * and the orientation cannot be determined.
+ *
+ * @param orientation The new orientation of the device.
+ *
+ * @see #ORIENTATION_UNKNOWN
+ */
+ abstract public void onOrientationChanged(int orientation);
+}
diff --git a/android/view/OrientationListener.java b/android/view/OrientationListener.java
new file mode 100644
index 00000000..ce8074e2
--- /dev/null
+++ b/android/view/OrientationListener.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008 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.content.Context;
+import android.hardware.SensorListener;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ * @deprecated use {@link android.view.OrientationEventListener} instead.
+ * This class internally uses the OrientationEventListener.
+ */
+@Deprecated
+public abstract class OrientationListener implements SensorListener {
+ private OrientationEventListener mOrientationEventLis;
+
+ /**
+ * Returned from onOrientationChanged when the device orientation cannot be determined
+ * (typically when the device is in a close to flat position).
+ *
+ * @see #onOrientationChanged
+ */
+ public static final int ORIENTATION_UNKNOWN = OrientationEventListener.ORIENTATION_UNKNOWN;
+
+ /**
+ * Creates a new OrientationListener.
+ *
+ * @param context for the OrientationListener.
+ */
+ public OrientationListener(Context context) {
+ mOrientationEventLis = new OrientationEventListenerInternal(context);
+ }
+
+ /**
+ * Creates a new OrientationListener.
+ *
+ * @param context for the OrientationListener.
+ * @param rate at which sensor events are processed (see also
+ * {@link android.hardware.SensorManager SensorManager}). Use the default
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ */
+ public OrientationListener(Context context, int rate) {
+ mOrientationEventLis = new OrientationEventListenerInternal(context, rate);
+ }
+
+ class OrientationEventListenerInternal extends OrientationEventListener {
+ OrientationEventListenerInternal(Context context) {
+ super(context);
+ }
+
+ OrientationEventListenerInternal(Context context, int rate) {
+ super(context, rate);
+ // register so that onSensorChanged gets invoked
+ registerListener(OrientationListener.this);
+ }
+
+ public void onOrientationChanged(int orientation) {
+ OrientationListener.this.onOrientationChanged(orientation);
+ }
+ }
+
+ /**
+ * Enables the OrientationListener so it will monitor the sensor and call
+ * {@link #onOrientationChanged} when the device orientation changes.
+ */
+ public void enable() {
+ mOrientationEventLis.enable();
+ }
+
+ /**
+ * Disables the OrientationListener.
+ */
+ public void disable() {
+ mOrientationEventLis.disable();
+ }
+
+ public void onAccuracyChanged(int sensor, int accuracy) {
+ }
+
+ public void onSensorChanged(int sensor, float[] values) {
+ // just ignore the call here onOrientationChanged is invoked anyway
+ }
+
+
+ /**
+ * Look at {@link android.view.OrientationEventListener#onOrientationChanged}
+ * for method description and usage
+ * @param orientation The new orientation of the device.
+ *
+ * @see #ORIENTATION_UNKNOWN
+ */
+ abstract public void onOrientationChanged(int orientation);
+
+}
diff --git a/android/view/PixelCopy.java b/android/view/PixelCopy.java
new file mode 100644
index 00000000..a14609f3
--- /dev/null
+++ b/android/view/PixelCopy.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2016 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides a mechanisms to issue pixel copy requests to allow for copy
+ * operations from {@link Surface} to {@link Bitmap}
+ */
+public final class PixelCopy {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SUCCESS, ERROR_UNKNOWN, ERROR_TIMEOUT, ERROR_SOURCE_NO_DATA,
+ ERROR_SOURCE_INVALID, ERROR_DESTINATION_INVALID})
+ public @interface CopyResultStatus {}
+
+ /** The pixel copy request succeeded */
+ public static final int SUCCESS = 0;
+
+ /** The pixel copy request failed with an unknown error. */
+ public static final int ERROR_UNKNOWN = 1;
+
+ /**
+ * A timeout occurred while trying to acquire a buffer from the source to
+ * copy from.
+ */
+ public static final int ERROR_TIMEOUT = 2;
+
+ /**
+ * The source has nothing to copy from. When the source is a {@link Surface}
+ * this means that no buffers have been queued yet. Wait for the source
+ * to produce a frame and try again.
+ */
+ public static final int ERROR_SOURCE_NO_DATA = 3;
+
+ /**
+ * It is not possible to copy from the source. This can happen if the source
+ * is hardware-protected or destroyed.
+ */
+ public static final int ERROR_SOURCE_INVALID = 4;
+
+ /**
+ * The destination isn't a valid copy target. If the destination is a bitmap
+ * this can occur if the bitmap is too large for the hardware to copy to.
+ * It can also occur if the destination has been destroyed.
+ */
+ public static final int ERROR_DESTINATION_INVALID = 5;
+
+ /**
+ * Listener for observing the completion of a PixelCopy request.
+ */
+ public interface OnPixelCopyFinishedListener {
+ /**
+ * Callback for when a pixel copy request has completed. This will be called
+ * regardless of whether the copy succeeded or failed.
+ *
+ * @param copyResult Contains the resulting status of the copy request.
+ * This will either be {@link PixelCopy#SUCCESS} or one of the
+ * <code>PixelCopy.ERROR_*</code> values.
+ */
+ void onPixelCopyFinished(@CopyResultStatus int copyResult);
+ }
+
+ /**
+ * Requests for the display content of a {@link SurfaceView} to be copied
+ * into a provided {@link Bitmap}.
+ *
+ * The contents of the source will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the SurfaceView's Surface will be used as the source of the copy.
+ *
+ * @param source The source from which to copy
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest,
+ @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+ request(source.getHolder().getSurface(), dest, listener, listenerThread);
+ }
+
+ /**
+ * Requests for the display content of a {@link SurfaceView} to be copied
+ * into a provided {@link Bitmap}.
+ *
+ * The contents of the source will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the SurfaceView's Surface will be used as the source of the copy.
+ *
+ * @param source The source from which to copy
+ * @param srcRect The area of the source to copy from. If this is null
+ * the copy area will be the entire surface. The rect will be clamped to
+ * the bounds of the Surface.
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull SurfaceView source, @Nullable Rect srcRect,
+ @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+ @NonNull Handler listenerThread) {
+ request(source.getHolder().getSurface(), srcRect,
+ dest, listener, listenerThread);
+ }
+
+ /**
+ * Requests a copy of the pixels from a {@link Surface} to be copied into
+ * a provided {@link Bitmap}.
+ *
+ * The contents of the source will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the Surface will be used as the source of the copy.
+ *
+ * @param source The source from which to copy
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull Surface source, @NonNull Bitmap dest,
+ @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+ request(source, null, dest, listener, listenerThread);
+ }
+
+ /**
+ * Requests a copy of the pixels at the provided {@link Rect} from
+ * a {@link Surface} to be copied into a provided {@link Bitmap}.
+ *
+ * The contents of the source rect will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the Surface will be used as the source of the copy.
+ *
+ * @param source The source from which to copy
+ * @param srcRect The area of the source to copy from. If this is null
+ * the copy area will be the entire surface. The rect will be clamped to
+ * the bounds of the Surface.
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull Surface source, @Nullable Rect srcRect,
+ @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+ @NonNull Handler listenerThread) {
+ validateBitmapDest(dest);
+ if (!source.isValid()) {
+ throw new IllegalArgumentException("Surface isn't valid, source.isValid() == false");
+ }
+ if (srcRect != null && srcRect.isEmpty()) {
+ throw new IllegalArgumentException("sourceRect is empty");
+ }
+ // TODO: Make this actually async and fast and cool and stuff
+ int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest);
+ listenerThread.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onPixelCopyFinished(result);
+ }
+ });
+ }
+
+ /**
+ * Requests a copy of the pixels from a {@link Window} to be copied into
+ * a provided {@link Bitmap}.
+ *
+ * The contents of the source will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the Window's Surface will be used as the source of the copy.
+ *
+ * Note: This is limited to being able to copy from Window's with a non-null
+ * DecorView. If {@link Window#peekDecorView()} is null this throws an
+ * {@link IllegalArgumentException}. It will similarly throw an exception
+ * if the DecorView has not yet acquired a backing surface. It is recommended
+ * that {@link OnDrawListener} is used to ensure that at least one draw
+ * has happened before trying to copy from the window, otherwise either
+ * an {@link IllegalArgumentException} will be thrown or an error will
+ * be returned to the {@link OnPixelCopyFinishedListener}.
+ *
+ * @param source The source from which to copy
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull Window source, @NonNull Bitmap dest,
+ @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+ request(source, null, dest, listener, listenerThread);
+ }
+
+ /**
+ * Requests a copy of the pixels at the provided {@link Rect} from
+ * a {@link Window} to be copied into a provided {@link Bitmap}.
+ *
+ * The contents of the source rect will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the Window's Surface will be used as the source of the copy.
+ *
+ * Note: This is limited to being able to copy from Window's with a non-null
+ * DecorView. If {@link Window#peekDecorView()} is null this throws an
+ * {@link IllegalArgumentException}. It will similarly throw an exception
+ * if the DecorView has not yet acquired a backing surface. It is recommended
+ * that {@link OnDrawListener} is used to ensure that at least one draw
+ * has happened before trying to copy from the window, otherwise either
+ * an {@link IllegalArgumentException} will be thrown or an error will
+ * be returned to the {@link OnPixelCopyFinishedListener}.
+ *
+ * @param source The source from which to copy
+ * @param srcRect The area of the source to copy from. If this is null
+ * the copy area will be the entire surface. The rect will be clamped to
+ * the bounds of the Surface.
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull Window source, @Nullable Rect srcRect,
+ @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+ @NonNull Handler listenerThread) {
+ validateBitmapDest(dest);
+ if (source == null) {
+ throw new IllegalArgumentException("source is null");
+ }
+ if (source.peekDecorView() == null) {
+ throw new IllegalArgumentException(
+ "Only able to copy windows with decor views");
+ }
+ Surface surface = null;
+ if (source.peekDecorView().getViewRootImpl() != null) {
+ surface = source.peekDecorView().getViewRootImpl().mSurface;
+ }
+ if (surface == null || !surface.isValid()) {
+ throw new IllegalArgumentException(
+ "Window doesn't have a backing surface!");
+ }
+ request(surface, srcRect, dest, listener, listenerThread);
+ }
+
+ private static void validateBitmapDest(Bitmap bitmap) {
+ // TODO: Pre-check max texture dimens if we can
+ if (bitmap == null) {
+ throw new IllegalArgumentException("Bitmap cannot be null");
+ }
+ if (bitmap.isRecycled()) {
+ throw new IllegalArgumentException("Bitmap is recycled");
+ }
+ if (!bitmap.isMutable()) {
+ throw new IllegalArgumentException("Bitmap is immutable");
+ }
+ }
+
+ private PixelCopy() {}
+}
diff --git a/android/view/PointerIcon.java b/android/view/PointerIcon.java
new file mode 100644
index 00000000..998fd019
--- /dev/null
+++ b/android/view/PointerIcon.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2011 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.XmlRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.XmlUtils;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * <p>
+ * Pointer icons can be provided either by the system using system types,
+ * or by applications using bitmaps or application resources.
+ * </p>
+ */
+public final class PointerIcon implements Parcelable {
+ private static final String TAG = "PointerIcon";
+
+ /** {@hide} Type constant: Custom icon with a user-supplied bitmap. */
+ public static final int TYPE_CUSTOM = -1;
+
+ /** Type constant: Null icon. It has no bitmap. */
+ public static final int TYPE_NULL = 0;
+
+ /** Type constant: no icons are specified. If all views uses this, then falls back
+ * to the default type, but this is helpful to distinguish a view explicitly want
+ * to have the default icon.
+ * @hide
+ */
+ public static final int TYPE_NOT_SPECIFIED = 1;
+
+ /** Type constant: Arrow icon. (Default mouse pointer) */
+ public static final int TYPE_ARROW = 1000;
+
+ /** {@hide} Type constant: Spot hover icon for touchpads. */
+ public static final int TYPE_SPOT_HOVER = 2000;
+
+ /** {@hide} Type constant: Spot touch icon for touchpads. */
+ public static final int TYPE_SPOT_TOUCH = 2001;
+
+ /** {@hide} Type constant: Spot anchor icon for touchpads. */
+ public static final int TYPE_SPOT_ANCHOR = 2002;
+
+ // Type constants for additional predefined icons for mice.
+ /** Type constant: context-menu. */
+ public static final int TYPE_CONTEXT_MENU = 1001;
+
+ /** Type constant: hand. */
+ public static final int TYPE_HAND = 1002;
+
+ /** Type constant: help. */
+ public static final int TYPE_HELP = 1003;
+
+ /** Type constant: wait. */
+ public static final int TYPE_WAIT = 1004;
+
+ /** Type constant: cell. */
+ public static final int TYPE_CELL = 1006;
+
+ /** Type constant: crosshair. */
+ public static final int TYPE_CROSSHAIR = 1007;
+
+ /** Type constant: text. */
+ public static final int TYPE_TEXT = 1008;
+
+ /** Type constant: vertical-text. */
+ public static final int TYPE_VERTICAL_TEXT = 1009;
+
+ /** Type constant: alias (indicating an alias of/shortcut to something is
+ * to be created. */
+ public static final int TYPE_ALIAS = 1010;
+
+ /** Type constant: copy. */
+ public static final int TYPE_COPY = 1011;
+
+ /** Type constant: no-drop. */
+ public static final int TYPE_NO_DROP = 1012;
+
+ /** Type constant: all-scroll. */
+ public static final int TYPE_ALL_SCROLL = 1013;
+
+ /** Type constant: horizontal double arrow mainly for resizing. */
+ public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014;
+
+ /** Type constant: vertical double arrow mainly for resizing. */
+ public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015;
+
+ /** Type constant: diagonal double arrow -- top-right to bottom-left. */
+ public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
+
+ /** Type constant: diagonal double arrow -- top-left to bottom-right. */
+ public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
+
+ /** Type constant: zoom-in. */
+ public static final int TYPE_ZOOM_IN = 1018;
+
+ /** Type constant: zoom-out. */
+ public static final int TYPE_ZOOM_OUT = 1019;
+
+ /** Type constant: grab. */
+ public static final int TYPE_GRAB = 1020;
+
+ /** Type constant: grabbing. */
+ public static final int TYPE_GRABBING = 1021;
+
+ // OEM private types should be defined starting at this range to avoid
+ // conflicts with any system types that may be defined in the future.
+ private static final int TYPE_OEM_FIRST = 10000;
+
+ /** The default pointer icon. */
+ public static final int TYPE_DEFAULT = TYPE_ARROW;
+
+ private static final PointerIcon gNullIcon = new PointerIcon(TYPE_NULL);
+ private static final SparseArray<PointerIcon> gSystemIcons = new SparseArray<PointerIcon>();
+ private static boolean sUseLargeIcons = false;
+
+ private final int mType;
+ private int mSystemIconResourceId;
+ private Bitmap mBitmap;
+ private float mHotSpotX;
+ private float mHotSpotY;
+ // The bitmaps for the additional frame of animated pointer icon. Note that the first frame
+ // will be stored in mBitmap.
+ private Bitmap mBitmapFrames[];
+ private int mDurationPerFrame;
+
+ private PointerIcon(int type) {
+ mType = type;
+ }
+
+ /**
+ * Gets a special pointer icon that has no bitmap.
+ *
+ * @return The null pointer icon.
+ *
+ * @see #TYPE_NULL
+ * @hide
+ */
+ public static PointerIcon getNullIcon() {
+ return gNullIcon;
+ }
+
+ /**
+ * Gets the default pointer icon.
+ *
+ * @param context The context.
+ * @return The default pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ * @hide
+ */
+ public static PointerIcon getDefaultIcon(@NonNull Context context) {
+ return getSystemIcon(context, TYPE_DEFAULT);
+ }
+
+ /**
+ * Gets a system pointer icon for the given type.
+ * If typeis not recognized, returns the default pointer icon.
+ *
+ * @param context The context.
+ * @param type The pointer icon type.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIcon getSystemIcon(@NonNull Context context, int type) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (type == TYPE_NULL) {
+ return gNullIcon;
+ }
+
+ PointerIcon icon = gSystemIcons.get(type);
+ if (icon != null) {
+ return icon;
+ }
+
+ int typeIndex = getSystemIconTypeIndex(type);
+ if (typeIndex == 0) {
+ typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
+ }
+
+ int defStyle = sUseLargeIcons ?
+ com.android.internal.R.style.LargePointer : com.android.internal.R.style.Pointer;
+ TypedArray a = context.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.Pointer,
+ 0, defStyle);
+ int resourceId = a.getResourceId(typeIndex, -1);
+ a.recycle();
+
+ if (resourceId == -1) {
+ Log.w(TAG, "Missing theme resources for pointer icon type " + type);
+ return type == TYPE_DEFAULT ? gNullIcon : getSystemIcon(context, TYPE_DEFAULT);
+ }
+
+ icon = new PointerIcon(type);
+ if ((resourceId & 0xff000000) == 0x01000000) {
+ icon.mSystemIconResourceId = resourceId;
+ } else {
+ icon.loadResource(context, context.getResources(), resourceId);
+ }
+ gSystemIcons.append(type, icon);
+ return icon;
+ }
+
+ /**
+ * Updates wheter accessibility large icons are used or not.
+ * @hide
+ */
+ public static void setUseLargeIcons(boolean use) {
+ sUseLargeIcons = use;
+ gSystemIcons.clear();
+ }
+
+ /**
+ * Creates a custom pointer icon from the given bitmap and hotspot information.
+ *
+ * @param bitmap The bitmap for the icon.
+ * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getWidth()) range.
+ * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getHeight()) range.
+ * @return A pointer icon for this bitmap.
+ *
+ * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+ * parameters are invalid.
+ */
+ public static PointerIcon create(@NonNull Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (bitmap == null) {
+ throw new IllegalArgumentException("bitmap must not be null");
+ }
+ validateHotSpot(bitmap, hotSpotX, hotSpotY);
+
+ PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
+ icon.mBitmap = bitmap;
+ icon.mHotSpotX = hotSpotX;
+ icon.mHotSpotY = hotSpotY;
+ return icon;
+ }
+
+ /**
+ * Loads a custom pointer icon from an XML resource.
+ * <p>
+ * The XML resource should have the following form:
+ * <code>
+ * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+ * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:bitmap="@drawable/my_pointer_bitmap"
+ * android:hotSpotX="24"
+ * android:hotSpotY="24" /&gt;
+ * </code>
+ * </p>
+ *
+ * @param resources The resources object.
+ * @param resourceId The resource id.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if resources is null.
+ * @throws Resources.NotFoundException if the resource was not found or the drawable
+ * linked in the resource was not found.
+ */
+ public static PointerIcon load(@NonNull Resources resources, @XmlRes int resourceId) {
+ if (resources == null) {
+ throw new IllegalArgumentException("resources must not be null");
+ }
+
+ PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
+ icon.loadResource(null, resources, resourceId);
+ return icon;
+ }
+
+ /**
+ * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
+ * Returns a pointer icon (not necessarily the same instance) with the information filled in.
+ *
+ * @param context The context.
+ * @return The loaded pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ * @hide
+ */
+ public PointerIcon load(@NonNull Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (mSystemIconResourceId == 0 || mBitmap != null) {
+ return this;
+ }
+
+ PointerIcon result = new PointerIcon(mType);
+ result.mSystemIconResourceId = mSystemIconResourceId;
+ result.loadResource(context, context.getResources(), mSystemIconResourceId);
+ return result;
+ }
+
+ /** @hide */
+ public int getType() {
+ return mType;
+ }
+
+ public static final Parcelable.Creator<PointerIcon> CREATOR
+ = new Parcelable.Creator<PointerIcon>() {
+ public PointerIcon createFromParcel(Parcel in) {
+ int type = in.readInt();
+ if (type == TYPE_NULL) {
+ return getNullIcon();
+ }
+
+ int systemIconResourceId = in.readInt();
+ if (systemIconResourceId != 0) {
+ PointerIcon icon = new PointerIcon(type);
+ icon.mSystemIconResourceId = systemIconResourceId;
+ return icon;
+ }
+
+ Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
+ float hotSpotX = in.readFloat();
+ float hotSpotY = in.readFloat();
+ return PointerIcon.create(bitmap, hotSpotX, hotSpotY);
+ }
+
+ public PointerIcon[] newArray(int size) {
+ return new PointerIcon[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mType);
+
+ if (mType != TYPE_NULL) {
+ out.writeInt(mSystemIconResourceId);
+ if (mSystemIconResourceId == 0) {
+ mBitmap.writeToParcel(out, flags);
+ out.writeFloat(mHotSpotX);
+ out.writeFloat(mHotSpotY);
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || !(other instanceof PointerIcon)) {
+ return false;
+ }
+
+ PointerIcon otherIcon = (PointerIcon) other;
+ if (mType != otherIcon.mType
+ || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+ return false;
+ }
+
+ if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+ || mHotSpotX != otherIcon.mHotSpotX
+ || mHotSpotY != otherIcon.mHotSpotY)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void loadResource(Context context, Resources resources, @XmlRes int resourceId) {
+ final XmlResourceParser parser = resources.getXml(resourceId);
+ final int bitmapRes;
+ final float hotSpotX;
+ final float hotSpotY;
+ try {
+ XmlUtils.beginDocument(parser, "pointer-icon");
+
+ final TypedArray a = resources.obtainAttributes(
+ parser, com.android.internal.R.styleable.PointerIcon);
+ bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+ hotSpotX = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ hotSpotY = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ a.recycle();
+ } catch (Exception ex) {
+ throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
+ } finally {
+ parser.close();
+ }
+
+ if (bitmapRes == 0) {
+ throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
+ }
+
+ Drawable drawable;
+ if (context == null) {
+ drawable = resources.getDrawable(bitmapRes);
+ } else {
+ drawable = context.getDrawable(bitmapRes);
+ }
+ if (drawable instanceof AnimationDrawable) {
+ // Extract animation frame bitmaps.
+ final AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
+ final int frames = animationDrawable.getNumberOfFrames();
+ drawable = animationDrawable.getFrame(0);
+ if (frames == 1) {
+ Log.w(TAG, "Animation icon with single frame -- simply treating the first "
+ + "frame as a normal bitmap icon.");
+ } else {
+ // Assumes they have the exact duration.
+ mDurationPerFrame = animationDrawable.getDuration(0);
+ mBitmapFrames = new Bitmap[frames - 1];
+ final int width = drawable.getIntrinsicWidth();
+ final int height = drawable.getIntrinsicHeight();
+ for (int i = 1; i < frames; ++i) {
+ Drawable drawableFrame = animationDrawable.getFrame(i);
+ if (!(drawableFrame instanceof BitmapDrawable)) {
+ throw new IllegalArgumentException("Frame of an animated pointer icon "
+ + "must refer to a bitmap drawable.");
+ }
+ if (drawableFrame.getIntrinsicWidth() != width ||
+ drawableFrame.getIntrinsicHeight() != height) {
+ throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
+ + "is different. All frames should have the exact same size and "
+ + "share the same hotspot.");
+ }
+ mBitmapFrames[i - 1] = ((BitmapDrawable)drawableFrame).getBitmap();
+ }
+ }
+ }
+ if (!(drawable instanceof BitmapDrawable)) {
+ throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+ + "refer to a bitmap drawable.");
+ }
+
+ // Set the properties now that we have successfully loaded the icon.
+ mBitmap = ((BitmapDrawable)drawable).getBitmap();
+ mHotSpotX = hotSpotX;
+ mHotSpotY = hotSpotY;
+ }
+
+ private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+ throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
+ }
+ if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+ throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
+ }
+ }
+
+ private static int getSystemIconTypeIndex(int type) {
+ switch (type) {
+ case TYPE_ARROW:
+ return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+ case TYPE_SPOT_HOVER:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
+ case TYPE_SPOT_TOUCH:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
+ case TYPE_SPOT_ANCHOR:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+ case TYPE_HAND:
+ return com.android.internal.R.styleable.Pointer_pointerIconHand;
+ case TYPE_CONTEXT_MENU:
+ return com.android.internal.R.styleable.Pointer_pointerIconContextMenu;
+ case TYPE_HELP:
+ return com.android.internal.R.styleable.Pointer_pointerIconHelp;
+ case TYPE_WAIT:
+ return com.android.internal.R.styleable.Pointer_pointerIconWait;
+ case TYPE_CELL:
+ return com.android.internal.R.styleable.Pointer_pointerIconCell;
+ case TYPE_CROSSHAIR:
+ return com.android.internal.R.styleable.Pointer_pointerIconCrosshair;
+ case TYPE_TEXT:
+ return com.android.internal.R.styleable.Pointer_pointerIconText;
+ case TYPE_VERTICAL_TEXT:
+ return com.android.internal.R.styleable.Pointer_pointerIconVerticalText;
+ case TYPE_ALIAS:
+ return com.android.internal.R.styleable.Pointer_pointerIconAlias;
+ case TYPE_COPY:
+ return com.android.internal.R.styleable.Pointer_pointerIconCopy;
+ case TYPE_ALL_SCROLL:
+ return com.android.internal.R.styleable.Pointer_pointerIconAllScroll;
+ case TYPE_NO_DROP:
+ return com.android.internal.R.styleable.Pointer_pointerIconNodrop;
+ case TYPE_HORIZONTAL_DOUBLE_ARROW:
+ return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow;
+ case TYPE_VERTICAL_DOUBLE_ARROW:
+ return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow;
+ case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW:
+ return com.android.internal.R.styleable.
+ Pointer_pointerIconTopRightDiagonalDoubleArrow;
+ case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW:
+ return com.android.internal.R.styleable.
+ Pointer_pointerIconTopLeftDiagonalDoubleArrow;
+ case TYPE_ZOOM_IN:
+ return com.android.internal.R.styleable.Pointer_pointerIconZoomIn;
+ case TYPE_ZOOM_OUT:
+ return com.android.internal.R.styleable.Pointer_pointerIconZoomOut;
+ case TYPE_GRAB:
+ return com.android.internal.R.styleable.Pointer_pointerIconGrab;
+ case TYPE_GRABBING:
+ return com.android.internal.R.styleable.Pointer_pointerIconGrabbing;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/android/view/PointerIcon_Delegate.java b/android/view/PointerIcon_Delegate.java
new file mode 100644
index 00000000..4a5ea9b5
--- /dev/null
+++ b/android/view/PointerIcon_Delegate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+public class PointerIcon_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void loadResource(PointerIcon icon, Context context, Resources resources,
+ int resourceId) {
+ // HACK: This bypasses the problem of having an enum resolved as a resourceId.
+ // PointerIcon would not be displayed by layoutlib anyway, so we always return the null
+ // icon.
+ }
+}
diff --git a/android/view/RecordingCanvas.java b/android/view/RecordingCanvas.java
new file mode 100644
index 00000000..5088cdc9
--- /dev/null
+++ b/android/view/RecordingCanvas.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2016 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.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.graphics.BaseCanvas;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.NinePatch;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Picture;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.TemporaryBuffer;
+import android.text.GraphicsOperations;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * This class is a base class for canvases that defer drawing operations, so all
+ * the draw operations can be marked @FastNative. It contains a re-implementation of
+ * all the methods in {@link BaseCanvas}.
+ *
+ * @hide
+ */
+public class RecordingCanvas extends Canvas {
+
+ public RecordingCanvas(long nativeCanvas) {
+ super(nativeCanvas);
+ }
+
+ @Override
+ public final void drawArc(float left, float top, float right, float bottom, float startAngle,
+ float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+ nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
+ useCenter, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle,
+ boolean useCenter, @NonNull Paint paint) {
+ drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
+ paint);
+ }
+
+ @Override
+ public final void drawARGB(int a, int r, int g, int b) {
+ drawColor(Color.argb(a, r, g, b));
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, float left, float top,
+ @Nullable Paint paint) {
+ throwIfCannotDraw(bitmap);
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top,
+ paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix,
+ @Nullable Paint paint) {
+ nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(),
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ float left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // check for valid input
+ if (width < 0) {
+ throw new IllegalArgumentException("width must be >= 0");
+ }
+ if (height < 0) {
+ throw new IllegalArgumentException("height must be >= 0");
+ }
+ if (Math.abs(stride) < width) {
+ throw new IllegalArgumentException("abs(stride) must be >= width");
+ }
+ int lastScanline = offset + (height - 1) * stride;
+ int length = colors.length;
+ if (offset < 0 || (offset + width > length) || lastScanline < 0
+ || (lastScanline + width > length)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // quick escape if there's nothing to draw
+ if (width == 0 || height == 0) {
+ return;
+ }
+ // punch down to native for the actual draw
+ nDrawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // call through to the common float version
+ drawBitmap(colors, offset, stride, (float) x, (float) y, width, height,
+ hasAlpha, paint);
+ }
+
+ @Override
+ public final void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
+ @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
+ @Nullable Paint paint) {
+ if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ if (meshWidth == 0 || meshHeight == 0) {
+ return;
+ }
+ int count = (meshWidth + 1) * (meshHeight + 1);
+ // we mul by 2 since we need two floats per vertex
+ checkRange(verts.length, vertOffset, count * 2);
+ if (colors != null) {
+ // no mul by 2, since we need only 1 color per vertex
+ checkRange(colors.length, colorOffset, count);
+ }
+ nDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight,
+ verts, vertOffset, colors, colorOffset,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ @Override
+ public final void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
+ nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color) {
+ nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+ nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
+ }
+
+ @Override
+ public final void drawLine(float startX, float startY, float stopX, float stopY,
+ @NonNull Paint paint) {
+ nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
+ drawLines(pts, 0, pts.length, paint);
+ }
+
+ @Override
+ public final void drawOval(float left, float top, float right, float bottom,
+ @NonNull Paint paint) {
+ nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
+ if (oval == null) {
+ throw new NullPointerException();
+ }
+ drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
+ }
+
+ @Override
+ public final void drawPaint(@NonNull Paint paint) {
+ nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst,
+ @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ @Override
+ public final void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ @Override
+ public final void drawPath(@NonNull Path path, @NonNull Paint paint) {
+ if (path.isSimplePath && path.rects != null) {
+ nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
+ } else {
+ nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
+ }
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture) {
+ picture.endRecording();
+ int restoreCount = save();
+ picture.draw(this);
+ restoreToCount(restoreCount);
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture, @NonNull Rect dst) {
+ save();
+ translate(dst.left, dst.top);
+ if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+ scale((float) dst.width() / picture.getWidth(),
+ (float) dst.height() / picture.getHeight());
+ }
+ drawPicture(picture);
+ restore();
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture, @NonNull RectF dst) {
+ save();
+ translate(dst.left, dst.top);
+ if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+ scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
+ }
+ drawPicture(picture);
+ restore();
+ }
+
+ @Override
+ public final void drawPoint(float x, float y, @NonNull Paint paint) {
+ nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
+ drawPoints(pts, 0, pts.length, paint);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawPosText(@NonNull char[] text, int index, int count,
+ @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length || count * 2 > pos.length) {
+ throw new IndexOutOfBoundsException();
+ }
+ for (int i = 0; i < count; i++) {
+ drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
+ }
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
+ }
+
+ @Override
+ public final void drawRect(float left, float top, float right, float bottom,
+ @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRect(@NonNull Rect r, @NonNull Paint paint) {
+ drawRect(r.left, r.top, r.right, r.bottom, paint);
+ }
+
+ @Override
+ public final void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper,
+ rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRGB(int r, int g, int b) {
+ drawColor(Color.rgb(r, g, b));
+ }
+
+ @Override
+ public final void drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, @NonNull Paint paint) {
+ nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
+ drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
+ }
+
+ @Override
+ public final void drawText(@NonNull char[] text, int index, int count, float x, float y,
+ @NonNull Paint paint) {
+ if ((index | count | (index + count)
+ | (text.length - index - count)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (text instanceof String || text instanceof SpannedString
+ || text instanceof SpannableString) {
+ nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
+ paint.mBidiFlags, paint.getNativeInstance());
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawText(this, start, end, x, y,
+ paint);
+ } else {
+ char[] buf = TemporaryBuffer.obtain(end - start);
+ TextUtils.getChars(text, start, end, buf, 0);
+ nDrawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
+ paint.mBidiFlags, paint.getNativeInstance());
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ @Override
+ public final void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
+ nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawText(@NonNull String text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
+ float hOffset, float vOffset, @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
+ path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
+ float vOffset, @NonNull Paint paint) {
+ if (text.length() > 0) {
+ nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance());
+ }
+ }
+
+ @Override
+ public final void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+ int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((index | count | contextIndex | contextCount | index - contextIndex
+ | (contextIndex + contextCount) - (index + count)
+ | text.length - (contextIndex + contextCount)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
+ x, y, isRtl, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
+ int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((start | end | contextStart | contextEnd | start - contextStart | end - start
+ | contextEnd - end | text.length() - contextEnd) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (text instanceof String || text instanceof SpannedString
+ || text instanceof SpannableString) {
+ nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
+ contextEnd, x, y, isRtl, paint.getNativeInstance());
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawTextRun(this, start, end,
+ contextStart, contextEnd, x, y, isRtl, paint);
+ } else {
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance());
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ @Override
+ public final void drawVertices(@NonNull VertexMode mode, int vertexCount,
+ @NonNull float[] verts, int vertOffset, @Nullable float[] texs, int texOffset,
+ @Nullable int[] colors, int colorOffset, @Nullable short[] indices, int indexOffset,
+ int indexCount, @NonNull Paint paint) {
+ checkRange(verts.length, vertOffset, vertexCount);
+ if (isHardwareAccelerated()) {
+ return;
+ }
+ if (texs != null) {
+ checkRange(texs.length, texOffset, vertexCount);
+ }
+ if (colors != null) {
+ checkRange(colors.length, colorOffset, vertexCount / 2);
+ }
+ if (indices != null) {
+ checkRange(indices.length, indexOffset, indexCount);
+ }
+ nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
+ vertOffset, texs, texOffset, colors, colorOffset,
+ indices, indexOffset, indexCount, paint.getNativeInstance());
+ }
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+ long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap,
+ float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ long nativePaintOrZero, int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+ float x, float y, int width, int height, boolean hasAlpha, long nativePaintOrZero);
+
+ @FastNative
+ private static native void nDrawColor(long nativeCanvas, int color, int mode);
+
+ @FastNative
+ private static native void nDrawPaint(long nativeCanvas, long nativePaint);
+
+ @FastNative
+ private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
+
+ @FastNative
+ private static native void nDrawPoints(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ @FastNative
+ private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX,
+ float stopY, long nativePaint);
+
+ @FastNative
+ private static native void nDrawLines(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ @FastNative
+ private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ @FastNative
+ private static native void nDrawOval(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ @FastNative
+ private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius,
+ long nativePaint);
+
+ @FastNative
+ private static native void nDrawArc(long nativeCanvas, float left, float top, float right,
+ float bottom, float startAngle, float sweep, boolean useCenter, long nativePaint);
+
+ @FastNative
+ private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right,
+ float bottom, float rx, float ry, long nativePaint);
+
+ @FastNative
+ private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
+
+ @FastNative
+ private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
+
+ @FastNative
+ private static native void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero,
+ int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmapMatrix(long nativeCanvas, Bitmap bitmap,
+ long nativeMatrix, long nativePaint);
+
+ @FastNative
+ private static native void nDrawBitmapMesh(long nativeCanvas, Bitmap bitmap, int meshWidth,
+ int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset,
+ long nativePaint);
+
+ @FastNative
+ private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts,
+ int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
+ short[] indices, int indexOffset, int indexCount, long nativePaint);
+
+ @FastNative
+ private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
+ float x, float y, int flags, long nativePaint);
+
+ @FastNative
+ private static native void nDrawText(long nativeCanvas, String text, int start, int end,
+ float x, float y, int flags, long nativePaint);
+
+ @FastNative
+ private static native void nDrawTextRun(long nativeCanvas, String text, int start, int end,
+ int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint);
+
+ @FastNative
+ private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
+ int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint);
+
+ @FastNative
+ private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
+ long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint);
+
+ @FastNative
+ private static native void nDrawTextOnPath(long nativeCanvas, String text, long nativePath,
+ float hOffset, float vOffset, int flags, long nativePaint);
+}
diff --git a/android/view/RectShadowPainter.java b/android/view/RectShadowPainter.java
new file mode 100644
index 00000000..5665d4f3
--- /dev/null
+++ b/android/view/RectShadowPainter.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 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 com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+
+import android.graphics.Canvas;
+import android.graphics.Canvas_Delegate;
+import android.graphics.LinearGradient;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Path.FillType;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region.Op;
+import android.graphics.Shader.TileMode;
+
+import java.awt.Rectangle;
+
+/**
+ * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly,
+ * since it modifies the size of the content, that we can't do.
+ */
+public class RectShadowPainter {
+
+
+ private static final int START_COLOR = ResourceHelper.getColor("#37000000");
+ private static final int END_COLOR = ResourceHelper.getColor("#03000000");
+ private static final float PERPENDICULAR_ANGLE = 90f;
+
+ public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+ Rect outline = new Rect();
+ if (!viewOutline.getRect(outline)) {
+ assert false : "Outline is not a rect shadow";
+ return;
+ }
+
+ // TODO replacing the algorithm here to create better shadow
+
+ float shadowSize = elevationToShadow(elevation);
+ int saved = modifyCanvas(canvas, shadowSize);
+ if (saved == -1) {
+ return;
+ }
+
+ float radius = viewOutline.getRadius();
+ if (radius <= 0) {
+ // We can not paint a shadow with radius 0
+ return;
+ }
+
+ try {
+ Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+ cornerPaint.setStyle(Style.FILL);
+ Paint edgePaint = new Paint(cornerPaint);
+ edgePaint.setAntiAlias(false);
+ float outerArcRadius = radius + shadowSize;
+ int[] colors = {START_COLOR, START_COLOR, END_COLOR};
+ cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
+ new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
+ edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR,
+ TileMode.CLAMP));
+ Path path = new Path();
+ path.setFillType(FillType.EVEN_ODD);
+ // A rectangle bounding the complete shadow.
+ RectF shadowRect = new RectF(outline);
+ shadowRect.inset(-shadowSize, -shadowSize);
+ // A rectangle with edges corresponding to the straight edges of the outline.
+ RectF inset = new RectF(outline);
+ inset.inset(radius, radius);
+ // A rectangle used to represent the edge shadow.
+ RectF edgeShadowRect = new RectF();
+
+
+ // left and right sides.
+ edgeShadowRect.set(-shadowSize, 0f, 0f, inset.height());
+ // Left shadow
+ sideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0);
+ // Right shadow
+ sideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2);
+ // Top shadow
+ edgeShadowRect.set(-shadowSize, 0, 0, inset.width());
+ sideShadow(canvas, edgePaint, edgeShadowRect, inset.right, outline.top, 1);
+ // bottom shadow. This needs an inset so that blank doesn't appear when the content is
+ // moved up.
+ edgeShadowRect.set(-shadowSize, 0, shadowSize / 2f, inset.width());
+ edgePaint.setShader(new LinearGradient(edgeShadowRect.right, 0, edgeShadowRect.left, 0,
+ colors, new float[]{0f, 1 / 3f, 1f}, TileMode.CLAMP));
+ sideShadow(canvas, edgePaint, edgeShadowRect, inset.left, outline.bottom, 3);
+
+ // Draw corners.
+ drawCorner(canvas, cornerPaint, path, inset.right, inset.bottom, outerArcRadius, 0);
+ drawCorner(canvas, cornerPaint, path, inset.left, inset.bottom, outerArcRadius, 1);
+ drawCorner(canvas, cornerPaint, path, inset.left, inset.top, outerArcRadius, 2);
+ drawCorner(canvas, cornerPaint, path, inset.right, inset.top, outerArcRadius, 3);
+ } finally {
+ canvas.restoreToCount(saved);
+ }
+ }
+
+ private static float elevationToShadow(float elevation) {
+ // The factor is chosen by eyeballing the shadow size on device and preview.
+ return elevation * 0.5f;
+ }
+
+ /**
+ * Translate canvas by half of shadow size up, so that it appears that light is coming
+ * slightly from above. Also, remove clipping, so that shadow is not clipped.
+ */
+ private static int modifyCanvas(Canvas canvas, float shadowSize) {
+ Rect clipBounds = canvas.getClipBounds();
+ if (clipBounds.isEmpty()) {
+ return -1;
+ }
+ int saved = canvas.save();
+ // Usually canvas has been translated to the top left corner of the view when this is
+ // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow.
+ // Thus, we just expand in each direction by width and height of the canvas, while staying
+ // inside the original drawing region.
+ GcSnapshot snapshot = Canvas_Delegate.getDelegate(canvas).getSnapshot();
+ Rectangle originalClip = snapshot.getOriginalClip();
+ if (originalClip != null) {
+ canvas.clipRect(originalClip.x, originalClip.y, originalClip.x + originalClip.width,
+ originalClip.y + originalClip.height, Op.REPLACE);
+ canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
+ canvas.getHeight(), Op.INTERSECT);
+ }
+ canvas.translate(0, shadowSize / 2f);
+ return saved;
+ }
+
+ private static void sideShadow(Canvas canvas, Paint edgePaint,
+ RectF edgeShadowRect, float dx, float dy, int rotations) {
+ if (isRectEmpty(edgeShadowRect)) {
+ return;
+ }
+ int saved = canvas.save();
+ canvas.translate(dx, dy);
+ canvas.rotate(rotations * PERPENDICULAR_ANGLE);
+ canvas.drawRect(edgeShadowRect, edgePaint);
+ canvas.restoreToCount(saved);
+ }
+
+ /**
+ * @param canvas Canvas to draw the rectangle on.
+ * @param paint Paint to use when drawing the corner.
+ * @param path A path to reuse. Prevents allocating memory for each path.
+ * @param x Center of circle, which this corner is a part of.
+ * @param y Center of circle, which this corner is a part of.
+ * @param radius radius of the arc
+ * @param rotations number of quarter rotations before starting to paint the arc.
+ */
+ private static void drawCorner(Canvas canvas, Paint paint, Path path, float x, float y,
+ float radius, int rotations) {
+ int saved = canvas.save();
+ canvas.translate(x, y);
+ path.reset();
+ path.arcTo(-radius, -radius, radius, radius, rotations * PERPENDICULAR_ANGLE,
+ PERPENDICULAR_ANGLE, false);
+ path.lineTo(0, 0);
+ path.close();
+ canvas.drawPath(path, paint);
+ canvas.restoreToCount(saved);
+ }
+
+ /**
+ * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
+ * <p/>
+ * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float,
+ * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
+ * drawing empty rectangles, which results in IllegalArgumentException.
+ */
+ private static boolean isRectEmpty(RectF rect) {
+ return (int) rect.left >= (int) rect.right || (int) rect.top >= (int) rect.bottom;
+ }
+}
diff --git a/android/view/RemotableViewMethod.java b/android/view/RemotableViewMethod.java
new file mode 100644
index 00000000..03aed9a5
--- /dev/null
+++ b/android/view/RemotableViewMethod.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @hide
+ * This annotation indicates that a method on a subclass of View
+ * is alllowed to be used with the {@link android.widget.RemoteViews} mechanism.
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RemotableViewMethod {
+ /**
+ * @return Method name which can be called on a background thread. It should have the
+ * same arguments as the original method and should return a {@link Runnable} (or null)
+ * which will be called on the UI thread.
+ */
+ String asyncImpl() default "";
+}
diff --git a/android/view/RenderNode.java b/android/view/RenderNode.java
new file mode 100644
index 00000000..ea6e63c3
--- /dev/null
+++ b/android/view/RenderNode.java
@@ -0,0 +1,977 @@
+/*
+ * Copyright (C) 2010 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.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * <p>A display list records a series of graphics related operations and can replay
+ * them later. Display lists are usually built by recording operations on a
+ * {@link DisplayListCanvas}. Replaying the operations from a display list avoids
+ * executing application code on every frame, and is thus much more efficient.</p>
+ *
+ * <p>Display lists are used internally for all views by default, and are not
+ * typically used directly. One reason to consider using a display is a custom
+ * {@link View} implementation that needs to issue a large number of drawing commands.
+ * When the view invalidates, all the drawing commands must be reissued, even if
+ * large portions of the drawing command stream stay the same frame to frame, which
+ * can become a performance bottleneck. To solve this issue, a custom View might split
+ * its content into several display lists. A display list is updated only when its
+ * content, and only its content, needs to be updated.</p>
+ *
+ * <p>A text editor might for instance store each paragraph into its own display list.
+ * Thus when the user inserts or removes characters, only the display list of the
+ * affected paragraph needs to be recorded again.</p>
+ *
+ * <h3>Hardware acceleration</h3>
+ * <p>Display lists can only be replayed using a {@link DisplayListCanvas}. They are not
+ * supported in software. Always make sure that the {@link android.graphics.Canvas}
+ * you are using to render a display list is hardware accelerated using
+ * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p>
+ *
+ * <h3>Creating a display list</h3>
+ * <pre class="prettyprint">
+ * ThreadedRenderer renderer = myView.getThreadedRenderer();
+ * if (renderer != null) {
+ * DisplayList displayList = renderer.createDisplayList();
+ * DisplayListCanvas canvas = displayList.start(width, height);
+ * try {
+ * // Draw onto the canvas
+ * // For instance: canvas.drawBitmap(...);
+ * } finally {
+ * displayList.end();
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Rendering a display list on a View</h3>
+ * <pre class="prettyprint">
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+ * displayListCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Releasing resources</h3>
+ * <p>This step is not mandatory but recommended if you want to release resources
+ * held by a display list as soon as possible.</p>
+ * <pre class="prettyprint">
+ * // Mark this display list invalid, it cannot be used for drawing anymore,
+ * // and release resources held by this display list
+ * displayList.clear();
+ * </pre>
+ *
+ * <h3>Properties</h3>
+ * <p>In addition, a display list offers several properties, such as
+ * {@link #setScaleX(float)} or {@link #setLeft(int)}, that can be used to affect all
+ * the drawing commands recorded within. For instance, these properties can be used
+ * to move around a large number of images without re-issuing all the individual
+ * <code>drawBitmap()</code> calls.</p>
+ *
+ * <pre class="prettyprint">
+ * private void createDisplayList() {
+ * mDisplayList = DisplayList.create("MyDisplayList");
+ * DisplayListCanvas canvas = mDisplayList.start(width, height);
+ * try {
+ * for (Bitmap b : mBitmaps) {
+ * canvas.drawBitmap(b, 0.0f, 0.0f, null);
+ * canvas.translate(0.0f, b.getHeight());
+ * }
+ * } finally {
+ * displayList.end();
+ * }
+ * }
+ *
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+ * displayListCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ *
+ * private void moveContentBy(int x) {
+ * // This will move all the bitmaps recorded inside the display list
+ * // by x pixels to the right and redraw this view. All the commands
+ * // recorded in createDisplayList() won't be re-issued, only onDraw()
+ * // will be invoked and will execute very quickly
+ * mDisplayList.offsetLeftAndRight(x);
+ * invalidate();
+ * }
+ * </pre>
+ *
+ * <h3>Threading</h3>
+ * <p>Display lists must be created on and manipulated from the UI thread only.</p>
+ *
+ * @hide
+ */
+public class RenderNode {
+
+ // Use a Holder to allow static initialization in the boot image.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024);
+ }
+
+ // Do not access directly unless you are ThreadedRenderer
+ final long mNativeRenderNode;
+ private final View mOwningView;
+
+ private RenderNode(String name, View owningView) {
+ mNativeRenderNode = nCreate(name);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode);
+ mOwningView = owningView;
+ }
+
+ /**
+ * @see RenderNode#adopt(long)
+ */
+ private RenderNode(long nativePtr) {
+ mNativeRenderNode = nativePtr;
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode);
+ mOwningView = null;
+ }
+
+ /**
+ * Immediately destroys the RenderNode
+ * Only suitable for testing/benchmarking where waiting for the GC/finalizer
+ * is not feasible.
+ */
+ public void destroy() {
+ // TODO: Removed temporarily
+ }
+
+ /**
+ * Creates a new RenderNode that can be used to record batches of
+ * drawing operations, and store / apply render properties when drawn.
+ *
+ * @param name The name of the RenderNode, used for debugging purpose. May be null.
+ *
+ * @return A new RenderNode.
+ */
+ public static RenderNode create(String name, @Nullable View owningView) {
+ return new RenderNode(name, owningView);
+ }
+
+ /**
+ * Adopts an existing native render node.
+ *
+ * Note: This will *NOT* incRef() on the native object, however it will
+ * decRef() when it is destroyed. The caller should have already incRef'd it
+ */
+ public static RenderNode adopt(long nativePtr) {
+ return new RenderNode(nativePtr);
+ }
+
+ /**
+ * Enable callbacks for position changes.
+ */
+ public void requestPositionUpdates(SurfaceView view) {
+ nRequestPositionUpdates(mNativeRenderNode, view);
+ }
+
+
+ /**
+ * Starts recording a display list for the render node. All
+ * operations performed on the returned canvas are recorded and
+ * stored in this display list.
+ *
+ * Calling this method will mark the render node invalid until
+ * {@link #end(DisplayListCanvas)} is called.
+ * Only valid render nodes can be replayed.
+ *
+ * @param width The width of the recording viewport
+ * @param height The height of the recording viewport
+ *
+ * @return A canvas to record drawing operations.
+ *
+ * @see #end(DisplayListCanvas)
+ * @see #isValid()
+ */
+ public DisplayListCanvas start(int width, int height) {
+ return DisplayListCanvas.obtain(this, width, height);
+ }
+
+ /**
+ * Ends the recording for this display list. A display list cannot be
+ * replayed if recording is not finished. Calling this method marks
+ * the display list valid and {@link #isValid()} will return true.
+ *
+ * @see #start(int, int)
+ * @see #isValid()
+ */
+ public void end(DisplayListCanvas canvas) {
+ long displayList = canvas.finishRecording();
+ nSetDisplayList(mNativeRenderNode, displayList);
+ canvas.recycle();
+ }
+
+ /**
+ * Reset native resources. This is called when cleaning up the state of display lists
+ * during destruction of hardware resources, to ensure that we do not hold onto
+ * obsolete resources after related resources are gone.
+ */
+ public void discardDisplayList() {
+ nSetDisplayList(mNativeRenderNode, 0);
+ }
+
+ /**
+ * Returns whether the RenderNode's display list content is currently usable.
+ * If this returns false, the display list should be re-recorded prior to replaying it.
+ *
+ * @return boolean true if the display list is able to be replayed, false otherwise.
+ */
+ public boolean isValid() {
+ return nIsValid(mNativeRenderNode);
+ }
+
+ long getNativeDisplayList() {
+ if (!isValid()) {
+ throw new IllegalStateException("The display list is not valid.");
+ }
+ return mNativeRenderNode;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Matrix manipulation
+ ///////////////////////////////////////////////////////////////////////////
+
+ public boolean hasIdentityMatrix() {
+ return nHasIdentityMatrix(mNativeRenderNode);
+ }
+
+ public void getMatrix(@NonNull Matrix outMatrix) {
+ nGetTransformMatrix(mNativeRenderNode, outMatrix.native_instance);
+ }
+
+ public void getInverseMatrix(@NonNull Matrix outMatrix) {
+ nGetInverseTransformMatrix(mNativeRenderNode, outMatrix.native_instance);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // RenderProperty Setters
+ ///////////////////////////////////////////////////////////////////////////
+
+ public boolean setLayerType(int layerType) {
+ return nSetLayerType(mNativeRenderNode, layerType);
+ }
+
+ public boolean setLayerPaint(@Nullable Paint paint) {
+ return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ public boolean setClipBounds(@Nullable Rect rect) {
+ if (rect == null) {
+ return nSetClipBoundsEmpty(mNativeRenderNode);
+ } else {
+ return nSetClipBounds(mNativeRenderNode, rect.left, rect.top, rect.right, rect.bottom);
+ }
+ }
+
+ /**
+ * Set whether the Render node should clip itself to its bounds. This property is controlled by
+ * the view's parent.
+ *
+ * @param clipToBounds true if the display list should clip to its bounds
+ */
+ public boolean setClipToBounds(boolean clipToBounds) {
+ return nSetClipToBounds(mNativeRenderNode, clipToBounds);
+ }
+
+ /**
+ * Sets whether the display list should be drawn immediately after the
+ * closest ancestor display list containing a projection receiver.
+ *
+ * @param shouldProject true if the display list should be projected onto a
+ * containing volume.
+ */
+ public boolean setProjectBackwards(boolean shouldProject) {
+ return nSetProjectBackwards(mNativeRenderNode, shouldProject);
+ }
+
+ /**
+ * Sets whether the display list is a projection receiver - that its parent
+ * DisplayList should draw any descendent DisplayLists with
+ * ProjectBackwards=true directly on top of it. Default value is false.
+ */
+ public boolean setProjectionReceiver(boolean shouldRecieve) {
+ return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve);
+ }
+
+ /**
+ * Sets the outline, defining the shape that casts a shadow, and the path to
+ * be clipped if setClipToOutline is set.
+ *
+ * Deep copies the data into native to simplify reference ownership.
+ */
+ public boolean setOutline(@Nullable Outline outline) {
+ if (outline == null) {
+ return nSetOutlineNone(mNativeRenderNode);
+ }
+
+ switch(outline.mMode) {
+ case Outline.MODE_EMPTY:
+ return nSetOutlineEmpty(mNativeRenderNode);
+ case Outline.MODE_ROUND_RECT:
+ return nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top,
+ outline.mRect.right, outline.mRect.bottom, outline.mRadius, outline.mAlpha);
+ case Outline.MODE_CONVEX_PATH:
+ return nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath,
+ outline.mAlpha);
+ }
+
+ throw new IllegalArgumentException("Unrecognized outline?");
+ }
+
+ public boolean hasShadow() {
+ return nHasShadow(mNativeRenderNode);
+ }
+
+ /**
+ * Enables or disables clipping to the outline.
+ *
+ * @param clipToOutline true if clipping to the outline.
+ */
+ public boolean setClipToOutline(boolean clipToOutline) {
+ return nSetClipToOutline(mNativeRenderNode, clipToOutline);
+ }
+
+ public boolean getClipToOutline() {
+ return nGetClipToOutline(mNativeRenderNode);
+ }
+
+ /**
+ * Controls the RenderNode's circular reveal clip.
+ */
+ public boolean setRevealClip(boolean shouldClip,
+ float x, float y, float radius) {
+ return nSetRevealClip(mNativeRenderNode, shouldClip, x, y, radius);
+ }
+
+ /**
+ * Set the static matrix on the display list. The specified matrix is combined with other
+ * transforms (such as {@link #setScaleX(float)}, {@link #setRotation(float)}, etc.)
+ *
+ * @param matrix A transform matrix to apply to this display list
+ */
+ public boolean setStaticMatrix(Matrix matrix) {
+ return nSetStaticMatrix(mNativeRenderNode, matrix.native_instance);
+ }
+
+ /**
+ * Set the Animation matrix on the display list. This matrix exists if an Animation is
+ * currently playing on a View, and is set on the display list during at draw() time. When
+ * the Animation finishes, the matrix should be cleared by sending <code>null</code>
+ * for the matrix parameter.
+ *
+ * @param matrix The matrix, null indicates that the matrix should be cleared.
+ */
+ public boolean setAnimationMatrix(Matrix matrix) {
+ return nSetAnimationMatrix(mNativeRenderNode,
+ (matrix != null) ? matrix.native_instance : 0);
+ }
+
+ /**
+ * Sets the translucency level for the display list.
+ *
+ * @param alpha The translucency of the display list, must be a value between 0.0f and 1.0f
+ *
+ * @see View#setAlpha(float)
+ * @see #getAlpha()
+ */
+ public boolean setAlpha(float alpha) {
+ return nSetAlpha(mNativeRenderNode, alpha);
+ }
+
+ /**
+ * Returns the translucency level of this display list.
+ *
+ * @return A value between 0.0f and 1.0f
+ *
+ * @see #setAlpha(float)
+ */
+ public float getAlpha() {
+ return nGetAlpha(mNativeRenderNode);
+ }
+
+ /**
+ * Sets whether the display list renders content which overlaps. Non-overlapping rendering
+ * can use a fast path for alpha that avoids rendering to an offscreen buffer. By default
+ * display lists consider they do not have overlapping content.
+ *
+ * @param hasOverlappingRendering False if the content is guaranteed to be non-overlapping,
+ * true otherwise.
+ *
+ * @see android.view.View#hasOverlappingRendering()
+ * @see #hasOverlappingRendering()
+ */
+ public boolean setHasOverlappingRendering(boolean hasOverlappingRendering) {
+ return nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering);
+ }
+
+ /**
+ * Indicates whether the content of this display list overlaps.
+ *
+ * @return True if this display list renders content which overlaps, false otherwise.
+ *
+ * @see #setHasOverlappingRendering(boolean)
+ */
+ public boolean hasOverlappingRendering() {
+ //noinspection SimplifiableIfStatement
+ return nHasOverlappingRendering(mNativeRenderNode);
+ }
+
+ public boolean setElevation(float lift) {
+ return nSetElevation(mNativeRenderNode, lift);
+ }
+
+ public float getElevation() {
+ return nGetElevation(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the translation value for the display list on the X axis.
+ *
+ * @param translationX The X axis translation value of the display list, in pixels
+ *
+ * @see View#setTranslationX(float)
+ * @see #getTranslationX()
+ */
+ public boolean setTranslationX(float translationX) {
+ return nSetTranslationX(mNativeRenderNode, translationX);
+ }
+
+ /**
+ * Returns the translation value for this display list on the X axis, in pixels.
+ *
+ * @see #setTranslationX(float)
+ */
+ public float getTranslationX() {
+ return nGetTranslationX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the translation value for the display list on the Y axis.
+ *
+ * @param translationY The Y axis translation value of the display list, in pixels
+ *
+ * @see View#setTranslationY(float)
+ * @see #getTranslationY()
+ */
+ public boolean setTranslationY(float translationY) {
+ return nSetTranslationY(mNativeRenderNode, translationY);
+ }
+
+ /**
+ * Returns the translation value for this display list on the Y axis, in pixels.
+ *
+ * @see #setTranslationY(float)
+ */
+ public float getTranslationY() {
+ return nGetTranslationY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the translation value for the display list on the Z axis.
+ *
+ * @see View#setTranslationZ(float)
+ * @see #getTranslationZ()
+ */
+ public boolean setTranslationZ(float translationZ) {
+ return nSetTranslationZ(mNativeRenderNode, translationZ);
+ }
+
+ /**
+ * Returns the translation value for this display list on the Z axis.
+ *
+ * @see #setTranslationZ(float)
+ */
+ public float getTranslationZ() {
+ return nGetTranslationZ(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the rotation value for the display list around the Z axis.
+ *
+ * @param rotation The rotation value of the display list, in degrees
+ *
+ * @see View#setRotation(float)
+ * @see #getRotation()
+ */
+ public boolean setRotation(float rotation) {
+ return nSetRotation(mNativeRenderNode, rotation);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the Z axis, in degrees.
+ *
+ * @see #setRotation(float)
+ */
+ public float getRotation() {
+ return nGetRotation(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the rotation value for the display list around the X axis.
+ *
+ * @param rotationX The rotation value of the display list, in degrees
+ *
+ * @see View#setRotationX(float)
+ * @see #getRotationX()
+ */
+ public boolean setRotationX(float rotationX) {
+ return nSetRotationX(mNativeRenderNode, rotationX);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the X axis, in degrees.
+ *
+ * @see #setRotationX(float)
+ */
+ public float getRotationX() {
+ return nGetRotationX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the rotation value for the display list around the Y axis.
+ *
+ * @param rotationY The rotation value of the display list, in degrees
+ *
+ * @see View#setRotationY(float)
+ * @see #getRotationY()
+ */
+ public boolean setRotationY(float rotationY) {
+ return nSetRotationY(mNativeRenderNode, rotationY);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the Y axis, in degrees.
+ *
+ * @see #setRotationY(float)
+ */
+ public float getRotationY() {
+ return nGetRotationY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the scale value for the display list on the X axis.
+ *
+ * @param scaleX The scale value of the display list
+ *
+ * @see View#setScaleX(float)
+ * @see #getScaleX()
+ */
+ public boolean setScaleX(float scaleX) {
+ return nSetScaleX(mNativeRenderNode, scaleX);
+ }
+
+ /**
+ * Returns the scale value for this display list on the X axis.
+ *
+ * @see #setScaleX(float)
+ */
+ public float getScaleX() {
+ return nGetScaleX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the scale value for the display list on the Y axis.
+ *
+ * @param scaleY The scale value of the display list
+ *
+ * @see View#setScaleY(float)
+ * @see #getScaleY()
+ */
+ public boolean setScaleY(float scaleY) {
+ return nSetScaleY(mNativeRenderNode, scaleY);
+ }
+
+ /**
+ * Returns the scale value for this display list on the Y axis.
+ *
+ * @see #setScaleY(float)
+ */
+ public float getScaleY() {
+ return nGetScaleY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the pivot value for the display list on the X axis
+ *
+ * @param pivotX The pivot value of the display list on the X axis, in pixels
+ *
+ * @see View#setPivotX(float)
+ * @see #getPivotX()
+ */
+ public boolean setPivotX(float pivotX) {
+ return nSetPivotX(mNativeRenderNode, pivotX);
+ }
+
+ /**
+ * Returns the pivot value for this display list on the X axis, in pixels.
+ *
+ * @see #setPivotX(float)
+ */
+ public float getPivotX() {
+ return nGetPivotX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the pivot value for the display list on the Y axis
+ *
+ * @param pivotY The pivot value of the display list on the Y axis, in pixels
+ *
+ * @see View#setPivotY(float)
+ * @see #getPivotY()
+ */
+ public boolean setPivotY(float pivotY) {
+ return nSetPivotY(mNativeRenderNode, pivotY);
+ }
+
+ /**
+ * Returns the pivot value for this display list on the Y axis, in pixels.
+ *
+ * @see #setPivotY(float)
+ */
+ public float getPivotY() {
+ return nGetPivotY(mNativeRenderNode);
+ }
+
+ public boolean isPivotExplicitlySet() {
+ return nIsPivotExplicitlySet(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the camera distance for the display list. Refer to
+ * {@link View#setCameraDistance(float)} for more information on how to
+ * use this property.
+ *
+ * @param distance The distance in Z of the camera of the display list
+ *
+ * @see View#setCameraDistance(float)
+ * @see #getCameraDistance()
+ */
+ public boolean setCameraDistance(float distance) {
+ return nSetCameraDistance(mNativeRenderNode, distance);
+ }
+
+ /**
+ * Returns the distance in Z of the camera of the display list.
+ *
+ * @see #setCameraDistance(float)
+ */
+ public float getCameraDistance() {
+ return nGetCameraDistance(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the left position for the display list.
+ *
+ * @param left The left position, in pixels, of the display list
+ *
+ * @see View#setLeft(int)
+ */
+ public boolean setLeft(int left) {
+ return nSetLeft(mNativeRenderNode, left);
+ }
+
+ /**
+ * Sets the top position for the display list.
+ *
+ * @param top The top position, in pixels, of the display list
+ *
+ * @see View#setTop(int)
+ */
+ public boolean setTop(int top) {
+ return nSetTop(mNativeRenderNode, top);
+ }
+
+ /**
+ * Sets the right position for the display list.
+ *
+ * @param right The right position, in pixels, of the display list
+ *
+ * @see View#setRight(int)
+ */
+ public boolean setRight(int right) {
+ return nSetRight(mNativeRenderNode, right);
+ }
+
+ /**
+ * Sets the bottom position for the display list.
+ *
+ * @param bottom The bottom position, in pixels, of the display list
+ *
+ * @see View#setBottom(int)
+ */
+ public boolean setBottom(int bottom) {
+ return nSetBottom(mNativeRenderNode, bottom);
+ }
+
+ /**
+ * Sets the left and top positions for the display list
+ *
+ * @param left The left position of the display list, in pixels
+ * @param top The top position of the display list, in pixels
+ * @param right The right position of the display list, in pixels
+ * @param bottom The bottom position of the display list, in pixels
+ *
+ * @see View#setLeft(int)
+ * @see View#setTop(int)
+ * @see View#setRight(int)
+ * @see View#setBottom(int)
+ */
+ public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
+ }
+
+ /**
+ * Offsets the left and right positions for the display list
+ *
+ * @param offset The amount that the left and right positions of the display
+ * list are offset, in pixels
+ *
+ * @see View#offsetLeftAndRight(int)
+ */
+ public boolean offsetLeftAndRight(int offset) {
+ return nOffsetLeftAndRight(mNativeRenderNode, offset);
+ }
+
+ /**
+ * Offsets the top and bottom values for the display list
+ *
+ * @param offset The amount that the top and bottom positions of the display
+ * list are offset, in pixels
+ *
+ * @see View#offsetTopAndBottom(int)
+ */
+ public boolean offsetTopAndBottom(int offset) {
+ return nOffsetTopAndBottom(mNativeRenderNode, offset);
+ }
+
+ /**
+ * Outputs the display list to the log. This method exists for use by
+ * tools to output display lists for selected nodes to the log.
+ */
+ public void output() {
+ nOutput(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the size of the DisplayList for debug purposes.
+ */
+ public int getDebugSize() {
+ return nGetDebugSize(mNativeRenderNode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Animations
+ ///////////////////////////////////////////////////////////////////////////
+
+ public void addAnimator(RenderNodeAnimator animator) {
+ if (mOwningView == null || mOwningView.mAttachInfo == null) {
+ throw new IllegalStateException("Cannot start this animator on a detached view!");
+ }
+ nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
+ mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
+ }
+
+ public boolean isAttached() {
+ return mOwningView != null && mOwningView.mAttachInfo != null;
+ }
+
+ public void registerVectorDrawableAnimator(
+ AnimatedVectorDrawable.VectorDrawableAnimatorRT animatorSet) {
+ if (mOwningView == null || mOwningView.mAttachInfo == null) {
+ throw new IllegalStateException("Cannot start this animator on a detached view!");
+ }
+ mOwningView.mAttachInfo.mViewRootImpl.registerVectorDrawableAnimator(animatorSet);
+ }
+
+ public void endAllAnimators() {
+ nEndAllAnimators(mNativeRenderNode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Regular JNI methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native long nCreate(String name);
+
+ private static native long nGetNativeFinalizer();
+ private static native void nOutput(long renderNode);
+ private static native int nGetDebugSize(long renderNode);
+ private static native void nRequestPositionUpdates(long renderNode, SurfaceView callback);
+
+ // Animations
+
+ private static native void nAddAnimator(long renderNode, long animatorPtr);
+ private static native void nEndAllAnimators(long renderNode);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // @FastNative methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ @FastNative
+ private static native void nSetDisplayList(long renderNode, long newData);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // @CriticalNative methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ @CriticalNative
+ private static native boolean nIsValid(long renderNode);
+
+ // Matrix
+
+ @CriticalNative
+ private static native void nGetTransformMatrix(long renderNode, long nativeMatrix);
+ @CriticalNative
+ private static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix);
+ @CriticalNative
+ private static native boolean nHasIdentityMatrix(long renderNode);
+
+ // Properties
+
+ @CriticalNative
+ private static native boolean nOffsetTopAndBottom(long renderNode, int offset);
+ @CriticalNative
+ private static native boolean nOffsetLeftAndRight(long renderNode, int offset);
+ @CriticalNative
+ private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top,
+ int right, int bottom);
+ @CriticalNative
+ private static native boolean nSetBottom(long renderNode, int bottom);
+ @CriticalNative
+ private static native boolean nSetRight(long renderNode, int right);
+ @CriticalNative
+ private static native boolean nSetTop(long renderNode, int top);
+ @CriticalNative
+ private static native boolean nSetLeft(long renderNode, int left);
+ @CriticalNative
+ private static native boolean nSetCameraDistance(long renderNode, float distance);
+ @CriticalNative
+ private static native boolean nSetPivotY(long renderNode, float pivotY);
+ @CriticalNative
+ private static native boolean nSetPivotX(long renderNode, float pivotX);
+ @CriticalNative
+ private static native boolean nSetLayerType(long renderNode, int layerType);
+ @CriticalNative
+ private static native boolean nSetLayerPaint(long renderNode, long paint);
+ @CriticalNative
+ private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
+ @CriticalNative
+ private static native boolean nSetClipBounds(long renderNode, int left, int top,
+ int right, int bottom);
+ @CriticalNative
+ private static native boolean nSetClipBoundsEmpty(long renderNode);
+ @CriticalNative
+ private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
+ @CriticalNative
+ private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+ @CriticalNative
+ private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
+ int right, int bottom, float radius, float alpha);
+ @CriticalNative
+ private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath,
+ float alpha);
+ @CriticalNative
+ private static native boolean nSetOutlineEmpty(long renderNode);
+ @CriticalNative
+ private static native boolean nSetOutlineNone(long renderNode);
+ @CriticalNative
+ private static native boolean nHasShadow(long renderNode);
+ @CriticalNative
+ private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
+ @CriticalNative
+ private static native boolean nSetRevealClip(long renderNode,
+ boolean shouldClip, float x, float y, float radius);
+ @CriticalNative
+ private static native boolean nSetAlpha(long renderNode, float alpha);
+ @CriticalNative
+ private static native boolean nSetHasOverlappingRendering(long renderNode,
+ boolean hasOverlappingRendering);
+ @CriticalNative
+ private static native boolean nSetElevation(long renderNode, float lift);
+ @CriticalNative
+ private static native boolean nSetTranslationX(long renderNode, float translationX);
+ @CriticalNative
+ private static native boolean nSetTranslationY(long renderNode, float translationY);
+ @CriticalNative
+ private static native boolean nSetTranslationZ(long renderNode, float translationZ);
+ @CriticalNative
+ private static native boolean nSetRotation(long renderNode, float rotation);
+ @CriticalNative
+ private static native boolean nSetRotationX(long renderNode, float rotationX);
+ @CriticalNative
+ private static native boolean nSetRotationY(long renderNode, float rotationY);
+ @CriticalNative
+ private static native boolean nSetScaleX(long renderNode, float scaleX);
+ @CriticalNative
+ private static native boolean nSetScaleY(long renderNode, float scaleY);
+ @CriticalNative
+ private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix);
+ @CriticalNative
+ private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix);
+
+ @CriticalNative
+ private static native boolean nHasOverlappingRendering(long renderNode);
+ @CriticalNative
+ private static native boolean nGetClipToOutline(long renderNode);
+ @CriticalNative
+ private static native float nGetAlpha(long renderNode);
+ @CriticalNative
+ private static native float nGetCameraDistance(long renderNode);
+ @CriticalNative
+ private static native float nGetScaleX(long renderNode);
+ @CriticalNative
+ private static native float nGetScaleY(long renderNode);
+ @CriticalNative
+ private static native float nGetElevation(long renderNode);
+ @CriticalNative
+ private static native float nGetTranslationX(long renderNode);
+ @CriticalNative
+ private static native float nGetTranslationY(long renderNode);
+ @CriticalNative
+ private static native float nGetTranslationZ(long renderNode);
+ @CriticalNative
+ private static native float nGetRotation(long renderNode);
+ @CriticalNative
+ private static native float nGetRotationX(long renderNode);
+ @CriticalNative
+ private static native float nGetRotationY(long renderNode);
+ @CriticalNative
+ private static native boolean nIsPivotExplicitlySet(long renderNode);
+ @CriticalNative
+ private static native float nGetPivotX(long renderNode);
+ @CriticalNative
+ private static native float nGetPivotY(long renderNode);
+}
diff --git a/android/view/RenderNodeAnimator.java b/android/view/RenderNodeAnimator.java
new file mode 100644
index 00000000..95150409
--- /dev/null
+++ b/android/view/RenderNodeAnimator.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2014 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.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.VirtualRefBasePtr;
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class RenderNodeAnimator extends Animator {
+ // Keep in sync with enum RenderProperty in Animator.h
+ public static final int TRANSLATION_X = 0;
+ public static final int TRANSLATION_Y = 1;
+ public static final int TRANSLATION_Z = 2;
+ public static final int SCALE_X = 3;
+ public static final int SCALE_Y = 4;
+ public static final int ROTATION = 5;
+ public static final int ROTATION_X = 6;
+ public static final int ROTATION_Y = 7;
+ public static final int X = 8;
+ public static final int Y = 9;
+ public static final int Z = 10;
+ public static final int ALPHA = 11;
+ // The last value in the enum, used for array size initialization
+ public static final int LAST_VALUE = ALPHA;
+
+ // Keep in sync with enum PaintFields in Animator.h
+ public static final int PAINT_STROKE_WIDTH = 0;
+
+ /**
+ * Field for the Paint alpha channel, which should be specified as a value
+ * between 0 and 255.
+ */
+ public static final int PAINT_ALPHA = 1;
+
+ // ViewPropertyAnimator uses a mask for its values, we need to remap them
+ // to the enum values here. RenderPropertyAnimator can't use the mask values
+ // directly as internally it uses a lookup table so it needs the values to
+ // be sequential starting from 0
+ private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
+ put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
+ put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
+ put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
+ put(ViewPropertyAnimator.SCALE_X, SCALE_X);
+ put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
+ put(ViewPropertyAnimator.ROTATION, ROTATION);
+ put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
+ put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
+ put(ViewPropertyAnimator.X, X);
+ put(ViewPropertyAnimator.Y, Y);
+ put(ViewPropertyAnimator.Z, Z);
+ put(ViewPropertyAnimator.ALPHA, ALPHA);
+ }};
+
+ private VirtualRefBasePtr mNativePtr;
+
+ private RenderNode mTarget;
+ private View mViewTarget;
+ private int mRenderProperty = -1;
+ private float mFinalValue;
+ private TimeInterpolator mInterpolator;
+
+ private static final int STATE_PREPARE = 0;
+ private static final int STATE_DELAYED = 1;
+ private static final int STATE_RUNNING = 2;
+ private static final int STATE_FINISHED = 3;
+ private int mState = STATE_PREPARE;
+
+ private long mUnscaledDuration = 300;
+ private long mUnscaledStartDelay = 0;
+ // If this is true, we will run any start delays on the UI thread. This is
+ // the safe default, and is necessary to ensure start listeners fire at
+ // the correct time. Animators created by RippleDrawable (the
+ // CanvasProperty<> ones) do not have this expectation, and as such will
+ // set this to false so that the renderthread handles the startdelay instead
+ private final boolean mUiThreadHandlesDelay;
+ private long mStartDelay = 0;
+ private long mStartTime;
+
+ public static int mapViewPropertyToRenderProperty(int viewProperty) {
+ return sViewPropertyAnimatorMap.get(viewProperty);
+ }
+
+ public RenderNodeAnimator(int property, float finalValue) {
+ mRenderProperty = property;
+ mFinalValue = finalValue;
+ mUiThreadHandlesDelay = true;
+ init(nCreateAnimator(property, finalValue));
+ }
+
+ public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
+ init(nCreateCanvasPropertyFloatAnimator(
+ property.getNativeContainer(), finalValue));
+ mUiThreadHandlesDelay = false;
+ }
+
+ /**
+ * Creates a new render node animator for a field on a Paint property.
+ *
+ * @param property The paint property to target
+ * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
+ * {@link #PAINT_STROKE_WIDTH}
+ * @param finalValue The target value for the property
+ */
+ public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
+ init(nCreateCanvasPropertyPaintAnimator(
+ property.getNativeContainer(), paintField, finalValue));
+ mUiThreadHandlesDelay = false;
+ }
+
+ public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
+ init(nCreateRevealAnimator(x, y, startRadius, endRadius));
+ mUiThreadHandlesDelay = true;
+ }
+
+ private void init(long ptr) {
+ mNativePtr = new VirtualRefBasePtr(ptr);
+ }
+
+ private void checkMutable() {
+ if (mState != STATE_PREPARE) {
+ throw new IllegalStateException("Animator has already started, cannot change it now!");
+ }
+ if (mNativePtr == null) {
+ throw new IllegalStateException("Animator's target has been destroyed "
+ + "(trying to modify an animation after activity destroy?)");
+ }
+ }
+
+ static boolean isNativeInterpolator(TimeInterpolator interpolator) {
+ return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
+ }
+
+ private void applyInterpolator() {
+ if (mInterpolator == null) return;
+
+ long ni;
+ if (isNativeInterpolator(mInterpolator)) {
+ ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
+ } else {
+ long duration = nGetDuration(mNativePtr.get());
+ ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
+ }
+ nSetInterpolator(mNativePtr.get(), ni);
+ }
+
+ @Override
+ public void start() {
+ if (mTarget == null) {
+ throw new IllegalStateException("Missing target!");
+ }
+
+ if (mState != STATE_PREPARE) {
+ throw new IllegalStateException("Already started!");
+ }
+
+ mState = STATE_DELAYED;
+ applyInterpolator();
+
+ if (mNativePtr == null) {
+ // It's dead, immediately cancel
+ cancel();
+ } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
+ nSetStartDelay(mNativePtr.get(), mStartDelay);
+ doStart();
+ } else {
+ getHelper().addDelayedAnimation(this);
+ }
+ }
+
+ private void doStart() {
+ // Alpha is a special snowflake that has the canonical value stored
+ // in mTransformationInfo instead of in RenderNode, so we need to update
+ // it with the final value here.
+ if (mRenderProperty == RenderNodeAnimator.ALPHA) {
+ mViewTarget.ensureTransformationInfo();
+ mViewTarget.mTransformationInfo.mAlpha = mFinalValue;
+ }
+
+ moveToRunningState();
+
+ if (mViewTarget != null) {
+ // Kick off a frame to start the process
+ mViewTarget.invalidateViewProperty(true, false);
+ }
+ }
+
+ private void moveToRunningState() {
+ mState = STATE_RUNNING;
+ if (mNativePtr != null) {
+ nStart(mNativePtr.get());
+ }
+ notifyStartListeners();
+ }
+
+ private void notifyStartListeners() {
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationStart(this);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
+ if (mState == STATE_DELAYED) {
+ getHelper().removeDelayedAnimation(this);
+ moveToRunningState();
+ }
+
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationCancel(this);
+ }
+
+ end();
+ }
+ }
+
+ @Override
+ public void end() {
+ if (mState != STATE_FINISHED) {
+ if (mState < STATE_RUNNING) {
+ getHelper().removeDelayedAnimation(this);
+ doStart();
+ }
+ if (mNativePtr != null) {
+ nEnd(mNativePtr.get());
+ if (mViewTarget != null) {
+ // Kick off a frame to flush the state change
+ mViewTarget.invalidateViewProperty(true, false);
+ }
+ } else {
+ // It's already dead, jump to onFinish
+ onFinished();
+ }
+ }
+ }
+
+ @Override
+ public void pause() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resume() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setTarget(View view) {
+ mViewTarget = view;
+ setTarget(mViewTarget.mRenderNode);
+ }
+
+ public void setTarget(Canvas canvas) {
+ if (!(canvas instanceof DisplayListCanvas)) {
+ throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
+ }
+ final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
+ setTarget(recordingCanvas.mNode);
+ }
+
+ private void setTarget(RenderNode node) {
+ checkMutable();
+ if (mTarget != null) {
+ throw new IllegalStateException("Target already set!");
+ }
+ nSetListener(mNativePtr.get(), this);
+ mTarget = node;
+ mTarget.addAnimator(this);
+ }
+
+ public void setStartValue(float startValue) {
+ checkMutable();
+ nSetStartValue(mNativePtr.get(), startValue);
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+ checkMutable();
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
+ }
+ mUnscaledStartDelay = startDelay;
+ mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
+ }
+
+ @Override
+ public long getStartDelay() {
+ return mUnscaledStartDelay;
+ }
+
+ @Override
+ public RenderNodeAnimator setDuration(long duration) {
+ checkMutable();
+ if (duration < 0) {
+ throw new IllegalArgumentException("duration must be positive; " + duration);
+ }
+ mUnscaledDuration = duration;
+ nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
+ return this;
+ }
+
+ @Override
+ public long getDuration() {
+ return mUnscaledDuration;
+ }
+
+ @Override
+ public long getTotalDuration() {
+ return mUnscaledDuration + mUnscaledStartDelay;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mState == STATE_DELAYED || mState == STATE_RUNNING;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mState != STATE_PREPARE;
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator interpolator) {
+ checkMutable();
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ protected void onFinished() {
+ if (mState == STATE_PREPARE) {
+ // Unlikely but possible, the native side has been destroyed
+ // before we have started.
+ releaseNativePtr();
+ return;
+ }
+ if (mState == STATE_DELAYED) {
+ getHelper().removeDelayedAnimation(this);
+ notifyStartListeners();
+ }
+ mState = STATE_FINISHED;
+
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationEnd(this);
+ }
+
+ // Release the native object, as it has a global reference to us. This
+ // breaks the cyclic reference chain, and allows this object to be
+ // GC'd
+ releaseNativePtr();
+ }
+
+ private void releaseNativePtr() {
+ if (mNativePtr != null) {
+ mNativePtr.release();
+ mNativePtr = null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private ArrayList<AnimatorListener> cloneListeners() {
+ ArrayList<AnimatorListener> listeners = getListeners();
+ if (listeners != null) {
+ listeners = (ArrayList<AnimatorListener>) listeners.clone();
+ }
+ return listeners;
+ }
+
+ long getNativeAnimator() {
+ return mNativePtr.get();
+ }
+
+ /**
+ * @return true if the animator was started, false if still delayed
+ */
+ private boolean processDelayed(long frameTimeMs) {
+ if (mStartTime == 0) {
+ mStartTime = frameTimeMs;
+ } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
+ doStart();
+ return true;
+ }
+ return false;
+ }
+
+ private static DelayedAnimationHelper getHelper() {
+ DelayedAnimationHelper helper = sAnimationHelper.get();
+ if (helper == null) {
+ helper = new DelayedAnimationHelper();
+ sAnimationHelper.set(helper);
+ }
+ return helper;
+ }
+
+ private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
+ new ThreadLocal<DelayedAnimationHelper>();
+
+ private static class DelayedAnimationHelper implements Runnable {
+
+ private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
+ private final Choreographer mChoreographer;
+ private boolean mCallbackScheduled;
+
+ public DelayedAnimationHelper() {
+ mChoreographer = Choreographer.getInstance();
+ }
+
+ public void addDelayedAnimation(RenderNodeAnimator animator) {
+ mDelayedAnims.add(animator);
+ scheduleCallback();
+ }
+
+ public void removeDelayedAnimation(RenderNodeAnimator animator) {
+ mDelayedAnims.remove(animator);
+ }
+
+ private void scheduleCallback() {
+ if (!mCallbackScheduled) {
+ mCallbackScheduled = true;
+ mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+ }
+ }
+
+ @Override
+ public void run() {
+ long frameTimeMs = mChoreographer.getFrameTime();
+ mCallbackScheduled = false;
+
+ int end = 0;
+ for (int i = 0; i < mDelayedAnims.size(); i++) {
+ RenderNodeAnimator animator = mDelayedAnims.get(i);
+ if (!animator.processDelayed(frameTimeMs)) {
+ if (end != i) {
+ mDelayedAnims.set(end, animator);
+ }
+ end++;
+ }
+ }
+ while (mDelayedAnims.size() > end) {
+ mDelayedAnims.remove(mDelayedAnims.size() - 1);
+ }
+
+ if (mDelayedAnims.size() > 0) {
+ scheduleCallback();
+ }
+ }
+ }
+
+ // Called by native
+ private static void callOnFinished(RenderNodeAnimator animator) {
+ animator.onFinished();
+ }
+
+ @Override
+ public Animator clone() {
+ throw new IllegalStateException("Cannot clone this animator");
+ }
+
+ @Override
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ checkMutable();
+ nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
+ }
+
+ private static native long nCreateAnimator(int property, float finalValue);
+ private static native long nCreateCanvasPropertyFloatAnimator(
+ long canvasProperty, float finalValue);
+ private static native long nCreateCanvasPropertyPaintAnimator(
+ long canvasProperty, int paintField, float finalValue);
+ private static native long nCreateRevealAnimator(
+ int x, int y, float startRadius, float endRadius);
+
+ private static native void nSetStartValue(long nativePtr, float startValue);
+ private static native void nSetDuration(long nativePtr, long duration);
+ private static native long nGetDuration(long nativePtr);
+ private static native void nSetStartDelay(long nativePtr, long startDelay);
+ private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+ private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
+ private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
+
+ private static native void nStart(long animPtr);
+ private static native void nEnd(long animPtr);
+}
diff --git a/android/view/RenderNodeAnimatorSetHelper.java b/android/view/RenderNodeAnimatorSetHelper.java
new file mode 100644
index 00000000..e1ef0594
--- /dev/null
+++ b/android/view/RenderNodeAnimatorSetHelper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 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.animation.TimeInterpolator;
+
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * This is a helper class to get access to methods and fields needed for RenderNodeAnimatorSet
+ * that are internal or package private to android.view package.
+ *
+ * @hide
+ */
+public class RenderNodeAnimatorSetHelper {
+
+ public static RenderNode getTarget(DisplayListCanvas recordingCanvas) {
+ return recordingCanvas.mNode;
+ }
+
+ public static long createNativeInterpolator(TimeInterpolator interpolator, long
+ duration) {
+ if (interpolator == null) {
+ // create LinearInterpolator
+ return NativeInterpolatorFactoryHelper.createLinearInterpolator();
+ } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+ return ((NativeInterpolatorFactory)interpolator).createNativeInterpolator();
+ } else {
+ return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
+ }
+ }
+
+}
diff --git a/android/view/RenderNode_Delegate.java b/android/view/RenderNode_Delegate.java
new file mode 100644
index 00000000..152878bb
--- /dev/null
+++ b/android/view/RenderNode_Delegate.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2014 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 com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Matrix;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of {@link RenderNode}
+ * <p/>
+ * Through the layoutlib_create tool, some native methods of RenderNode have been replaced by calls
+ * to methods of the same name in this delegate class.
+ *
+ * @see DelegateManager
+ */
+public class RenderNode_Delegate {
+
+
+ // ---- delegate manager ----
+ private static final DelegateManager<RenderNode_Delegate> sManager =
+ new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class);
+ private static long sFinalizer = -1;
+
+ private float mLift;
+ private float mTranslationX;
+ private float mTranslationY;
+ private float mTranslationZ;
+ private float mRotation;
+ private float mScaleX = 1;
+ private float mScaleY = 1;
+ private float mPivotX;
+ private float mPivotY;
+ private boolean mPivotExplicitlySet;
+ private int mLeft;
+ private int mRight;
+ private int mTop;
+ private int mBottom;
+ @SuppressWarnings("UnusedDeclaration")
+ private String mName;
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreate(String name) {
+ RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate();
+ renderNodeDelegate.mName = name;
+ return sManager.addNewDelegate(renderNodeDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nGetNativeFinalizer() {
+ synchronized (RenderNode_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetElevation(long renderNode, float lift) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mLift != lift) {
+ delegate.mLift = lift;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetElevation(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mLift;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetTranslationX(long renderNode, float translationX) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mTranslationX != translationX) {
+ delegate.mTranslationX = translationX;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetTranslationX(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mTranslationX;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetTranslationY(long renderNode, float translationY) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mTranslationY != translationY) {
+ delegate.mTranslationY = translationY;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetTranslationY(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mTranslationY;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetTranslationZ(long renderNode, float translationZ) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mTranslationZ != translationZ) {
+ delegate.mTranslationZ = translationZ;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetTranslationZ(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mTranslationZ;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetRotation(long renderNode, float rotation) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mRotation != rotation) {
+ delegate.mRotation = rotation;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetRotation(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mRotation;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void getMatrix(RenderNode renderNode, Matrix outMatrix) {
+ outMatrix.reset();
+ if (renderNode != null) {
+ float rotation = renderNode.getRotation();
+ float translationX = renderNode.getTranslationX();
+ float translationY = renderNode.getTranslationY();
+ float pivotX = renderNode.getPivotX();
+ float pivotY = renderNode.getPivotY();
+ float scaleX = renderNode.getScaleX();
+ float scaleY = renderNode.getScaleY();
+
+ outMatrix.setTranslate(translationX, translationY);
+ outMatrix.preRotate(rotation, pivotX, pivotY);
+ outMatrix.preScale(scaleX, scaleY, pivotX, pivotY);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetLeft(long renderNode, int left) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mLeft != left) {
+ delegate.mLeft = left;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetTop(long renderNode, int top) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mTop != top) {
+ delegate.mTop = top;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetRight(long renderNode, int right) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mRight != right) {
+ delegate.mRight = right;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetBottom(long renderNode, int bottom) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mBottom != bottom) {
+ delegate.mBottom = bottom;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetLeftTopRightBottom(long renderNode, int left, int top, int right,
+ int bottom) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && (delegate.mLeft != left || delegate.mTop != top || delegate
+ .mRight != right || delegate.mBottom != bottom)) {
+ delegate.mLeft = left;
+ delegate.mTop = top;
+ delegate.mRight = right;
+ delegate.mBottom = bottom;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nIsPivotExplicitlySet(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ return delegate != null && delegate.mPivotExplicitlySet;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetPivotX(long renderNode, float pivotX) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ delegate.mPivotX = pivotX;
+ delegate.mPivotExplicitlySet = true;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetPivotX(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ if (delegate.mPivotExplicitlySet) {
+ return delegate.mPivotX;
+ } else {
+ return (delegate.mRight - delegate.mLeft) / 2.0f;
+ }
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetPivotY(long renderNode, float pivotY) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ delegate.mPivotY = pivotY;
+ delegate.mPivotExplicitlySet = true;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetPivotY(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ if (delegate.mPivotExplicitlySet) {
+ return delegate.mPivotY;
+ } else {
+ return (delegate.mBottom - delegate.mTop) / 2.0f;
+ }
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetScaleX(long renderNode, float scaleX) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mScaleX != scaleX) {
+ delegate.mScaleX = scaleX;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetScaleX(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mScaleX;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetScaleY(long renderNode, float scaleY) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mScaleY != scaleY) {
+ delegate.mScaleY = scaleY;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetScaleY(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mScaleY;
+ }
+ return 0f;
+ }
+}
diff --git a/android/view/RoundScrollbarRenderer.java b/android/view/RoundScrollbarRenderer.java
new file mode 100644
index 00000000..13485104
--- /dev/null
+++ b/android/view/RoundScrollbarRenderer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 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.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * Helper class for drawing round scroll bars on round Wear devices.
+ */
+class RoundScrollbarRenderer {
+ // The range of the scrollbar position represented as an angle in degrees.
+ private static final int SCROLLBAR_ANGLE_RANGE = 90;
+ private static final int MAX_SCROLLBAR_ANGLE_SWIPE = 16;
+ private static final int MIN_SCROLLBAR_ANGLE_SWIPE = 6;
+ private static final float WIDTH_PERCENTAGE = 0.02f;
+ private static final int DEFAULT_THUMB_COLOR = 0x4CFFFFFF;
+ private static final int DEFAULT_TRACK_COLOR = 0x26FFFFFF;
+
+ private final Paint mThumbPaint = new Paint();
+ private final Paint mTrackPaint = new Paint();
+ private final RectF mRect = new RectF();
+ private final View mParent;
+
+ public RoundScrollbarRenderer(View parent) {
+ // Paints for the round scrollbar.
+ // Set up the thumb paint
+ mThumbPaint.setAntiAlias(true);
+ mThumbPaint.setStrokeCap(Paint.Cap.ROUND);
+ mThumbPaint.setStyle(Paint.Style.STROKE);
+
+ // Set up the track paint
+ mTrackPaint.setAntiAlias(true);
+ mTrackPaint.setStrokeCap(Paint.Cap.ROUND);
+ mTrackPaint.setStyle(Paint.Style.STROKE);
+
+ mParent = parent;
+ }
+
+ public void drawRoundScrollbars(Canvas canvas, float alpha, Rect bounds) {
+ if (alpha == 0) {
+ return;
+ }
+ // Get information about the current scroll state of the parent view.
+ float maxScroll = mParent.computeVerticalScrollRange();
+ float scrollExtent = mParent.computeVerticalScrollExtent();
+ if (scrollExtent <= 0 || maxScroll <= scrollExtent) {
+ return;
+ }
+ float currentScroll = Math.max(0, mParent.computeVerticalScrollOffset());
+ float linearThumbLength = mParent.computeVerticalScrollExtent();
+ float thumbWidth = mParent.getWidth() * WIDTH_PERCENTAGE;
+ mThumbPaint.setStrokeWidth(thumbWidth);
+ mTrackPaint.setStrokeWidth(thumbWidth);
+
+ setThumbColor(applyAlpha(DEFAULT_THUMB_COLOR, alpha));
+ setTrackColor(applyAlpha(DEFAULT_TRACK_COLOR, alpha));
+
+ // Normalize the sweep angle for the scroll bar.
+ float sweepAngle = (linearThumbLength / maxScroll) * SCROLLBAR_ANGLE_RANGE;
+ sweepAngle = clamp(sweepAngle, MIN_SCROLLBAR_ANGLE_SWIPE, MAX_SCROLLBAR_ANGLE_SWIPE);
+ // Normalize the start angle so that it falls on the track.
+ float startAngle = (currentScroll * (SCROLLBAR_ANGLE_RANGE - sweepAngle))
+ / (maxScroll - linearThumbLength) - SCROLLBAR_ANGLE_RANGE / 2;
+ startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2,
+ SCROLLBAR_ANGLE_RANGE / 2 - sweepAngle);
+
+ // Draw the track and the scroll bar.
+ mRect.set(
+ bounds.left - thumbWidth / 2,
+ bounds.top,
+ bounds.right - thumbWidth / 2,
+ bounds.bottom);
+
+ canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE, false,
+ mTrackPaint);
+ canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint);
+ }
+
+ private static float clamp(float val, float min, float max) {
+ if (val < min) {
+ return min;
+ } else if (val > max) {
+ return max;
+ } else {
+ return val;
+ }
+ }
+
+ private static int applyAlpha(int color, float alpha) {
+ int alphaByte = (int) (Color.alpha(color) * alpha);
+ return Color.argb(alphaByte, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ private void setThumbColor(int thumbColor) {
+ if (mThumbPaint.getColor() != thumbColor) {
+ mThumbPaint.setColor(thumbColor);
+ }
+ }
+
+ private void setTrackColor(int trackColor) {
+ if (mTrackPaint.getColor() != trackColor) {
+ mTrackPaint.setColor(trackColor);
+ }
+ }
+}
diff --git a/android/view/ScaleGestureDetector.java b/android/view/ScaleGestureDetector.java
new file mode 100644
index 00000000..9787494c
--- /dev/null
+++ b/android/view/ScaleGestureDetector.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2010 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.content.Context;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Handler;
+
+/**
+ * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
+ * The {@link OnScaleGestureListener} callback will notify users when a particular
+ * gesture event has occurred.
+ *
+ * This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code ScaleGestureDetector} for your
+ * {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ * </ul>
+ */
+public class ScaleGestureDetector {
+ private static final String TAG = "ScaleGestureDetector";
+
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnScaleGestureListener}.
+ *
+ * An application will receive events in the following order:
+ * <ul>
+ * <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
+ * <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
+ * <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
+ * </ul>
+ */
+ public interface OnScaleGestureListener {
+ /**
+ * Responds to scaling events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should consider this event
+ * as handled. If an event was not handled, the detector
+ * will continue to accumulate movement until an event is
+ * handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is
+ * greater than 0.01.
+ */
+ public boolean onScale(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a scaling gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should continue recognizing
+ * this gesture. For example, if a gesture is beginning
+ * with a focal point outside of a region where it makes
+ * sense, onScaleBegin() may return false to ignore the
+ * rest of the gesture.
+ */
+ public boolean onScaleBegin(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the end of a scale gesture. Reported by existing
+ * pointers going up.
+ *
+ * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+ * and {@link ScaleGestureDetector#getFocusY()} will return focal point
+ * of the pointers remaining on the screen.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ */
+ public void onScaleEnd(ScaleGestureDetector detector);
+ }
+
+ /**
+ * A convenience class to extend when you only want to listen for a subset
+ * of scaling-related events. This implements all methods in
+ * {@link OnScaleGestureListener} but does nothing.
+ * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
+ * {@code false} so that a subclass can retrieve the accumulated scale
+ * factor in an overridden onScaleEnd.
+ * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
+ * {@code true}.
+ */
+ public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ // Intentionally empty
+ }
+ }
+
+ private final Context mContext;
+ private final OnScaleGestureListener mListener;
+
+ private float mFocusX;
+ private float mFocusY;
+
+ private boolean mQuickScaleEnabled;
+ private boolean mStylusScaleEnabled;
+
+ private float mCurrSpan;
+ private float mPrevSpan;
+ private float mInitialSpan;
+ private float mCurrSpanX;
+ private float mCurrSpanY;
+ private float mPrevSpanX;
+ private float mPrevSpanY;
+ private long mCurrTime;
+ private long mPrevTime;
+ private boolean mInProgress;
+ private int mSpanSlop;
+ private int mMinSpan;
+
+ private final Handler mHandler;
+
+ private float mAnchoredScaleStartX;
+ private float mAnchoredScaleStartY;
+ private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+
+ private static final long TOUCH_STABILIZE_TIME = 128; // ms
+ private static final float SCALE_FACTOR = .5f;
+ private static final int ANCHORED_SCALE_MODE_NONE = 0;
+ private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
+ private static final int ANCHORED_SCALE_MODE_STYLUS = 2;
+
+
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+ private GestureDetector mGestureDetector;
+
+ private boolean mEventBeforeOrAboveStartingGestureEvent;
+
+ /**
+ * Creates a ScaleGestureDetector with the supplied listener.
+ * You may only use this constructor from a {@link android.os.Looper Looper} thread.
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+ this(context, listener, null);
+ }
+
+ /**
+ * Creates a ScaleGestureDetector with the supplied listener.
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use for running deferred listener events.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public ScaleGestureDetector(Context context, OnScaleGestureListener listener,
+ Handler handler) {
+ mContext = context;
+ mListener = listener;
+ mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2;
+
+ final Resources res = context.getResources();
+ mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan);
+ mHandler = handler;
+ // Quick scale is enabled by default after JB_MR2
+ final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ setQuickScaleEnabled(true);
+ }
+ // Stylus scale is enabled by default after LOLLIPOP_MR1
+ if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ setStylusScaleEnabled(true);
+ }
+ }
+
+ /**
+ * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
+ * when appropriate.
+ *
+ * <p>Applications should pass a complete and consistent event stream to this method.
+ * A complete and consistent event stream involves all MotionEvents from the initial
+ * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
+ *
+ * @param event The event to process
+ * @return true if the event was processed and the detector wants to receive the
+ * rest of the MotionEvents in this event stream.
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+ }
+
+ mCurrTime = event.getEventTime();
+
+ final int action = event.getActionMasked();
+
+ // Forward the event to check for double tap gesture
+ if (mQuickScaleEnabled) {
+ mGestureDetector.onTouchEvent(event);
+ }
+
+ final int count = event.getPointerCount();
+ final boolean isStylusButtonDown =
+ (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
+
+ final boolean anchoredScaleCancelled =
+ mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
+ final boolean streamComplete = action == MotionEvent.ACTION_UP ||
+ action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
+
+ if (action == MotionEvent.ACTION_DOWN || streamComplete) {
+ // Reset any scale in progress with the listener.
+ // If it's an ACTION_DOWN we're beginning a new event stream.
+ // This means the app probably didn't give us all the events. Shame on it.
+ if (mInProgress) {
+ mListener.onScaleEnd(this);
+ mInProgress = false;
+ mInitialSpan = 0;
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+ } else if (inAnchoredScaleMode() && streamComplete) {
+ mInProgress = false;
+ mInitialSpan = 0;
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+ }
+
+ if (streamComplete) {
+ return true;
+ }
+ }
+
+ if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()
+ && !streamComplete && isStylusButtonDown) {
+ // Start of a button scale gesture
+ mAnchoredScaleStartX = event.getX();
+ mAnchoredScaleStartY = event.getY();
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
+ mInitialSpan = 0;
+ }
+
+ final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
+ action == MotionEvent.ACTION_POINTER_UP ||
+ action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
+
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int div = pointerUp ? count - 1 : count;
+ final float focusX;
+ final float focusY;
+ if (inAnchoredScaleMode()) {
+ // In anchored scale mode, the focal pt is always where the double tap
+ // or button down gesture started
+ focusX = mAnchoredScaleStartX;
+ focusY = mAnchoredScaleStartY;
+ if (event.getY() < focusY) {
+ mEventBeforeOrAboveStartingGestureEvent = true;
+ } else {
+ mEventBeforeOrAboveStartingGestureEvent = false;
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
+ }
+
+ focusX = sumX / div;
+ focusY = sumY / div;
+ }
+
+ // Determine average deviation from focal point
+ float devSumX = 0, devSumY = 0;
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+
+ // Convert the resulting diameter into a radius.
+ devSumX += Math.abs(event.getX(i) - focusX);
+ devSumY += Math.abs(event.getY(i) - focusY);
+ }
+ final float devX = devSumX / div;
+ final float devY = devSumY / div;
+
+ // Span is the average distance between touch points through the focal point;
+ // i.e. the diameter of the circle with a radius of the average deviation from
+ // the focal point.
+ final float spanX = devX * 2;
+ final float spanY = devY * 2;
+ final float span;
+ if (inAnchoredScaleMode()) {
+ span = spanY;
+ } else {
+ span = (float) Math.hypot(spanX, spanY);
+ }
+
+ // Dispatch begin/end events as needed.
+ // If the configuration changes, notify the app to reset its current state by beginning
+ // a fresh scale event stream.
+ final boolean wasInProgress = mInProgress;
+ mFocusX = focusX;
+ mFocusY = focusY;
+ if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
+ mListener.onScaleEnd(this);
+ mInProgress = false;
+ mInitialSpan = span;
+ }
+ if (configChanged) {
+ mPrevSpanX = mCurrSpanX = spanX;
+ mPrevSpanY = mCurrSpanY = spanY;
+ mInitialSpan = mPrevSpan = mCurrSpan = span;
+ }
+
+ final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
+ if (!mInProgress && span >= minSpan &&
+ (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
+ mPrevSpanX = mCurrSpanX = spanX;
+ mPrevSpanY = mCurrSpanY = spanY;
+ mPrevSpan = mCurrSpan = span;
+ mPrevTime = mCurrTime;
+ mInProgress = mListener.onScaleBegin(this);
+ }
+
+ // Handle motion; focal point and span/scale factor are changing.
+ if (action == MotionEvent.ACTION_MOVE) {
+ mCurrSpanX = spanX;
+ mCurrSpanY = spanY;
+ mCurrSpan = span;
+
+ boolean updatePrev = true;
+
+ if (mInProgress) {
+ updatePrev = mListener.onScale(this);
+ }
+
+ if (updatePrev) {
+ mPrevSpanX = mCurrSpanX;
+ mPrevSpanY = mCurrSpanY;
+ mPrevSpan = mCurrSpan;
+ mPrevTime = mCurrTime;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean inAnchoredScaleMode() {
+ return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
+ }
+
+ /**
+ * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks
+ * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default
+ * if the app targets API 19 and newer.
+ * @param scales true to enable quick scaling, false to disable
+ */
+ public void setQuickScaleEnabled(boolean scales) {
+ mQuickScaleEnabled = scales;
+ if (mQuickScaleEnabled && mGestureDetector == null) {
+ GestureDetector.SimpleOnGestureListener gestureListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ // Double tap: start watching for a swipe
+ mAnchoredScaleStartX = e.getX();
+ mAnchoredScaleStartY = e.getY();
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
+ return true;
+ }
+ };
+ mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
+ }
+ }
+
+ /**
+ * Return whether the quick scale gesture, in which the user performs a double tap followed by a
+ * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}.
+ */
+ public boolean isQuickScaleEnabled() {
+ return mQuickScaleEnabled;
+ }
+
+ /**
+ * Sets whether the associates {@link OnScaleGestureListener} should receive
+ * onScale callbacks when the user uses a stylus and presses the button.
+ * Note that this is enabled by default if the app targets API 23 and newer.
+ *
+ * @param scales true to enable stylus scaling, false to disable.
+ */
+ public void setStylusScaleEnabled(boolean scales) {
+ mStylusScaleEnabled = scales;
+ }
+
+ /**
+ * Return whether the stylus scale gesture, in which the user uses a stylus and presses the
+ * button, should perform scaling. {@see #setStylusScaleEnabled(boolean)}
+ */
+ public boolean isStylusScaleEnabled() {
+ return mStylusScaleEnabled;
+ }
+
+ /**
+ * Returns {@code true} if a scale gesture is in progress.
+ */
+ public boolean isInProgress() {
+ return mInProgress;
+ }
+
+ /**
+ * Get the X coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
+ * If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return X coordinate of the focal point in pixels.
+ */
+ public float getFocusX() {
+ return mFocusX;
+ }
+
+ /**
+ * Get the Y coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
+ * If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return Y coordinate of the focal point in pixels.
+ */
+ public float getFocusY() {
+ return mFocusY;
+ }
+
+ /**
+ * Return the average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ return mCurrSpan;
+ }
+
+ /**
+ * Return the average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpanX() {
+ return mCurrSpanX;
+ }
+
+ /**
+ * Return the average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpanY() {
+ return mCurrSpanY;
+ }
+
+ /**
+ * Return the previous average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ return mPrevSpan;
+ }
+
+ /**
+ * Return the previous average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpanX() {
+ return mPrevSpanX;
+ }
+
+ /**
+ * Return the previous average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpanY() {
+ return mPrevSpanY;
+ }
+
+ /**
+ * Return the scaling factor from the previous scale event to the current
+ * event. This value is defined as
+ * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
+ *
+ * @return The current scaling factor.
+ */
+ public float getScaleFactor() {
+ if (inAnchoredScaleMode()) {
+ // Drag is moving up; the further away from the gesture
+ // start, the smaller the span should be, the closer,
+ // the larger the span, and therefore the larger the scale
+ final boolean scaleUp =
+ (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||
+ (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));
+ final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
+ return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
+ }
+ return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous
+ * accepted scaling event and the current scaling event.
+ *
+ * @return Time difference since the last scaling event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mCurrTime - mPrevTime;
+ }
+
+ /**
+ * Return the event time of the current event being processed.
+ *
+ * @return Current event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrTime;
+ }
+} \ No newline at end of file
diff --git a/android/view/SearchEvent.java b/android/view/SearchEvent.java
new file mode 100644
index 00000000..72b5e4ba
--- /dev/null
+++ b/android/view/SearchEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Class that contains information about an event that triggers a search.
+ */
+public class SearchEvent {
+
+ private InputDevice mInputDevice;
+
+ /** Create a new search event. */
+ public SearchEvent(InputDevice inputDevice) {
+ mInputDevice = inputDevice;
+ }
+
+ /**
+ * Returns the {@link InputDevice} that triggered the search.
+ * @return InputDevice the InputDevice that triggered the search.
+ */
+ public InputDevice getInputDevice() {
+ return mInputDevice;
+ }
+}
diff --git a/android/view/ShadowPainter.java b/android/view/ShadowPainter.java
new file mode 100644
index 00000000..f09fffd1
--- /dev/null
+++ b/android/view/ShadowPainter.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2014 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 java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+
+public class ShadowPainter {
+
+ /**
+ * Adds a drop shadow to a semi-transparent image (of an arbitrary shape) and returns it as a
+ * new image. This method attempts to mimic the same visual characteristics as the rectangular
+ * shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)}
+ * and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}.
+ * <p/>
+ * If shadowSize is less or equals to 1, no shadow will be painted and the source image will be
+ * returned instead.
+ *
+ * @param source the source image
+ * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
+ * #SMALL_SHADOW_SIZE}}
+ *
+ * @return an image with the shadow painted in or the source image if shadowSize <= 1
+ */
+ @NonNull
+ public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+ shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
+
+ return createDropShadow(source, shadowSize, 0.7f, 0);
+ }
+
+ /**
+ * Creates a drop shadow of a given image and returns a new image which shows the input image on
+ * top of its drop shadow.
+ * <p/>
+ * <b>NOTE: If the shape is rectangular and opaque, consider using {@link
+ * #drawRectangleShadow(Graphics2D, int, int, int, int)} instead.</b>
+ *
+ * @param source the source image to be shadowed
+ * @param shadowSize the size of the shadow in pixels
+ * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque
+ * @param shadowRgb the RGB int to use for the shadow color
+ *
+ * @return a new image with the source image on top of its shadow when shadowSize > 0 or the
+ * source image otherwise
+ */
+ @SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"}) // Imported code
+ public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
+ float shadowOpacity, int shadowRgb) {
+ if (shadowSize <= 0) {
+ return source;
+ }
+
+ // This code is based on
+ // http://www.jroller.com/gfx/entry/non_rectangular_shadow
+
+ BufferedImage image;
+ int width = source.getWidth();
+ int height = source.getHeight();
+ image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2 = image.createGraphics();
+ g2.drawImage(image, shadowSize, shadowSize, null);
+
+ int dstWidth = image.getWidth();
+ int dstHeight = image.getHeight();
+
+ int left = (shadowSize - 1) >> 1;
+ int right = shadowSize - left;
+ int xStart = left;
+ int xStop = dstWidth - right;
+ int yStart = left;
+ int yStop = dstHeight - right;
+
+ shadowRgb &= 0x00FFFFFF;
+
+ int[] aHistory = new int[shadowSize];
+ int historyIdx;
+
+ int aSum;
+
+ int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+ int lastPixelOffset = right * dstWidth;
+ float sumDivider = shadowOpacity / shadowSize;
+
+ // horizontal pass
+ for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
+ aSum = 0;
+ historyIdx = 0;
+ for (int x = 0; x < shadowSize; x++, bufferOffset++) {
+ int a = dataBuffer[bufferOffset] >>> 24;
+ aHistory[x] = a;
+ aSum += a;
+ }
+
+ bufferOffset -= right;
+
+ for (int x = xStart; x < xStop; x++, bufferOffset++) {
+ int a = (int) (aSum * sumDivider);
+ dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+ // subtract the oldest pixel from the sum
+ aSum -= aHistory[historyIdx];
+
+ // get the latest pixel
+ a = dataBuffer[bufferOffset + right] >>> 24;
+ aHistory[historyIdx] = a;
+ aSum += a;
+
+ if (++historyIdx >= shadowSize) {
+ historyIdx -= shadowSize;
+ }
+ }
+ }
+ // vertical pass
+ for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
+ aSum = 0;
+ historyIdx = 0;
+ for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) {
+ int a = dataBuffer[bufferOffset] >>> 24;
+ aHistory[y] = a;
+ aSum += a;
+ }
+
+ bufferOffset -= lastPixelOffset;
+
+ for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
+ int a = (int) (aSum * sumDivider);
+ dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+ // subtract the oldest pixel from the sum
+ aSum -= aHistory[historyIdx];
+
+ // get the latest pixel
+ a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
+ aHistory[historyIdx] = a;
+ aSum += a;
+
+ if (++historyIdx >= shadowSize) {
+ historyIdx -= shadowSize;
+ }
+ }
+ }
+
+ g2.drawImage(source, null, 0, 0);
+ g2.dispose();
+
+ return image;
+ }
+
+ /**
+ * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by {@link #SHADOW_SIZE} around
+ * the given source and returns a new image with both combined
+ *
+ * @param source the source image
+ *
+ * @return the source image with a drop shadow on the bottom and right
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public static BufferedImage createRectangularDropShadow(BufferedImage source) {
+ int type = source.getType();
+ if (type == BufferedImage.TYPE_CUSTOM) {
+ type = BufferedImage.TYPE_INT_ARGB;
+ }
+
+ int width = source.getWidth();
+ int height = source.getHeight();
+ BufferedImage image;
+ image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type);
+ Graphics2D g = image.createGraphics();
+ g.drawImage(source, 0, 0, null);
+ drawRectangleShadow(image, 0, 0, width, height);
+ g.dispose();
+
+ return image;
+ }
+
+ /**
+ * Draws a small rectangular drop shadow (of size {@link #SMALL_SHADOW_SIZE} by {@link
+ * #SMALL_SHADOW_SIZE} around the given source and returns a new image with both combined
+ *
+ * @param source the source image
+ *
+ * @return the source image with a drop shadow on the bottom and right
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public static BufferedImage createSmallRectangularDropShadow(BufferedImage source) {
+ int type = source.getType();
+ if (type == BufferedImage.TYPE_CUSTOM) {
+ type = BufferedImage.TYPE_INT_ARGB;
+ }
+
+ int width = source.getWidth();
+ int height = source.getHeight();
+
+ BufferedImage image;
+ image = new BufferedImage(width + SMALL_SHADOW_SIZE, height + SMALL_SHADOW_SIZE, type);
+
+ Graphics2D g = image.createGraphics();
+ g.drawImage(source, 0, 0, null);
+ drawSmallRectangleShadow(image, 0, 0, width, height);
+ g.dispose();
+
+ return image;
+ }
+
+ /**
+ * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+ * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+ * graphics. The size of the shadow is {@link #SHADOW_SIZE}.
+ *
+ * @param image the image to draw the shadow into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawRectangleShadow(BufferedImage image,
+ int x, int y, int width, int height) {
+ Graphics2D gc = image.createGraphics();
+ try {
+ drawRectangleShadow(gc, x, y, width, height);
+ } finally {
+ gc.dispose();
+ }
+ }
+
+ /**
+ * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+ * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+ * shadow graphics. The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
+ *
+ * @param image the image to draw the shadow into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawSmallRectangleShadow(BufferedImage image,
+ int x, int y, int width, int height) {
+ Graphics2D gc = image.createGraphics();
+ try {
+ drawSmallRectangleShadow(gc, x, y, width, height);
+ } finally {
+ gc.dispose();
+ }
+ }
+
+ /**
+ * The width and height of the drop shadow painted by
+ * {@link #drawRectangleShadow(Graphics2D, int, int, int, int)}
+ */
+ public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
+
+ /**
+ * The width and height of the drop shadow painted by
+ * {@link #drawSmallRectangleShadow(Graphics2D, int, int, int, int)}
+ */
+ public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
+
+ /**
+ * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+ * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+ * graphics.
+ *
+ * @param gc the graphics context to draw into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawRectangleShadow(Graphics2D gc, int x, int y, int width, int height) {
+ assert ShadowBottomLeft != null;
+ assert ShadowBottomRight.getWidth(null) == SHADOW_SIZE;
+ assert ShadowBottomRight.getHeight(null) == SHADOW_SIZE;
+
+ int blWidth = ShadowBottomLeft.getWidth(null);
+ int trHeight = ShadowTopRight.getHeight(null);
+ if (width < blWidth) {
+ return;
+ }
+ if (height < trHeight) {
+ return;
+ }
+
+ gc.drawImage(ShadowBottomLeft, x - ShadowBottomLeft.getWidth(null), y + height, null);
+ gc.drawImage(ShadowBottomRight, x + width, y + height, null);
+ gc.drawImage(ShadowTopRight, x + width, y, null);
+ gc.drawImage(ShadowTopLeft, x - ShadowTopLeft.getWidth(null), y, null);
+ gc.drawImage(ShadowBottom,
+ x, y + height, x + width, y + height + ShadowBottom.getHeight(null),
+ 0, 0, ShadowBottom.getWidth(null), ShadowBottom.getHeight(null), null);
+ gc.drawImage(ShadowRight,
+ x + width, y + ShadowTopRight.getHeight(null), x + width + ShadowRight.getWidth(null), y + height,
+ 0, 0, ShadowRight.getWidth(null), ShadowRight.getHeight(null), null);
+ gc.drawImage(ShadowLeft,
+ x - ShadowLeft.getWidth(null), y + ShadowTopLeft.getHeight(null), x, y + height,
+ 0, 0, ShadowLeft.getWidth(null), ShadowLeft.getHeight(null), null);
+ }
+
+ /**
+ * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+ * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+ * shadow graphics.
+ * <p/>
+ *
+ * @param gc the graphics context to draw into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static void drawSmallRectangleShadow(Graphics2D gc, int x, int y, int width,
+ int height) {
+ assert Shadow2BottomLeft != null;
+ assert Shadow2TopRight != null;
+ assert Shadow2BottomRight.getWidth(null) == SMALL_SHADOW_SIZE;
+ assert Shadow2BottomRight.getHeight(null) == SMALL_SHADOW_SIZE;
+
+ int blWidth = Shadow2BottomLeft.getWidth(null);
+ int trHeight = Shadow2TopRight.getHeight(null);
+ if (width < blWidth) {
+ return;
+ }
+ if (height < trHeight) {
+ return;
+ }
+
+ gc.drawImage(Shadow2BottomLeft, x - Shadow2BottomLeft.getWidth(null), y + height, null);
+ gc.drawImage(Shadow2BottomRight, x + width, y + height, null);
+ gc.drawImage(Shadow2TopRight, x + width, y, null);
+ gc.drawImage(Shadow2TopLeft, x - Shadow2TopLeft.getWidth(null), y, null);
+ gc.drawImage(Shadow2Bottom,
+ x, y + height, x + width, y + height + Shadow2Bottom.getHeight(null),
+ 0, 0, Shadow2Bottom.getWidth(null), Shadow2Bottom.getHeight(null), null);
+ gc.drawImage(Shadow2Right,
+ x + width, y + Shadow2TopRight.getHeight(null), x + width + Shadow2Right.getWidth(null), y + height,
+ 0, 0, Shadow2Right.getWidth(null), Shadow2Right.getHeight(null), null);
+ gc.drawImage(Shadow2Left,
+ x - Shadow2Left.getWidth(null), y + Shadow2TopLeft.getHeight(null), x, y + height,
+ 0, 0, Shadow2Left.getWidth(null), Shadow2Left.getHeight(null), null);
+ }
+
+ private static Image loadIcon(String name) {
+ InputStream inputStream = ShadowPainter.class.getResourceAsStream(name);
+ if (inputStream == null) {
+ throw new RuntimeException("Unable to load image for shadow: " + name);
+ }
+ try {
+ return ImageIO.read(inputStream);
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to load image for shadow:" + name, e);
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ }
+
+ // Shadow graphics. This was generated by creating a drop shadow in
+ // Gimp, using the parameters x offset=10, y offset=10, blur radius=10,
+ // (for the small drop shadows x offset=10, y offset=10, blur radius=10)
+ // color=black, and opacity=51. These values attempt to make a shadow
+ // that is legible both for dark and light themes, on top of the
+ // canvas background (rgb(150,150,150). Darker shadows would tend to
+ // blend into the foreground for a dark holo screen, and lighter shadows
+ // would be hard to spot on the canvas background. If you make adjustments,
+ // make sure to check the shadow with both dark and light themes.
+ //
+ // After making the graphics, I cut out the top right, bottom left
+ // and bottom right corners as 20x20 images, and these are reproduced by
+ // painting them in the corresponding places in the target graphics context.
+ // I then grabbed a single horizontal gradient line from the middle of the
+ // right edge,and a single vertical gradient line from the bottom. These
+ // are then painted scaled/stretched in the target to fill the gaps between
+ // the three corner images.
+ //
+ // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
+
+ // Normal Drop Shadow
+ private static final Image ShadowBottom = loadIcon("/icons/shadow-b.png");
+ private static final Image ShadowBottomLeft = loadIcon("/icons/shadow-bl.png");
+ private static final Image ShadowBottomRight = loadIcon("/icons/shadow-br.png");
+ private static final Image ShadowRight = loadIcon("/icons/shadow-r.png");
+ private static final Image ShadowTopRight = loadIcon("/icons/shadow-tr.png");
+ private static final Image ShadowTopLeft = loadIcon("/icons/shadow-tl.png");
+ private static final Image ShadowLeft = loadIcon("/icons/shadow-l.png");
+
+ // Small Drop Shadow
+ private static final Image Shadow2Bottom = loadIcon("/icons/shadow2-b.png");
+ private static final Image Shadow2BottomLeft = loadIcon("/icons/shadow2-bl.png");
+ private static final Image Shadow2BottomRight = loadIcon("/icons/shadow2-br.png");
+ private static final Image Shadow2Right = loadIcon("/icons/shadow2-r.png");
+ private static final Image Shadow2TopRight = loadIcon("/icons/shadow2-tr.png");
+ private static final Image Shadow2TopLeft = loadIcon("/icons/shadow2-tl.png");
+ private static final Image Shadow2Left = loadIcon("/icons/shadow2-l.png");
+}
diff --git a/android/view/SoundEffectConstants.java b/android/view/SoundEffectConstants.java
new file mode 100644
index 00000000..8d891bbc
--- /dev/null
+++ b/android/view/SoundEffectConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008 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;
+
+/**
+ * Constants to be used to play sound effects via {@link View#playSoundEffect(int)}
+ */
+public class SoundEffectConstants {
+
+ private SoundEffectConstants() {}
+
+ public static final int CLICK = 0;
+
+ public static final int NAVIGATION_LEFT = 1;
+ public static final int NAVIGATION_UP = 2;
+ public static final int NAVIGATION_RIGHT = 3;
+ public static final int NAVIGATION_DOWN = 4;
+
+ /**
+ * Get the sonification constant for the focus directions.
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}
+ * or {@link View#FOCUS_BACKWARD}
+
+ * @return The appropriate sonification constant.
+ * @throws {@link IllegalArgumentException} when the passed direction is not one of the
+ * documented values.
+ */
+ public static int getContantForFocusDirection(int direction) {
+ switch (direction) {
+ case View.FOCUS_RIGHT:
+ return SoundEffectConstants.NAVIGATION_RIGHT;
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_DOWN:
+ return SoundEffectConstants.NAVIGATION_DOWN;
+ case View.FOCUS_LEFT:
+ return SoundEffectConstants.NAVIGATION_LEFT;
+ case View.FOCUS_BACKWARD:
+ case View.FOCUS_UP:
+ return SoundEffectConstants.NAVIGATION_UP;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}.");
+ }
+}
diff --git a/android/view/SubMenu.java b/android/view/SubMenu.java
new file mode 100644
index 00000000..38662b09
--- /dev/null
+++ b/android/view/SubMenu.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 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.DrawableRes;
+import android.annotation.StringRes;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Subclass of {@link Menu} for sub menus.
+ * <p>
+ * Sub menus do not support item icons, or nested sub menus.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about creating menus, read the
+ * <a href="{@docRoot}guide/topics/ui/menus.html">Menus</a> developer guide.</p>
+ * </div>
+ */
+
+public interface SubMenu extends Menu {
+ /**
+ * Sets the submenu header's title to the title given in <var>titleRes</var>
+ * resource identifier.
+ *
+ * @param titleRes The string resource identifier used for the title.
+ * @return This SubMenu so additional setters can be called.
+ */
+ public SubMenu setHeaderTitle(@StringRes int titleRes);
+
+ /**
+ * Sets the submenu header's title to the title given in <var>title</var>.
+ *
+ * @param title The character sequence used for the title.
+ * @return This SubMenu so additional setters can be called.
+ */
+ public SubMenu setHeaderTitle(CharSequence title);
+
+ /**
+ * Sets the submenu header's icon to the icon given in <var>iconRes</var>
+ * resource id.
+ *
+ * @param iconRes The resource identifier used for the icon.
+ * @return This SubMenu so additional setters can be called.
+ */
+ public SubMenu setHeaderIcon(@DrawableRes int iconRes);
+
+ /**
+ * Sets the submenu header's icon to the icon given in <var>icon</var>
+ * {@link Drawable}.
+ *
+ * @param icon The {@link Drawable} used for the icon.
+ * @return This SubMenu so additional setters can be called.
+ */
+ public SubMenu setHeaderIcon(Drawable icon);
+
+ /**
+ * Sets the header of the submenu to the {@link View} given in
+ * <var>view</var>. This replaces the header title and icon (and those
+ * replace this).
+ *
+ * @param view The {@link View} used for the header.
+ * @return This SubMenu so additional setters can be called.
+ */
+ public SubMenu setHeaderView(View view);
+
+ /**
+ * Clears the header of the submenu.
+ */
+ public void clearHeader();
+
+ /**
+ * Change the icon associated with this submenu's item in its parent menu.
+ *
+ * @see MenuItem#setIcon(int)
+ * @param iconRes The new icon (as a resource ID) to be displayed.
+ * @return This SubMenu so additional setters can be called.
+ */
+ public SubMenu setIcon(@DrawableRes int iconRes);
+
+ /**
+ * Change the icon associated with this submenu's item in its parent menu.
+ *
+ * @see MenuItem#setIcon(Drawable)
+ * @param icon The new icon (as a Drawable) to be displayed.
+ * @return This SubMenu so additional setters can be called.
+ */
+ public SubMenu setIcon(Drawable icon);
+
+ /**
+ * Gets the {@link MenuItem} that represents this submenu in the parent
+ * menu. Use this for setting additional item attributes.
+ *
+ * @return The {@link MenuItem} that launches the submenu when invoked.
+ */
+ public MenuItem getItem();
+}
diff --git a/android/view/Surface.java b/android/view/Surface.java
new file mode 100644
index 00000000..2c1f7346
--- /dev/null
+++ b/android/view/Surface.java
@@ -0,0 +1,858 @@
+/*
+ * Copyright (C) 2007 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.IntDef;
+import android.content.res.CompatibilityInfo.Translator;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Handle onto a raw buffer that is being managed by the screen compositor.
+ *
+ * <p>A Surface is generally created by or from a consumer of image buffers (such as a
+ * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or
+ * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as
+ * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},
+ * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw
+ * into.</p>
+ *
+ * <p><strong>Note:</strong> A Surface acts like a
+ * {@link java.lang.ref.WeakReference weak reference} to the consumer it is associated with. By
+ * itself it will not keep its parent consumer from being reclaimed.</p>
+ */
+public class Surface implements Parcelable {
+ private static final String TAG = "Surface";
+
+ private static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
+ throws OutOfResourcesException;
+
+ private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);
+ private static native long nativeGetFromSurfaceControl(long surfaceControlNativeObject);
+
+ private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
+ throws OutOfResourcesException;
+ private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
+
+ private static native void nativeRelease(long nativeObject);
+ private static native boolean nativeIsValid(long nativeObject);
+ private static native boolean nativeIsConsumerRunningBehind(long nativeObject);
+ private static native long nativeReadFromParcel(long nativeObject, Parcel source);
+ private static native void nativeWriteToParcel(long nativeObject, Parcel dest);
+
+ private static native void nativeAllocateBuffers(long nativeObject);
+
+ private static native int nativeGetWidth(long nativeObject);
+ private static native int nativeGetHeight(long nativeObject);
+
+ private static native long nativeGetNextFrameNumber(long nativeObject);
+ private static native int nativeSetScalingMode(long nativeObject, int scalingMode);
+ private static native int nativeForceScopedDisconnect(long nativeObject);
+ private static native int nativeAttachAndQueueBuffer(long nativeObject, GraphicBuffer buffer);
+
+ private static native int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled);
+ private static native int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled);
+
+ public static final Parcelable.Creator<Surface> CREATOR =
+ new Parcelable.Creator<Surface>() {
+ @Override
+ public Surface createFromParcel(Parcel source) {
+ try {
+ Surface s = new Surface();
+ s.readFromParcel(source);
+ return s;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating surface from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public Surface[] newArray(int size) {
+ return new Surface[size];
+ }
+ };
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ // Guarded state.
+ final Object mLock = new Object(); // protects the native state
+ private String mName;
+ long mNativeObject; // package scope only for SurfaceControl access
+ private long mLockedObject;
+ private int mGenerationId; // incremented each time mNativeObject changes
+ private final Canvas mCanvas = new CompatibleCanvas();
+
+ // A matrix to scale the matrix set by application. This is set to null for
+ // non compatibility mode.
+ private Matrix mCompatibleMatrix;
+
+ private HwuiContext mHwuiContext;
+
+ private boolean mIsSingleBuffered;
+ private boolean mIsSharedBufferModeEnabled;
+ private boolean mIsAutoRefreshEnabled;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SCALING_MODE_FREEZE, SCALING_MODE_SCALE_TO_WINDOW,
+ SCALING_MODE_SCALE_CROP, SCALING_MODE_NO_SCALE_CROP})
+ public @interface ScalingMode {}
+ // From system/window.h
+ /** @hide */
+ public static final int SCALING_MODE_FREEZE = 0;
+ /** @hide */
+ public static final int SCALING_MODE_SCALE_TO_WINDOW = 1;
+ /** @hide */
+ public static final int SCALING_MODE_SCALE_CROP = 2;
+ /** @hide */
+ public static final int SCALING_MODE_NO_SCALE_CROP = 3;
+
+ /** @hide */
+ @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Rotation {}
+
+ /**
+ * Rotation constant: 0 degree rotation (natural orientation)
+ */
+ public static final int ROTATION_0 = 0;
+
+ /**
+ * Rotation constant: 90 degree rotation.
+ */
+ public static final int ROTATION_90 = 1;
+
+ /**
+ * Rotation constant: 180 degree rotation.
+ */
+ public static final int ROTATION_180 = 2;
+
+ /**
+ * Rotation constant: 270 degree rotation.
+ */
+ public static final int ROTATION_270 = 3;
+
+ /**
+ * Create an empty surface, which will later be filled in by readFromParcel().
+ * @hide
+ */
+ public Surface() {
+ }
+
+ /**
+ * Create Surface from a {@link SurfaceTexture}.
+ *
+ * Images drawn to the Surface will be made available to the {@link
+ * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link
+ * SurfaceTexture#updateTexImage}.
+ *
+ * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
+ * Surface.
+ * @throws OutOfResourcesException if the surface could not be created.
+ */
+ public Surface(SurfaceTexture surfaceTexture) {
+ if (surfaceTexture == null) {
+ throw new IllegalArgumentException("surfaceTexture must not be null");
+ }
+ mIsSingleBuffered = surfaceTexture.isSingleBuffered();
+ synchronized (mLock) {
+ mName = surfaceTexture.toString();
+ setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
+ }
+ }
+
+ /* called from android_view_Surface_createFromIGraphicBufferProducer() */
+ private Surface(long nativeObject) {
+ synchronized (mLock) {
+ setNativeObjectLocked(nativeObject);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ release();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Release the local reference to the server-side surface.
+ * Always call release() when you're done with a Surface.
+ * This will make the surface invalid.
+ */
+ public void release() {
+ synchronized (mLock) {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ setNativeObjectLocked(0);
+ }
+ if (mHwuiContext != null) {
+ mHwuiContext.destroy();
+ mHwuiContext = null;
+ }
+ }
+ }
+
+ /**
+ * Free all server-side state associated with this surface and
+ * release this object's reference. This method can only be
+ * called from the process that created the service.
+ * @hide
+ */
+ public void destroy() {
+ release();
+ }
+
+ /**
+ * Returns true if this object holds a valid surface.
+ *
+ * @return True if it holds a physical surface, so lockCanvas() will succeed.
+ * Otherwise returns false.
+ */
+ public boolean isValid() {
+ synchronized (mLock) {
+ if (mNativeObject == 0) return false;
+ return nativeIsValid(mNativeObject);
+ }
+ }
+
+ /**
+ * Gets the generation number of this surface, incremented each time
+ * the native surface contained within this object changes.
+ *
+ * @return The current generation number.
+ * @hide
+ */
+ public int getGenerationId() {
+ synchronized (mLock) {
+ return mGenerationId;
+ }
+ }
+
+ /**
+ * Returns the next frame number which will be dequeued for rendering.
+ * Intended for use with SurfaceFlinger's deferred transactions API.
+ *
+ * @hide
+ */
+ public long getNextFrameNumber() {
+ synchronized (mLock) {
+ return nativeGetNextFrameNumber(mNativeObject);
+ }
+ }
+
+ /**
+ * Returns true if the consumer of this Surface is running behind the producer.
+ *
+ * @return True if the consumer is more than one buffer ahead of the producer.
+ * @hide
+ */
+ public boolean isConsumerRunningBehind() {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ return nativeIsConsumerRunningBehind(mNativeObject);
+ }
+ }
+
+ /**
+ * Gets a {@link Canvas} for drawing into this surface.
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param inOutDirty A rectangle that represents the dirty region that the caller wants
+ * to redraw. This function may choose to expand the dirty rectangle if for example
+ * the surface has been resized or if the previous contents of the surface were
+ * not available. The caller must redraw the entire dirty region as represented
+ * by the contents of the inOutDirty rectangle upon return from this function.
+ * The caller may also pass <code>null</code> instead, in the case where the
+ * entire surface should be redrawn.
+ * @return A canvas for drawing into the surface.
+ *
+ * @throws IllegalArgumentException If the inOutDirty rectangle is not valid.
+ * @throws OutOfResourcesException If the canvas cannot be locked.
+ */
+ public Canvas lockCanvas(Rect inOutDirty)
+ throws Surface.OutOfResourcesException, IllegalArgumentException {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ if (mLockedObject != 0) {
+ // Ideally, nativeLockCanvas() would throw in this situation and prevent the
+ // double-lock, but that won't happen if mNativeObject was updated. We can't
+ // abandon the old mLockedObject because it might still be in use, so instead
+ // we just refuse to re-lock the Surface.
+ throw new IllegalArgumentException("Surface was already locked");
+ }
+ mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
+ return mCanvas;
+ }
+ }
+
+ /**
+ * Posts the new contents of the {@link Canvas} to the surface and
+ * releases the {@link Canvas}.
+ *
+ * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+ */
+ public void unlockCanvasAndPost(Canvas canvas) {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+
+ if (mHwuiContext != null) {
+ mHwuiContext.unlockAndPost(canvas);
+ } else {
+ unlockSwCanvasAndPost(canvas);
+ }
+ }
+ }
+
+ private void unlockSwCanvasAndPost(Canvas canvas) {
+ if (canvas != mCanvas) {
+ throw new IllegalArgumentException("canvas object must be the same instance that "
+ + "was previously returned by lockCanvas");
+ }
+ if (mNativeObject != mLockedObject) {
+ Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
+ Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
+ Long.toHexString(mLockedObject) +")");
+ }
+ if (mLockedObject == 0) {
+ throw new IllegalStateException("Surface was not locked");
+ }
+ try {
+ nativeUnlockCanvasAndPost(mLockedObject, canvas);
+ } finally {
+ nativeRelease(mLockedObject);
+ mLockedObject = 0;
+ }
+ }
+
+ /**
+ * Gets a {@link Canvas} for drawing into this surface.
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * Unlike {@link #lockCanvas(Rect)} this will return a hardware-accelerated
+ * canvas. See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+ * unsupported drawing operations</a> for a list of what is and isn't
+ * supported in a hardware-accelerated canvas. It is also required to
+ * fully cover the surface every time {@link #lockHardwareCanvas()} is
+ * called as the buffer is not preserved between frames. Partial updates
+ * are not supported.
+ *
+ * @return A canvas for drawing into the surface.
+ *
+ * @throws IllegalStateException If the canvas cannot be locked.
+ */
+ public Canvas lockHardwareCanvas() {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ if (mHwuiContext == null) {
+ mHwuiContext = new HwuiContext();
+ }
+ return mHwuiContext.lockCanvas(
+ nativeGetWidth(mNativeObject),
+ nativeGetHeight(mNativeObject));
+ }
+ }
+
+ /**
+ * @deprecated This API has been removed and is not supported. Do not use.
+ */
+ @Deprecated
+ public void unlockCanvas(Canvas canvas) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the translator used to scale canvas's width/height in compatibility
+ * mode.
+ */
+ void setCompatibilityTranslator(Translator translator) {
+ if (translator != null) {
+ float appScale = translator.applicationScale;
+ mCompatibleMatrix = new Matrix();
+ mCompatibleMatrix.setScale(appScale, appScale);
+ }
+ }
+
+ /**
+ * Copy another surface to this one. This surface now holds a reference
+ * to the same data as the original surface, and is -not- the owner.
+ * This is for use by the window manager when returning a window surface
+ * back from a client, converting it from the representation being managed
+ * by the window manager to the representation the client uses to draw
+ * in to it.
+ *
+ * @param other {@link SurfaceControl} to copy from.
+ *
+ * @hide
+ */
+ public void copyFrom(SurfaceControl other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
+ }
+
+ long surfaceControlPtr = other.mNativeObject;
+ if (surfaceControlPtr == 0) {
+ throw new NullPointerException(
+ "null SurfaceControl native object. Are you using a released SurfaceControl?");
+ }
+ long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr);
+
+ synchronized (mLock) {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ setNativeObjectLocked(newNativeObject);
+ }
+ }
+
+ /**
+ * Gets a reference a surface created from this one. This surface now holds a reference
+ * to the same data as the original surface, and is -not- the owner.
+ * This is for use by the window manager when returning a window surface
+ * back from a client, converting it from the representation being managed
+ * by the window manager to the representation the client uses to draw
+ * in to it.
+ *
+ * @param other {@link SurfaceControl} to create surface from.
+ *
+ * @hide
+ */
+ public void createFrom(SurfaceControl other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
+ }
+
+ long surfaceControlPtr = other.mNativeObject;
+ if (surfaceControlPtr == 0) {
+ throw new NullPointerException(
+ "null SurfaceControl native object. Are you using a released SurfaceControl?");
+ }
+ long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
+
+ synchronized (mLock) {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ setNativeObjectLocked(newNativeObject);
+ }
+ }
+
+ /**
+ * This is intended to be used by {@link SurfaceView#updateWindow} only.
+ * @param other access is not thread safe
+ * @hide
+ * @deprecated
+ */
+ @Deprecated
+ public void transferFrom(Surface other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
+ }
+ if (other != this) {
+ final long newPtr;
+ synchronized (other.mLock) {
+ newPtr = other.mNativeObject;
+ other.setNativeObjectLocked(0);
+ }
+
+ synchronized (mLock) {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ setNativeObjectLocked(newPtr);
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel source) {
+ if (source == null) {
+ throw new IllegalArgumentException("source must not be null");
+ }
+
+ synchronized (mLock) {
+ // nativeReadFromParcel() will either return mNativeObject, or
+ // create a new native Surface and return it after reducing
+ // the reference count on mNativeObject. Either way, it is
+ // not necessary to call nativeRelease() here.
+ // NOTE: This must be kept synchronized with the native parceling code
+ // in frameworks/native/libs/Surface.cpp
+ mName = source.readString();
+ mIsSingleBuffered = source.readInt() != 0;
+ setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("dest must not be null");
+ }
+ synchronized (mLock) {
+ // NOTE: This must be kept synchronized with the native parceling code
+ // in frameworks/native/libs/Surface.cpp
+ dest.writeString(mName);
+ dest.writeInt(mIsSingleBuffered ? 1 : 0);
+ nativeWriteToParcel(mNativeObject, dest);
+ }
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ release();
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mLock) {
+ return "Surface(name=" + mName + ")/@0x" +
+ Integer.toHexString(System.identityHashCode(this));
+ }
+ }
+
+ private void setNativeObjectLocked(long ptr) {
+ if (mNativeObject != ptr) {
+ if (mNativeObject == 0 && ptr != 0) {
+ mCloseGuard.open("release");
+ } else if (mNativeObject != 0 && ptr == 0) {
+ mCloseGuard.close();
+ }
+ mNativeObject = ptr;
+ mGenerationId += 1;
+ if (mHwuiContext != null) {
+ mHwuiContext.updateSurface();
+ }
+ }
+ }
+
+ private void checkNotReleasedLocked() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("Surface has already been released.");
+ }
+ }
+
+ /**
+ * Allocate buffers ahead of time to avoid allocation delays during rendering
+ * @hide
+ */
+ public void allocateBuffers() {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ nativeAllocateBuffers(mNativeObject);
+ }
+ }
+
+ /**
+ * Set the scaling mode to be used for this surfaces buffers
+ * @hide
+ */
+ void setScalingMode(@ScalingMode int scalingMode) {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ int err = nativeSetScalingMode(mNativeObject, scalingMode);
+ if (err != 0) {
+ throw new IllegalArgumentException("Invalid scaling mode: " + scalingMode);
+ }
+ }
+ }
+
+ void forceScopedDisconnect() {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ int err = nativeForceScopedDisconnect(mNativeObject);
+ if (err != 0) {
+ throw new RuntimeException("Failed to disconnect Surface instance (bad object?)");
+ }
+ }
+ }
+
+ /**
+ * Transfer ownership of buffer and present it on the Surface.
+ * @hide
+ */
+ public void attachAndQueueBuffer(GraphicBuffer buffer) {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ int err = nativeAttachAndQueueBuffer(mNativeObject, buffer);
+ if (err != 0) {
+ throw new RuntimeException(
+ "Failed to attach and queue buffer to Surface (bad object?)");
+ }
+ }
+ }
+
+ /**
+ * Returns whether or not this Surface is backed by a single-buffered SurfaceTexture
+ * @hide
+ */
+ public boolean isSingleBuffered() {
+ return mIsSingleBuffered;
+ }
+
+ /**
+ * <p>The shared buffer mode allows both the application and the surface compositor
+ * (SurfaceFlinger) to concurrently access this surface's buffer. While the
+ * application is still required to issue a present request
+ * (see {@link #unlockCanvasAndPost(Canvas)}) to the compositor when an update is required,
+ * the compositor may trigger an update at any time. Since the surface's buffer is shared
+ * between the application and the compositor, updates triggered by the compositor may
+ * cause visible tearing.</p>
+ *
+ * <p>The shared buffer mode can be used with
+ * {@link #setAutoRefreshEnabled(boolean) auto-refresh} to avoid the overhead of
+ * issuing present requests.</p>
+ *
+ * <p>If the application uses the shared buffer mode to reduce latency, it is
+ * recommended to use software rendering (see {@link #lockCanvas(Rect)} to ensure
+ * the graphics workloads are not affected by other applications and/or the system
+ * using the GPU. When using software rendering, the application should update the
+ * smallest possible region of the surface required.</p>
+ *
+ * <p class="note">The shared buffer mode might not be supported by the underlying
+ * hardware. Enabling shared buffer mode on hardware that does not support it will
+ * not yield an error but the application will not benefit from lower latency (and
+ * tearing will not be visible).</p>
+ *
+ * <p class="note">Depending on how many and what kind of surfaces are visible, the
+ * surface compositor may need to copy the shared buffer before it is displayed. When
+ * this happens, the latency benefits of shared buffer mode will be reduced.</p>
+ *
+ * @param enabled True to enable the shared buffer mode on this surface, false otherwise
+ *
+ * @see #isSharedBufferModeEnabled()
+ * @see #setAutoRefreshEnabled(boolean)
+ *
+ * @hide
+ */
+ public void setSharedBufferModeEnabled(boolean enabled) {
+ if (mIsSharedBufferModeEnabled != enabled) {
+ int error = nativeSetSharedBufferModeEnabled(mNativeObject, enabled);
+ if (error != 0) {
+ throw new RuntimeException(
+ "Failed to set shared buffer mode on Surface (bad object?)");
+ } else {
+ mIsSharedBufferModeEnabled = enabled;
+ }
+ }
+ }
+
+ /**
+ * @return True if shared buffer mode is enabled on this surface, false otherwise
+ *
+ * @see #setSharedBufferModeEnabled(boolean)
+ *
+ * @hide
+ */
+ public boolean isSharedBufferModeEnabled() {
+ return mIsSharedBufferModeEnabled;
+ }
+
+ /**
+ * <p>When auto-refresh is enabled, the surface compositor (SurfaceFlinger)
+ * automatically updates the display on a regular refresh cycle. The application
+ * can continue to issue present requests but it is not required. Enabling
+ * auto-refresh may result in visible tearing.</p>
+ *
+ * <p>Auto-refresh has no effect if the {@link #setSharedBufferModeEnabled(boolean)
+ * shared buffer mode} is not enabled.</p>
+ *
+ * <p>Because auto-refresh will trigger continuous updates of the display, it is
+ * recommended to turn it on only when necessary. For example, in a drawing/painting
+ * application auto-refresh should be enabled on finger/pen down and disabled on
+ * finger/pen up.</p>
+ *
+ * @param enabled True to enable auto-refresh on this surface, false otherwise
+ *
+ * @see #isAutoRefreshEnabled()
+ * @see #setSharedBufferModeEnabled(boolean)
+ *
+ * @hide
+ */
+ public void setAutoRefreshEnabled(boolean enabled) {
+ if (mIsAutoRefreshEnabled != enabled) {
+ int error = nativeSetAutoRefreshEnabled(mNativeObject, enabled);
+ if (error != 0) {
+ throw new RuntimeException("Failed to set auto refresh on Surface (bad object?)");
+ } else {
+ mIsAutoRefreshEnabled = enabled;
+ }
+ }
+ }
+
+ /**
+ * @return True if auto-refresh is enabled on this surface, false otherwise
+ *
+ * @hide
+ */
+ public boolean isAutoRefreshEnabled() {
+ return mIsAutoRefreshEnabled;
+ }
+
+ /**
+ * Exception thrown when a Canvas couldn't be locked with {@link Surface#lockCanvas}, or
+ * when a SurfaceTexture could not successfully be allocated.
+ */
+ @SuppressWarnings("serial")
+ public static class OutOfResourcesException extends RuntimeException {
+ public OutOfResourcesException() {
+ }
+ public OutOfResourcesException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns a human readable representation of a rotation.
+ *
+ * @param rotation The rotation.
+ * @return The rotation symbolic name.
+ *
+ * @hide
+ */
+ public static String rotationToString(int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0: {
+ return "ROTATION_0";
+ }
+ case Surface.ROTATION_90: {
+ return "ROTATION_90";
+ }
+ case Surface.ROTATION_180: {
+ return "ROTATION_180";
+ }
+ case Surface.ROTATION_270: {
+ return "ROTATION_270";
+ }
+ default: {
+ throw new IllegalArgumentException("Invalid rotation: " + rotation);
+ }
+ }
+ }
+
+ /**
+ * A Canvas class that can handle the compatibility mode.
+ * This does two things differently.
+ * <ul>
+ * <li>Returns the width and height of the target metrics, rather than
+ * native. For example, the canvas returns 320x480 even if an app is running
+ * in WVGA high density.
+ * <li>Scales the matrix in setMatrix by the application scale, except if
+ * the matrix looks like obtained from getMatrix. This is a hack to handle
+ * the case that an application uses getMatrix to keep the original matrix,
+ * set matrix of its own, then set the original matrix back. There is no
+ * perfect solution that works for all cases, and there are a lot of cases
+ * that this model does not work, but we hope this works for many apps.
+ * </ul>
+ */
+ private final class CompatibleCanvas extends Canvas {
+ // A temp matrix to remember what an application obtained via {@link getMatrix}
+ private Matrix mOrigMatrix = null;
+
+ @Override
+ public void setMatrix(Matrix matrix) {
+ if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
+ // don't scale the matrix if it's not compatibility mode, or
+ // the matrix was obtained from getMatrix.
+ super.setMatrix(matrix);
+ } else {
+ Matrix m = new Matrix(mCompatibleMatrix);
+ m.preConcat(matrix);
+ super.setMatrix(m);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void getMatrix(Matrix m) {
+ super.getMatrix(m);
+ if (mOrigMatrix == null) {
+ mOrigMatrix = new Matrix();
+ }
+ mOrigMatrix.set(m);
+ }
+ }
+
+ private final class HwuiContext {
+ private final RenderNode mRenderNode;
+ private long mHwuiRenderer;
+ private DisplayListCanvas mCanvas;
+
+ HwuiContext() {
+ mRenderNode = RenderNode.create("HwuiCanvas", null);
+ mRenderNode.setClipToBounds(false);
+ mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject);
+ }
+
+ Canvas lockCanvas(int width, int height) {
+ if (mCanvas != null) {
+ throw new IllegalStateException("Surface was already locked!");
+ }
+ mCanvas = mRenderNode.start(width, height);
+ return mCanvas;
+ }
+
+ void unlockAndPost(Canvas canvas) {
+ if (canvas != mCanvas) {
+ throw new IllegalArgumentException("canvas object must be the same instance that "
+ + "was previously returned by lockCanvas");
+ }
+ mRenderNode.end(mCanvas);
+ mCanvas = null;
+ nHwuiDraw(mHwuiRenderer);
+ }
+
+ void updateSurface() {
+ nHwuiSetSurface(mHwuiRenderer, mNativeObject);
+ }
+
+ void destroy() {
+ if (mHwuiRenderer != 0) {
+ nHwuiDestroy(mHwuiRenderer);
+ mHwuiRenderer = 0;
+ }
+ }
+ }
+
+ private static native long nHwuiCreate(long rootNode, long surface);
+ private static native void nHwuiSetSurface(long renderer, long surface);
+ private static native void nHwuiDraw(long renderer);
+ private static native void nHwuiDestroy(long renderer);
+}
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
new file mode 100644
index 00000000..91932dd4
--- /dev/null
+++ b/android/view/SurfaceControl.java
@@ -0,0 +1,938 @@
+/*
+ * 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.view;
+
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Surface.OutOfResourcesException;
+
+import dalvik.system.CloseGuard;
+
+/**
+ * SurfaceControl
+ * @hide
+ */
+public class SurfaceControl {
+ private static final String TAG = "SurfaceControl";
+
+ private static native long nativeCreate(SurfaceSession session, String name,
+ int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid)
+ throws OutOfResourcesException;
+ private static native void nativeRelease(long nativeObject);
+ private static native void nativeDestroy(long nativeObject);
+ private static native void nativeDisconnect(long nativeObject);
+
+ private static native Bitmap nativeScreenshot(IBinder displayToken,
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform, int rotation);
+ private static native GraphicBuffer nativeScreenshotToBuffer(IBinder displayToken,
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform, int rotation);
+ private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform);
+
+ private static native void nativeOpenTransaction();
+ private static native void nativeCloseTransaction(boolean sync);
+ private static native void nativeSetAnimationTransaction();
+
+ private static native void nativeSetLayer(long nativeObject, int zorder);
+ private static native void nativeSetRelativeLayer(long nativeObject, IBinder relativeTo,
+ int zorder);
+ private static native void nativeSetPosition(long nativeObject, float x, float y);
+ private static native void nativeSetGeometryAppliesWithResize(long nativeObject);
+ private static native void nativeSetSize(long nativeObject, int w, int h);
+ private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
+ private static native void nativeSetAlpha(long nativeObject, float alpha);
+ private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx,
+ float dtdy, float dsdy);
+ private static native void nativeSetFlags(long nativeObject, int flags, int mask);
+ private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
+ private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b);
+ private static native void nativeSetLayerStack(long nativeObject, int layerStack);
+
+ private static native boolean nativeClearContentFrameStats(long nativeObject);
+ private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
+ private static native boolean nativeClearAnimationFrameStats();
+ private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
+
+ private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
+ private static native IBinder nativeCreateDisplay(String name, boolean secure);
+ private static native void nativeDestroyDisplay(IBinder displayToken);
+ private static native void nativeSetDisplaySurface(
+ IBinder displayToken, long nativeSurfaceObject);
+ private static native void nativeSetDisplayLayerStack(
+ IBinder displayToken, int layerStack);
+ private static native void nativeSetDisplayProjection(
+ IBinder displayToken, int orientation,
+ int l, int t, int r, int b,
+ int L, int T, int R, int B);
+ private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height);
+ private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
+ IBinder displayToken);
+ private static native int nativeGetActiveConfig(IBinder displayToken);
+ private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
+ private static native int[] nativeGetDisplayColorModes(IBinder displayToken);
+ private static native int nativeGetActiveColorMode(IBinder displayToken);
+ private static native boolean nativeSetActiveColorMode(IBinder displayToken,
+ int colorMode);
+ private static native void nativeSetDisplayPowerMode(
+ IBinder displayToken, int mode);
+ private static native void nativeDeferTransactionUntil(long nativeObject,
+ IBinder handle, long frame);
+ private static native void nativeDeferTransactionUntilSurface(long nativeObject,
+ long surfaceObject, long frame);
+ private static native void nativeReparentChildren(long nativeObject,
+ IBinder handle);
+ private static native void nativeReparentChild(long nativeObject,
+ IBinder parentHandle, IBinder childHandle);
+ private static native void nativeSeverChildren(long nativeObject);
+ private static native void nativeSetOverrideScalingMode(long nativeObject,
+ int scalingMode);
+ private static native IBinder nativeGetHandle(long nativeObject);
+ private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
+
+ private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
+
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final String mName;
+ long mNativeObject; // package visibility only for Surface.java access
+
+ /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
+
+ /**
+ * Surface creation flag: Surface is created hidden
+ */
+ public static final int HIDDEN = 0x00000004;
+
+ /**
+ * Surface creation flag: The surface contains secure content, special
+ * measures will be taken to disallow the surface's content to be copied
+ * from another process. In particular, screenshots and VNC servers will
+ * be disabled, but other measures can take place, for instance the
+ * surface might not be hardware accelerated.
+ *
+ */
+ public static final int SECURE = 0x00000080;
+
+ /**
+ * Surface creation flag: Creates a surface where color components are interpreted
+ * as "non pre-multiplied" by their alpha channel. Of course this flag is
+ * meaningless for surfaces without an alpha channel. By default
+ * surfaces are pre-multiplied, which means that each color component is
+ * already multiplied by its alpha value. In this case the blending
+ * equation used is:
+ * <p>
+ * <code>DEST = SRC + DEST * (1-SRC_ALPHA)</code>
+ * <p>
+ * By contrast, non pre-multiplied surfaces use the following equation:
+ * <p>
+ * <code>DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA)</code>
+ * <p>
+ * pre-multiplied surfaces must always be used if transparent pixels are
+ * composited on top of each-other into the surface. A pre-multiplied
+ * surface can never lower the value of the alpha component of a given
+ * pixel.
+ * <p>
+ * In some rare situations, a non pre-multiplied surface is preferable.
+ *
+ */
+ public static final int NON_PREMULTIPLIED = 0x00000100;
+
+ /**
+ * Surface creation flag: Indicates that the surface must be considered opaque,
+ * even if its pixel format is set to translucent. This can be useful if an
+ * application needs full RGBA 8888 support for instance but will
+ * still draw every pixel opaque.
+ * <p>
+ * This flag is ignored if setAlpha() is used to make the surface non-opaque.
+ * Combined effects are (assuming a buffer format with an alpha channel):
+ * <ul>
+ * <li>OPAQUE + alpha(1.0) == opaque composition
+ * <li>OPAQUE + alpha(0.x) == blended composition
+ * <li>!OPAQUE + alpha(1.0) == blended composition
+ * <li>!OPAQUE + alpha(0.x) == blended composition
+ * </ul>
+ * If the underlying buffer lacks an alpha channel, the OPAQUE flag is effectively
+ * set automatically.
+ */
+ public static final int OPAQUE = 0x00000400;
+
+ /**
+ * Surface creation flag: Application requires a hardware-protected path to an
+ * external display sink. If a hardware-protected path is not available,
+ * then this surface will not be displayed on the external sink.
+ *
+ */
+ public static final int PROTECTED_APP = 0x00000800;
+
+ // 0x1000 is reserved for an independent DRM protected flag in framework
+
+ /**
+ * Surface creation flag: Window represents a cursor glyph.
+ */
+ public static final int CURSOR_WINDOW = 0x00002000;
+
+ /**
+ * Surface creation flag: Creates a normal surface.
+ * This is the default.
+ *
+ */
+ public static final int FX_SURFACE_NORMAL = 0x00000000;
+
+ /**
+ * Surface creation flag: Creates a Dim surface.
+ * Everything behind this surface is dimmed by the amount specified
+ * in {@link #setAlpha}. It is an error to lock a Dim surface, since it
+ * doesn't have a backing store.
+ *
+ */
+ public static final int FX_SURFACE_DIM = 0x00020000;
+
+ /**
+ * Mask used for FX values above.
+ *
+ */
+ public static final int FX_SURFACE_MASK = 0x000F0000;
+
+ /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
+
+ /**
+ * Surface flag: Hide the surface.
+ * Equivalent to calling hide().
+ * Updates the value set during Surface creation (see {@link #HIDDEN}).
+ */
+ private static final int SURFACE_HIDDEN = 0x01;
+
+ /**
+ * Surface flag: composite without blending when possible.
+ * Updates the value set during Surface creation (see {@link #OPAQUE}).
+ */
+ private static final int SURFACE_OPAQUE = 0x02;
+
+
+ /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
+ * these are different from the logical display ids used elsewhere in the framework */
+
+ /**
+ * Built-in physical display id: Main display.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
+ */
+ public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
+
+ /**
+ * Built-in physical display id: Attached HDMI display.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
+ */
+ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
+
+ /* Display power modes * /
+
+ /**
+ * Display power mode off: used while blanking the screen.
+ * Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ */
+ public static final int POWER_MODE_OFF = 0;
+
+ /**
+ * Display power mode doze: used while putting the screen into low power mode.
+ * Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ */
+ public static final int POWER_MODE_DOZE = 1;
+
+ /**
+ * Display power mode normal: used while unblanking the screen.
+ * Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ */
+ public static final int POWER_MODE_NORMAL = 2;
+
+ /**
+ * Display power mode doze: used while putting the screen into a suspended
+ * low power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ */
+ public static final int POWER_MODE_DOZE_SUSPEND = 3;
+
+ /**
+ * A value for windowType used to indicate that the window should be omitted from screenshots
+ * and display mirroring. A temporary workaround until we express such things with
+ * the hierarchy.
+ * TODO: b/64227542
+ * @hide
+ */
+ public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
+
+ /**
+ * Create a surface with a name.
+ * <p>
+ * The surface creation flags specify what kind of surface to create and
+ * certain options such as whether the surface can be assumed to be opaque
+ * and whether it should be initially hidden. Surfaces should always be
+ * created with the {@link #HIDDEN} flag set to ensure that they are not
+ * made visible prematurely before all of the surface's properties have been
+ * configured.
+ * <p>
+ * Good practice is to first create the surface with the {@link #HIDDEN} flag
+ * specified, open a transaction, set the surface layer, layer stack, alpha,
+ * and position, call {@link #show} if appropriate, and close the transaction.
+ *
+ * @param session The surface session, must not be null.
+ * @param name The surface name, must not be null.
+ * @param w The surface initial width.
+ * @param h The surface initial height.
+ * @param flags The surface creation flags. Should always include {@link #HIDDEN}
+ * in the creation flags.
+ * @param windowType The type of the window as specified in WindowManager.java.
+ * @param ownerUid A unique per-app ID.
+ *
+ * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
+ */
+ public SurfaceControl(SurfaceSession session,
+ String name, int w, int h, int format, int flags, int windowType, int ownerUid)
+ throws OutOfResourcesException {
+ this(session, name, w, h, format, flags, null, windowType, ownerUid);
+ }
+
+ public SurfaceControl(SurfaceSession session,
+ String name, int w, int h, int format, int flags)
+ throws OutOfResourcesException {
+ this(session, name, w, h, format, flags, null, INVALID_WINDOW_TYPE, Binder.getCallingUid());
+ }
+
+ public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
+ SurfaceControl parent, int windowType, int ownerUid)
+ throws OutOfResourcesException {
+ if (session == null) {
+ throw new IllegalArgumentException("session must not be null");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+
+ if ((flags & SurfaceControl.HIDDEN) == 0) {
+ Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
+ + "to ensure that they are not made visible prematurely before "
+ + "all of the surface's properties have been configured. "
+ + "Set the other properties and make the surface visible within "
+ + "a transaction. New surface name: " + name,
+ new Throwable());
+ }
+
+ mName = name;
+ mNativeObject = nativeCreate(session, name, w, h, format, flags,
+ parent != null ? parent.mNativeObject : 0, windowType, ownerUid);
+ if (mNativeObject == 0) {
+ throw new OutOfResourcesException(
+ "Couldn't allocate SurfaceControl native object");
+ }
+
+ mCloseGuard.open("release");
+ }
+
+ // This is a transfer constructor, useful for transferring a live SurfaceControl native
+ // object to another Java wrapper which could have some different behavior, e.g.
+ // event logging.
+ public SurfaceControl(SurfaceControl other) {
+ mName = other.mName;
+ mNativeObject = other.mNativeObject;
+ other.mCloseGuard.close();
+ other.mNativeObject = 0;
+ mCloseGuard.open("release");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Surface(name=" + mName + ")";
+ }
+
+ /**
+ * Release the local reference to the server-side surface.
+ * Always call release() when you're done with a Surface.
+ * This will make the surface invalid.
+ */
+ public void release() {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ mNativeObject = 0;
+ }
+ mCloseGuard.close();
+ }
+
+ /**
+ * Free all server-side state associated with this surface and
+ * release this object's reference. This method can only be
+ * called from the process that created the service.
+ */
+ public void destroy() {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ mNativeObject = 0;
+ }
+ mCloseGuard.close();
+ }
+
+ /**
+ * Disconnect any client still connected to the surface.
+ */
+ public void disconnect() {
+ if (mNativeObject != 0) {
+ nativeDisconnect(mNativeObject);
+ }
+ }
+
+ private void checkNotReleased() {
+ if (mNativeObject == 0) throw new NullPointerException(
+ "mNativeObject is null. Have you called release() already?");
+ }
+
+ /*
+ * set surface parameters.
+ * needs to be inside open/closeTransaction block
+ */
+
+ /** start a transaction */
+ public static void openTransaction() {
+ nativeOpenTransaction();
+ }
+
+ /** end a transaction */
+ public static void closeTransaction() {
+ nativeCloseTransaction(false);
+ }
+
+ public static void closeTransactionSync() {
+ nativeCloseTransaction(true);
+ }
+
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ if (frame > 0) {
+ nativeDeferTransactionUntil(mNativeObject, handle, frame);
+ }
+ }
+
+ public void deferTransactionUntil(Surface barrier, long frame) {
+ if (frame > 0) {
+ nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
+ }
+ }
+
+ public void reparentChildren(IBinder newParentHandle) {
+ nativeReparentChildren(mNativeObject, newParentHandle);
+ }
+
+ /** Re-parents a specific child layer to a new parent */
+ public void reparentChild(IBinder newParentHandle, IBinder childHandle) {
+ nativeReparentChild(mNativeObject, newParentHandle, childHandle);
+ }
+
+ public void detachChildren() {
+ nativeSeverChildren(mNativeObject);
+ }
+
+ public void setOverrideScalingMode(int scalingMode) {
+ checkNotReleased();
+ nativeSetOverrideScalingMode(mNativeObject, scalingMode);
+ }
+
+ public IBinder getHandle() {
+ return nativeGetHandle(mNativeObject);
+ }
+
+ /** flag the transaction as an animation */
+ public static void setAnimationTransaction() {
+ nativeSetAnimationTransaction();
+ }
+
+ public void setLayer(int zorder) {
+ checkNotReleased();
+ nativeSetLayer(mNativeObject, zorder);
+ }
+
+ public void setRelativeLayer(IBinder relativeTo, int zorder) {
+ checkNotReleased();
+ nativeSetRelativeLayer(mNativeObject, relativeTo, zorder);
+ }
+
+ public void setPosition(float x, float y) {
+ checkNotReleased();
+ nativeSetPosition(mNativeObject, x, y);
+ }
+
+ /**
+ * If the buffer size changes in this transaction, position and crop updates specified
+ * in this transaction will not complete until a buffer of the new size
+ * arrives. As transform matrix and size are already frozen in this fashion,
+ * this enables totally freezing the surface until the resize has completed
+ * (at which point the geometry influencing aspects of this transaction will then occur)
+ */
+ public void setGeometryAppliesWithResize() {
+ checkNotReleased();
+ nativeSetGeometryAppliesWithResize(mNativeObject);
+ }
+
+ public void setSize(int w, int h) {
+ checkNotReleased();
+ nativeSetSize(mNativeObject, w, h);
+ }
+
+ public void hide() {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+ }
+
+ public void show() {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
+ }
+
+ public void setTransparentRegionHint(Region region) {
+ checkNotReleased();
+ nativeSetTransparentRegionHint(mNativeObject, region);
+ }
+
+ public boolean clearContentFrameStats() {
+ checkNotReleased();
+ return nativeClearContentFrameStats(mNativeObject);
+ }
+
+ public boolean getContentFrameStats(WindowContentFrameStats outStats) {
+ checkNotReleased();
+ return nativeGetContentFrameStats(mNativeObject, outStats);
+ }
+
+ public static boolean clearAnimationFrameStats() {
+ return nativeClearAnimationFrameStats();
+ }
+
+ public static boolean getAnimationFrameStats(WindowAnimationFrameStats outStats) {
+ return nativeGetAnimationFrameStats(outStats);
+ }
+
+ /**
+ * Sets an alpha value for the entire Surface. This value is combined with the
+ * per-pixel alpha. It may be used with opaque Surfaces.
+ */
+ public void setAlpha(float alpha) {
+ checkNotReleased();
+ nativeSetAlpha(mNativeObject, alpha);
+ }
+
+ public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
+ checkNotReleased();
+ nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
+ }
+
+ public void setWindowCrop(Rect crop) {
+ checkNotReleased();
+ if (crop != null) {
+ nativeSetWindowCrop(mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
+ }
+ }
+
+ public void setFinalCrop(Rect crop) {
+ checkNotReleased();
+ if (crop != null) {
+ nativeSetFinalCrop(mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0);
+ }
+ }
+
+ public void setLayerStack(int layerStack) {
+ checkNotReleased();
+ nativeSetLayerStack(mNativeObject, layerStack);
+ }
+
+ /**
+ * Sets the opacity of the surface. Setting the flag is equivalent to creating the
+ * Surface with the {@link #OPAQUE} flag.
+ */
+ public void setOpaque(boolean isOpaque) {
+ checkNotReleased();
+ if (isOpaque) {
+ nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
+ } else {
+ nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE);
+ }
+ }
+
+ /**
+ * Sets the security of the surface. Setting the flag is equivalent to creating the
+ * Surface with the {@link #SECURE} flag.
+ */
+ public void setSecure(boolean isSecure) {
+ checkNotReleased();
+ if (isSecure) {
+ nativeSetFlags(mNativeObject, SECURE, SECURE);
+ } else {
+ nativeSetFlags(mNativeObject, 0, SECURE);
+ }
+ }
+
+ /*
+ * set display parameters.
+ * needs to be inside open/closeTransaction block
+ */
+
+ /**
+ * Describes the properties of a physical display known to surface flinger.
+ */
+ public static final class PhysicalDisplayInfo {
+ public int width;
+ public int height;
+ public float refreshRate;
+ public float density;
+ public float xDpi;
+ public float yDpi;
+ public boolean secure;
+ public long appVsyncOffsetNanos;
+ public long presentationDeadlineNanos;
+
+ public PhysicalDisplayInfo() {
+ }
+
+ public PhysicalDisplayInfo(PhysicalDisplayInfo other) {
+ copyFrom(other);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o);
+ }
+
+ public boolean equals(PhysicalDisplayInfo other) {
+ return other != null
+ && width == other.width
+ && height == other.height
+ && refreshRate == other.refreshRate
+ && density == other.density
+ && xDpi == other.xDpi
+ && yDpi == other.yDpi
+ && secure == other.secure
+ && appVsyncOffsetNanos == other.appVsyncOffsetNanos
+ && presentationDeadlineNanos == other.presentationDeadlineNanos;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ public void copyFrom(PhysicalDisplayInfo other) {
+ width = other.width;
+ height = other.height;
+ refreshRate = other.refreshRate;
+ density = other.density;
+ xDpi = other.xDpi;
+ yDpi = other.yDpi;
+ secure = other.secure;
+ appVsyncOffsetNanos = other.appVsyncOffsetNanos;
+ presentationDeadlineNanos = other.presentationDeadlineNanos;
+ }
+
+ // For debugging purposes
+ @Override
+ public String toString() {
+ return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
+ + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure
+ + ", appVsyncOffset " + appVsyncOffsetNanos
+ + ", bufferDeadline " + presentationDeadlineNanos + "}";
+ }
+ }
+
+ public static void setDisplayPowerMode(IBinder displayToken, int mode) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayPowerMode(displayToken, mode);
+ }
+
+ public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeGetDisplayConfigs(displayToken);
+ }
+
+ public static int getActiveConfig(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeGetActiveConfig(displayToken);
+ }
+
+ public static boolean setActiveConfig(IBinder displayToken, int id) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeSetActiveConfig(displayToken, id);
+ }
+
+ public static int[] getDisplayColorModes(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeGetDisplayColorModes(displayToken);
+ }
+
+ public static int getActiveColorMode(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeGetActiveColorMode(displayToken);
+ }
+
+ public static boolean setActiveColorMode(IBinder displayToken, int colorMode) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeSetActiveColorMode(displayToken, colorMode);
+ }
+
+ public static void setDisplayProjection(IBinder displayToken,
+ int orientation, Rect layerStackRect, Rect displayRect) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (layerStackRect == null) {
+ throw new IllegalArgumentException("layerStackRect must not be null");
+ }
+ if (displayRect == null) {
+ throw new IllegalArgumentException("displayRect must not be null");
+ }
+ nativeSetDisplayProjection(displayToken, orientation,
+ layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+ displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+ }
+
+ public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayLayerStack(displayToken, layerStack);
+ }
+
+ public static void setDisplaySurface(IBinder displayToken, Surface surface) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ if (surface != null) {
+ synchronized (surface.mLock) {
+ nativeSetDisplaySurface(displayToken, surface.mNativeObject);
+ }
+ } else {
+ nativeSetDisplaySurface(displayToken, 0);
+ }
+ }
+
+ public static void setDisplaySize(IBinder displayToken, int width, int height) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width and height must be positive");
+ }
+
+ nativeSetDisplaySize(displayToken, width, height);
+ }
+
+ public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ return nativeGetHdrCapabilities(displayToken);
+ }
+
+ public static IBinder createDisplay(String name, boolean secure) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ return nativeCreateDisplay(name, secure);
+ }
+
+ public static void destroyDisplay(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeDestroyDisplay(displayToken);
+ }
+
+ public static IBinder getBuiltInDisplay(int builtInDisplayId) {
+ return nativeGetBuiltInDisplay(builtInDisplayId);
+ }
+
+ /**
+ * Copy the current screen contents into the provided {@link Surface}
+ *
+ * @param display The display to take the screenshot of.
+ * @param consumer The {@link Surface} to take the screenshot into.
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param minLayer The lowest (bottom-most Z order) surface layer to
+ * include in the screenshot.
+ * @param maxLayer The highest (top-most Z order) surface layer to
+ * include in the screenshot.
+ * @param useIdentityTransform Replace whatever transformation (rotation,
+ * scaling, translation) the surface layers are currently using with the
+ * identity transformation while taking the screenshot.
+ */
+ public static void screenshot(IBinder display, Surface consumer,
+ int width, int height, int minLayer, int maxLayer,
+ boolean useIdentityTransform) {
+ screenshot(display, consumer, new Rect(), width, height, minLayer, maxLayer,
+ false, useIdentityTransform);
+ }
+
+ /**
+ * Copy the current screen contents into the provided {@link Surface}
+ *
+ * @param display The display to take the screenshot of.
+ * @param consumer The {@link Surface} to take the screenshot into.
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ */
+ public static void screenshot(IBinder display, Surface consumer,
+ int width, int height) {
+ screenshot(display, consumer, new Rect(), width, height, 0, 0, true, false);
+ }
+
+ /**
+ * Copy the current screen contents into the provided {@link Surface}
+ *
+ * @param display The display to take the screenshot of.
+ * @param consumer The {@link Surface} to take the screenshot into.
+ */
+ public static void screenshot(IBinder display, Surface consumer) {
+ screenshot(display, consumer, new Rect(), 0, 0, 0, 0, true, false);
+ }
+
+ /**
+ * Copy the current screen contents into a bitmap and return it.
+ *
+ * CAVEAT: Versions of screenshot that return a {@link Bitmap} can
+ * be extremely slow; avoid use unless absolutely necessary; prefer
+ * the versions that use a {@link Surface} instead, such as
+ * {@link SurfaceControl#screenshot(IBinder, Surface)}.
+ *
+ * @param sourceCrop The portion of the screen to capture into the Bitmap;
+ * caller may pass in 'new Rect()' if no cropping is desired.
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param minLayer The lowest (bottom-most Z order) surface layer to
+ * include in the screenshot.
+ * @param maxLayer The highest (top-most Z order) surface layer to
+ * include in the screenshot.
+ * @param useIdentityTransform Replace whatever transformation (rotation,
+ * scaling, translation) the surface layers are currently using with the
+ * identity transformation while taking the screenshot.
+ * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
+ * Surface.ROTATION_0,90,180,270. Surfaceflinger will always take
+ * screenshots in its native portrait orientation by default, so this is
+ * useful for returning screenshots that are independent of device
+ * orientation.
+ * @return Returns a Bitmap containing the screen contents, or null
+ * if an error occurs. Make sure to call Bitmap.recycle() as soon as
+ * possible, once its content is not needed anymore.
+ */
+ public static Bitmap screenshot(Rect sourceCrop, int width, int height,
+ int minLayer, int maxLayer, boolean useIdentityTransform,
+ int rotation) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, sourceCrop, width, height,
+ minLayer, maxLayer, false, useIdentityTransform, rotation);
+ }
+
+ /**
+ * Like {@link SurfaceControl#screenshot(Rect, int, int, int, int, boolean, int)}
+ * but returns a GraphicBuffer.
+ */
+ public static GraphicBuffer screenshotToBuffer(Rect sourceCrop, int width, int height,
+ int minLayer, int maxLayer, boolean useIdentityTransform,
+ int rotation) {
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshotToBuffer(displayToken, sourceCrop, width, height,
+ minLayer, maxLayer, false, useIdentityTransform, rotation);
+ }
+
+ /**
+ * Like {@link SurfaceControl#screenshot(int, int, int, int, boolean)} but
+ * includes all Surfaces in the screenshot.
+ *
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @return Returns a Bitmap containing the screen contents, or null
+ * if an error occurs. Make sure to call Bitmap.recycle() as soon as
+ * possible, once its content is not needed anymore.
+ */
+ public static Bitmap screenshot(int width, int height) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true,
+ false, Surface.ROTATION_0);
+ }
+
+ private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop,
+ int width, int height, int minLayer, int maxLayer, boolean allLayers,
+ boolean useIdentityTransform) {
+ if (display == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (consumer == null) {
+ throw new IllegalArgumentException("consumer must not be null");
+ }
+ nativeScreenshot(display, consumer, sourceCrop, width, height,
+ minLayer, maxLayer, allLayers, useIdentityTransform);
+ }
+}
diff --git a/android/view/SurfaceHolder.java b/android/view/SurfaceHolder.java
new file mode 100644
index 00000000..2fd2e966
--- /dev/null
+++ b/android/view/SurfaceHolder.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2006 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.Canvas;
+import android.graphics.Rect;
+
+/**
+ * Abstract interface to someone holding a display surface. Allows you to
+ * control the surface size and format, edit the pixels in the surface, and
+ * monitor changes to the surface. This interface is typically available
+ * through the {@link SurfaceView} class.
+ *
+ * <p>When using this interface from a thread other than the one running
+ * its {@link SurfaceView}, you will want to carefully read the
+ * methods
+ * {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated()}.
+ */
+public interface SurfaceHolder {
+
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int SURFACE_TYPE_NORMAL = 0;
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int SURFACE_TYPE_HARDWARE = 1;
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int SURFACE_TYPE_GPU = 2;
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int SURFACE_TYPE_PUSH_BUFFERS = 3;
+
+ /**
+ * Exception that is thrown from {@link #lockCanvas} when called on a Surface
+ * whose type is SURFACE_TYPE_PUSH_BUFFERS.
+ */
+ public static class BadSurfaceTypeException extends RuntimeException {
+ public BadSurfaceTypeException() {
+ }
+
+ public BadSurfaceTypeException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * A client may implement this interface to receive information about
+ * changes to the surface. When used with a {@link SurfaceView}, the
+ * Surface being held is only available between calls to
+ * {@link #surfaceCreated(SurfaceHolder)} and
+ * {@link #surfaceDestroyed(SurfaceHolder)}. The Callback is set with
+ * {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
+ */
+ public interface Callback {
+ /**
+ * This is called immediately after the surface is first created.
+ * Implementations of this should start up whatever rendering code
+ * they desire. Note that only one thread can ever draw into
+ * a {@link Surface}, so you should not draw into the Surface here
+ * if your normal rendering will be in another thread.
+ *
+ * @param holder The SurfaceHolder whose surface is being created.
+ */
+ public void surfaceCreated(SurfaceHolder holder);
+
+ /**
+ * This is called immediately after any structural changes (format or
+ * size) have been made to the surface. You should at this point update
+ * the imagery in the surface. This method is always called at least
+ * once, after {@link #surfaceCreated}.
+ *
+ * @param holder The SurfaceHolder whose surface has changed.
+ * @param format The new PixelFormat of the surface.
+ * @param width The new width of the surface.
+ * @param height The new height of the surface.
+ */
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height);
+
+ /**
+ * This is called immediately before a surface is being destroyed. After
+ * returning from this call, you should no longer try to access this
+ * surface. If you have a rendering thread that directly accesses
+ * the surface, you must ensure that thread is no longer touching the
+ * Surface before returning from this function.
+ *
+ * @param holder The SurfaceHolder whose surface is being destroyed.
+ */
+ public void surfaceDestroyed(SurfaceHolder holder);
+ }
+
+ /**
+ * Additional callbacks that can be received for {@link Callback}.
+ */
+ public interface Callback2 extends Callback {
+ /**
+ * Called when the application needs to redraw the content of its
+ * surface, after it is resized or for some other reason. By not
+ * returning from here until the redraw is complete, you can ensure that
+ * the user will not see your surface in a bad state (at its new
+ * size before it has been correctly drawn that way). This will
+ * typically be preceeded by a call to {@link #surfaceChanged}.
+ *
+ * As of O, {@link #surfaceRedrawNeededAsync} may be implemented
+ * to provide a non-blocking implementation. If {@link #surfaceRedrawNeededAsync}
+ * is not implemented, then this will be called instead.
+ *
+ * @param holder The SurfaceHolder whose surface has changed.
+ */
+ void surfaceRedrawNeeded(SurfaceHolder holder);
+
+ /**
+ * An alternative to surfaceRedrawNeeded where it is not required to block
+ * until the redraw is complete. You should initiate the redraw, and return,
+ * later invoking drawingFinished when your redraw is complete.
+ *
+ * This can be useful to avoid blocking your main application thread on rendering.
+ *
+ * As of O, if this is implemented {@link #surfaceRedrawNeeded} will not be called.
+ * However it is still recommended to implement {@link #surfaceRedrawNeeded} for
+ * compatibility with older versions of the platform.
+ *
+ * @param holder The SurfaceHolder which needs redrawing.
+ * @param drawingFinished A runnable to signal completion. This may be invoked
+ * from any thread.
+ *
+ */
+ default void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) {
+ surfaceRedrawNeeded(holder);
+ drawingFinished.run();
+ }
+ }
+
+ /**
+ * Add a Callback interface for this holder. There can several Callback
+ * interfaces associated with a holder.
+ *
+ * @param callback The new Callback interface.
+ */
+ public void addCallback(Callback callback);
+
+ /**
+ * Removes a previously added Callback interface from this holder.
+ *
+ * @param callback The Callback interface to remove.
+ */
+ public void removeCallback(Callback callback);
+
+ /**
+ * Use this method to find out if the surface is in the process of being
+ * created from Callback methods. This is intended to be used with
+ * {@link Callback#surfaceChanged}.
+ *
+ * @return true if the surface is in the process of being created.
+ */
+ public boolean isCreating();
+
+ /**
+ * Sets the surface's type.
+ *
+ * @deprecated this is ignored, this value is set automatically when needed.
+ */
+ @Deprecated
+ public void setType(int type);
+
+ /**
+ * Make the surface a fixed size. It will never change from this size.
+ * When working with a {@link SurfaceView}, this must be called from the
+ * same thread running the SurfaceView's window.
+ *
+ * @param width The surface's width.
+ * @param height The surface's height.
+ */
+ public void setFixedSize(int width, int height);
+
+ /**
+ * Allow the surface to resized based on layout of its container (this is
+ * the default). When this is enabled, you should monitor
+ * {@link Callback#surfaceChanged} for changes to the size of the surface.
+ * When working with a {@link SurfaceView}, this must be called from the
+ * same thread running the SurfaceView's window.
+ */
+ public void setSizeFromLayout();
+
+ /**
+ * Set the desired PixelFormat of the surface. The default is OPAQUE.
+ * When working with a {@link SurfaceView}, this must be called from the
+ * same thread running the SurfaceView's window.
+ *
+ * @param format A constant from PixelFormat.
+ *
+ * @see android.graphics.PixelFormat
+ */
+ public void setFormat(int format);
+
+ /**
+ * Enable or disable option to keep the screen turned on while this
+ * surface is displayed. The default is false, allowing it to turn off.
+ * This is safe to call from any thread.
+ *
+ * @param screenOn Set to true to force the screen to stay on, false
+ * to allow it to turn off.
+ */
+ public void setKeepScreenOn(boolean screenOn);
+
+ /**
+ * Start editing the pixels in the surface. The returned Canvas can be used
+ * to draw into the surface's bitmap. A null is returned if the surface has
+ * not been created or otherwise cannot be edited. You will usually need
+ * to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
+ * to find out when the Surface is available for use.
+ *
+ * <p>The content of the Surface is never preserved between unlockCanvas() and
+ * lockCanvas(), for this reason, every pixel within the Surface area
+ * must be written. The only exception to this rule is when a dirty
+ * rectangle is specified, in which case, non-dirty pixels will be
+ * preserved.
+ *
+ * <p>If you call this repeatedly when the Surface is not ready (before
+ * {@link Callback#surfaceCreated Callback.surfaceCreated} or after
+ * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed}), your calls
+ * will be throttled to a slow rate in order to avoid consuming CPU.
+ *
+ * <p>If null is not returned, this function internally holds a lock until
+ * the corresponding {@link #unlockCanvasAndPost} call, preventing
+ * {@link SurfaceView} from creating, destroying, or modifying the surface
+ * while it is being drawn. This can be more convenient than accessing
+ * the Surface directly, as you do not need to do special synchronization
+ * with a drawing thread in {@link Callback#surfaceDestroyed
+ * Callback.surfaceDestroyed}.
+ *
+ * @return Canvas Use to draw into the surface.
+ */
+ public Canvas lockCanvas();
+
+
+ /**
+ * Just like {@link #lockCanvas()} but allows specification of a dirty rectangle.
+ * Every
+ * pixel within that rectangle must be written; however pixels outside
+ * the dirty rectangle will be preserved by the next call to lockCanvas().
+ *
+ * @see android.view.SurfaceHolder#lockCanvas
+ *
+ * @param dirty Area of the Surface that will be modified.
+ * @return Canvas Use to draw into the surface.
+ */
+ public Canvas lockCanvas(Rect dirty);
+
+ /**
+ * <p>Just like {@link #lockCanvas()} but the returned canvas is hardware-accelerated.
+ *
+ * <p>See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+ * unsupported drawing operations</a> for a list of what is and isn't
+ * supported in a hardware-accelerated canvas.
+ *
+ * @return Canvas Use to draw into the surface.
+ * @throws IllegalStateException If the canvas cannot be locked.
+ */
+ default Canvas lockHardwareCanvas() {
+ throw new IllegalStateException("This SurfaceHolder doesn't support lockHardwareCanvas");
+ }
+
+ /**
+ * Finish editing pixels in the surface. After this call, the surface's
+ * current pixels will be shown on the screen, but its content is lost,
+ * in particular there is no guarantee that the content of the Surface
+ * will remain unchanged when lockCanvas() is called again.
+ *
+ * @see #lockCanvas()
+ *
+ * @param canvas The Canvas previously returned by lockCanvas().
+ */
+ public void unlockCanvasAndPost(Canvas canvas);
+
+ /**
+ * Retrieve the current size of the surface. Note: do not modify the
+ * returned Rect. This is only safe to call from the thread of
+ * {@link SurfaceView}'s window, or while inside of
+ * {@link #lockCanvas()}.
+ *
+ * @return Rect The surface's dimensions. The left and top are always 0.
+ */
+ public Rect getSurfaceFrame();
+
+ /**
+ * Direct access to the surface object. The Surface may not always be
+ * available -- for example when using a {@link SurfaceView} the holder's
+ * Surface is not created until the view has been attached to the window
+ * manager and performed a layout in order to determine the dimensions
+ * and screen position of the Surface. You will thus usually need
+ * to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
+ * to find out when the Surface is available for use.
+ *
+ * <p>Note that if you directly access the Surface from another thread,
+ * it is critical that you correctly implement
+ * {@link Callback#surfaceCreated Callback.surfaceCreated} and
+ * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed} to ensure
+ * that thread only accesses the Surface while it is valid, and that the
+ * Surface does not get destroyed while the thread is using it.
+ *
+ * <p>This method is intended to be used by frameworks which often need
+ * direct access to the Surface object (usually to pass it to native code).
+ *
+ * @return Surface The surface.
+ */
+ public Surface getSurface();
+}
diff --git a/android/view/SurfaceSession.java b/android/view/SurfaceSession.java
new file mode 100644
index 00000000..b5912bc1
--- /dev/null
+++ b/android/view/SurfaceSession.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2006 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;
+
+/**
+ * An instance of this class represents a connection to the surface
+ * flinger, from which you can create one or more Surface instances that will
+ * be composited to the screen.
+ * {@hide}
+ */
+public final class SurfaceSession {
+ // Note: This field is accessed by native code.
+ private long mNativeClient; // SurfaceComposerClient*
+
+ private static native long nativeCreate();
+ private static native long nativeCreateScoped(long surfacePtr);
+ private static native void nativeDestroy(long ptr);
+ private static native void nativeKill(long ptr);
+
+ /** Create a new connection with the surface flinger. */
+ public SurfaceSession() {
+ mNativeClient = nativeCreate();
+ }
+
+ public SurfaceSession(Surface root) {
+ mNativeClient = nativeCreateScoped(root.mNativeObject);
+ }
+
+ /* no user serviceable parts here ... */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mNativeClient != 0) {
+ nativeDestroy(mNativeClient);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Forcibly detach native resources associated with this object.
+ * Unlike destroy(), after this call any surfaces that were created
+ * from the session will no longer work.
+ */
+ public void kill() {
+ nativeKill(mNativeClient);
+ }
+}
+
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
new file mode 100644
index 00000000..cac27afa
--- /dev/null
+++ b/android/view/SurfaceView.java
@@ -0,0 +1,1224 @@
+/*
+ * Copyright (C) 2006 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 static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
+
+import android.content.Context;
+import android.content.res.CompatibilityInfo.Translator;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Provides a dedicated drawing surface embedded inside of a view hierarchy.
+ * You can control the format of this surface and, if you like, its size; the
+ * SurfaceView takes care of placing the surface at the correct location on the
+ * screen
+ *
+ * <p>The surface is Z ordered so that it is behind the window holding its
+ * SurfaceView; the SurfaceView punches a hole in its window to allow its
+ * surface to be displayed. The view hierarchy will take care of correctly
+ * compositing with the Surface any siblings of the SurfaceView that would
+ * normally appear on top of it. This can be used to place overlays such as
+ * buttons on top of the Surface, though note however that it can have an
+ * impact on performance since a full alpha-blended composite will be performed
+ * each time the Surface changes.
+ *
+ * <p> The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
+ *
+ * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
+ *
+ * <p>The Surface will be created for you while the SurfaceView's window is
+ * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
+ * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
+ * Surface is created and destroyed as the window is shown and hidden.
+ *
+ * <p>One of the purposes of this class is to provide a surface in which a
+ * secondary thread can render into the screen. If you are going to use it
+ * this way, you need to be aware of some threading semantics:
+ *
+ * <ul>
+ * <li> All SurfaceView and
+ * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
+ * from the thread running the SurfaceView's window (typically the main thread
+ * of the application). They thus need to correctly synchronize with any
+ * state that is also touched by the drawing thread.
+ * <li> You must ensure that the drawing thread only touches the underlying
+ * Surface while it is valid -- between
+ * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
+ * and
+ * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
+ * </ul>
+ *
+ * <p class="note"><strong>Note:</strong> Starting in platform version
+ * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
+ * updated synchronously with other View rendering. This means that translating
+ * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
+ * artifacts may occur on previous versions of the platform when its window is
+ * positioned asynchronously.</p>
+ */
+public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
+ private static final String TAG = "SurfaceView";
+ private static final boolean DEBUG = false;
+
+ final ArrayList<SurfaceHolder.Callback> mCallbacks
+ = new ArrayList<SurfaceHolder.Callback>();
+
+ final int[] mLocation = new int[2];
+
+ final ReentrantLock mSurfaceLock = new ReentrantLock();
+ final Surface mSurface = new Surface(); // Current surface in use
+ boolean mDrawingStopped = true;
+ // We use this to track if the application has produced a frame
+ // in to the Surface. Up until that point, we should be careful not to punch
+ // holes.
+ boolean mDrawFinished = false;
+
+ final Rect mScreenRect = new Rect();
+ SurfaceSession mSurfaceSession;
+
+ SurfaceControl mSurfaceControl;
+ // In the case of format changes we switch out the surface in-place
+ // we need to preserve the old one until the new one has drawn.
+ SurfaceControl mDeferredDestroySurfaceControl;
+ final Rect mTmpRect = new Rect();
+ final Configuration mConfiguration = new Configuration();
+
+ int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+
+ boolean mIsCreating = false;
+ private volatile boolean mRtHandlingPositionUpdates = false;
+
+ private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+ = new ViewTreeObserver.OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ updateSurface();
+ }
+ };
+
+ private final ViewTreeObserver.OnPreDrawListener mDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ // reposition ourselves where the surface is
+ mHaveFrame = getWidth() > 0 && getHeight() > 0;
+ updateSurface();
+ return true;
+ }
+ };
+
+ boolean mRequestedVisible = false;
+ boolean mWindowVisibility = false;
+ boolean mLastWindowVisibility = false;
+ boolean mViewVisibility = false;
+ boolean mWindowStopped = false;
+
+ int mRequestedWidth = -1;
+ int mRequestedHeight = -1;
+ /* Set SurfaceView's format to 565 by default to maintain backward
+ * compatibility with applications assuming this format.
+ */
+ int mRequestedFormat = PixelFormat.RGB_565;
+
+ boolean mHaveFrame = false;
+ boolean mSurfaceCreated = false;
+ long mLastLockTime = 0;
+
+ boolean mVisible = false;
+ int mWindowSpaceLeft = -1;
+ int mWindowSpaceTop = -1;
+ int mSurfaceWidth = -1;
+ int mSurfaceHeight = -1;
+ int mFormat = -1;
+ final Rect mSurfaceFrame = new Rect();
+ int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
+ private Translator mTranslator;
+
+ private boolean mGlobalListenersAdded;
+ private boolean mAttachedToWindow;
+
+ private int mSurfaceFlags = SurfaceControl.HIDDEN;
+
+ private int mPendingReportDraws;
+
+ public SurfaceView(Context context) {
+ this(context, null);
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mRenderNode.requestPositionUpdates(this);
+
+ setWillNotDraw(true);
+ }
+
+ /**
+ * Return the SurfaceHolder providing access and control over this
+ * SurfaceView's underlying surface.
+ *
+ * @return SurfaceHolder The holder of the surface.
+ */
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
+ }
+
+ private void updateRequestedVisibility() {
+ mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
+ }
+
+ /** @hide */
+ @Override
+ public void windowStopped(boolean stopped) {
+ mWindowStopped = stopped;
+ updateRequestedVisibility();
+ updateSurface();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getViewRootImpl().addWindowStoppedCallback(this);
+ mWindowStopped = false;
+
+ mViewVisibility = getVisibility() == VISIBLE;
+ updateRequestedVisibility();
+
+ mAttachedToWindow = true;
+ if (!mGlobalListenersAdded) {
+ ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnScrollChangedListener(mScrollChangedListener);
+ observer.addOnPreDrawListener(mDrawListener);
+ mGlobalListenersAdded = true;
+ }
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ mWindowVisibility = visibility == VISIBLE;
+ updateRequestedVisibility();
+ updateSurface();
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ mViewVisibility = visibility == VISIBLE;
+ boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
+ if (newRequestedVisible != mRequestedVisible) {
+ // our base class (View) invalidates the layout only when
+ // we go from/to the GONE state. However, SurfaceView needs
+ // to request a re-layout when the visibility changes at all.
+ // This is needed because the transparent region is computed
+ // as part of the layout phase, and it changes (obviously) when
+ // the visibility changes.
+ requestLayout();
+ }
+ mRequestedVisible = newRequestedVisible;
+ updateSurface();
+ }
+
+ private void performDrawFinished() {
+ if (mPendingReportDraws > 0) {
+ mDrawFinished = true;
+ if (mAttachedToWindow) {
+ mParent.requestTransparentRegion(SurfaceView.this);
+
+ notifyDrawFinished();
+ invalidate();
+ }
+ } else {
+ Log.e(TAG, System.identityHashCode(this) + "finished drawing"
+ + " but no pending report draw (extra call"
+ + " to draw completion runnable?)");
+ }
+ }
+
+ void notifyDrawFinished() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.pendingDrawFinished();
+ }
+ mPendingReportDraws--;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ // It's possible to create a SurfaceView using the default constructor and never
+ // attach it to a view hierarchy, this is a common use case when dealing with
+ // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
+ // the lifecycle. Instead of attaching it to a view, he/she can just pass
+ // the SurfaceHolder forward, most live wallpapers do it.
+ if (viewRoot != null) {
+ viewRoot.removeWindowStoppedCallback(this);
+ }
+
+ mAttachedToWindow = false;
+ if (mGlobalListenersAdded) {
+ ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnScrollChangedListener(mScrollChangedListener);
+ observer.removeOnPreDrawListener(mDrawListener);
+ mGlobalListenersAdded = false;
+ }
+
+ while (mPendingReportDraws > 0) {
+ notifyDrawFinished();
+ }
+
+ mRequestedVisible = false;
+
+ updateSurface();
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ }
+ mSurfaceControl = null;
+
+ mHaveFrame = false;
+
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = mRequestedWidth >= 0
+ ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
+ : getDefaultSize(0, widthMeasureSpec);
+ int height = mRequestedHeight >= 0
+ ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
+ : getDefaultSize(0, heightMeasureSpec);
+ setMeasuredDimension(width, height);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean result = super.setFrame(left, top, right, bottom);
+ updateSurface();
+ return result;
+ }
+
+ @Override
+ public boolean gatherTransparentRegion(Region region) {
+ if (isAboveParent() || !mDrawFinished) {
+ return super.gatherTransparentRegion(region);
+ }
+
+ boolean opaque = true;
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+ // this view draws, remove it from the transparent region
+ opaque = super.gatherTransparentRegion(region);
+ } else if (region != null) {
+ int w = getWidth();
+ int h = getHeight();
+ if (w>0 && h>0) {
+ getLocationInWindow(mLocation);
+ // otherwise, punch a hole in the whole hierarchy
+ int l = mLocation[0];
+ int t = mLocation[1];
+ region.op(l, t, l+w, t+h, Region.Op.UNION);
+ }
+ }
+ if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ opaque = false;
+ }
+ return opaque;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mDrawFinished && !isAboveParent()) {
+ // draw() is not called when SKIP_DRAW is set
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+ // punch a whole in the view-hierarchy below us
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
+ }
+ super.draw(canvas);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mDrawFinished && !isAboveParent()) {
+ // draw() is not called when SKIP_DRAW is set
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ // punch a whole in the view-hierarchy below us
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ /**
+ * Control whether the surface view's surface is placed on top of another
+ * regular surface view in the window (but still behind the window itself).
+ * This is typically used to place overlays on top of an underlying media
+ * surface view.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+ */
+ public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ mSubLayer = isMediaOverlay
+ ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
+ }
+
+ /**
+ * Control whether the surface view's surface is placed on top of its
+ * window. Normally it is placed behind the window, to allow it to
+ * (for the most part) appear to composite with the views in the
+ * hierarchy. By setting this, you cause it to be placed above the
+ * window. This means that none of the contents of the window this
+ * SurfaceView is in will be visible on top of its surface.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+ */
+ public void setZOrderOnTop(boolean onTop) {
+ if (onTop) {
+ mSubLayer = APPLICATION_PANEL_SUBLAYER;
+ } else {
+ mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+ }
+ }
+
+ /**
+ * Control whether the surface view's content should be treated as secure,
+ * preventing it from appearing in screenshots or from being viewed on
+ * non-secure displays.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>See {@link android.view.Display#FLAG_SECURE} for details.
+ *
+ * @param isSecure True if the surface view is secure.
+ */
+ public void setSecure(boolean isSecure) {
+ if (isSecure) {
+ mSurfaceFlags |= SurfaceControl.SECURE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.SECURE;
+ }
+ }
+
+ private void updateOpaqueFlag() {
+ if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ mSurfaceFlags |= SurfaceControl.OPAQUE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.OPAQUE;
+ }
+ }
+
+ private Rect getParentSurfaceInsets() {
+ final ViewRootImpl root = getViewRootImpl();
+ if (root == null) {
+ return null;
+ } else {
+ return root.mWindowAttributes.surfaceInsets;
+ }
+ }
+
+ /** @hide */
+ protected void updateSurface() {
+ if (!mHaveFrame) {
+ return;
+ }
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+ return;
+ }
+
+ mTranslator = viewRoot.mTranslator;
+ if (mTranslator != null) {
+ mSurface.setCompatibilityTranslator(mTranslator);
+ }
+
+ int myWidth = mRequestedWidth;
+ if (myWidth <= 0) myWidth = getWidth();
+ int myHeight = mRequestedHeight;
+ if (myHeight <= 0) myHeight = getHeight();
+
+ final boolean formatChanged = mFormat != mRequestedFormat;
+ final boolean visibleChanged = mVisible != mRequestedVisible;
+ final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
+ && mRequestedVisible;
+ final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
+ final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
+ boolean redrawNeeded = false;
+
+ if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+ getLocationInWindow(mLocation);
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Changes: creating=" + creating
+ + " format=" + formatChanged + " size=" + sizeChanged
+ + " visible=" + visibleChanged
+ + " left=" + (mWindowSpaceLeft != mLocation[0])
+ + " top=" + (mWindowSpaceTop != mLocation[1]));
+
+ try {
+ final boolean visible = mVisible = mRequestedVisible;
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
+ mFormat = mRequestedFormat;
+ mLastWindowVisibility = mWindowVisibility;
+
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ final Rect surfaceInsets = getParentSurfaceInsets();
+ mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+
+ if (creating) {
+ mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
+ mDeferredDestroySurfaceControl = mSurfaceControl;
+
+ updateOpaqueFlag();
+ mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
+ "SurfaceView - " + viewRoot.getTitle().toString(),
+ mSurfaceWidth, mSurfaceHeight, mFormat,
+ mSurfaceFlags);
+ } else if (mSurfaceControl == null) {
+ return;
+ }
+
+ boolean realSizeChanged = false;
+
+ mSurfaceLock.lock();
+ try {
+ mDrawingStopped = !visible;
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
+
+ SurfaceControl.openTransaction();
+ try {
+ mSurfaceControl.setLayer(mSubLayer);
+ if (mViewVisibility) {
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
+ }
+
+ // While creating the surface, we will set it's initial
+ // geometry. Outside of that though, we should generally
+ // leave it to the RenderThread.
+ //
+ // There is one more case when the buffer size changes we aren't yet
+ // prepared to sync (as even following the transaction applying
+ // we still need to latch a buffer).
+ // b/28866173
+ if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+ mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+ mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ mScreenRect.height() / (float) mSurfaceHeight);
+ }
+ if (sizeChanged) {
+ mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+ }
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+
+ if (sizeChanged || creating) {
+ redrawNeeded = true;
+ }
+
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (mTranslator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
+ } else {
+ float appInvertedScale = mTranslator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+ }
+
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+ } finally {
+ mSurfaceLock.unlock();
+ }
+
+ try {
+ redrawNeeded |= visible && !mDrawFinished;
+
+ SurfaceHolder.Callback callbacks[] = null;
+
+ final boolean surfaceChanged = creating;
+ if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
+ mSurfaceCreated = false;
+ if (mSurface.isValid()) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceDestroyed");
+ callbacks = getSurfaceCallbacks();
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
+ // Since Android N the same surface may be reused and given to us
+ // again by the system server at a later point. However
+ // as we didn't do this in previous releases, clients weren't
+ // necessarily required to clean up properly in
+ // surfaceDestroyed. This leads to problems for example when
+ // clients don't destroy their EGL context, and try
+ // and create a new one on the same surface following reuse.
+ // Since there is no valid use of the surface in-between
+ // surfaceDestroyed and surfaceCreated, we force a disconnect,
+ // so the next connect will always work if we end up reusing
+ // the surface.
+ if (mSurface.isValid()) {
+ mSurface.forceScopedDisconnect();
+ }
+ }
+ }
+
+ if (creating) {
+ mSurface.copyFrom(mSurfaceControl);
+ }
+
+ if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ mSurface.createFrom(mSurfaceControl);
+ }
+
+ if (visible && mSurface.isValid()) {
+ if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ mSurfaceCreated = true;
+ mIsCreating = true;
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceCreated");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
+ }
+ if (creating || formatChanged || sizeChanged
+ || visibleChanged || realSizeChanged) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceChanged -- format=" + mFormat
+ + " w=" + myWidth + " h=" + myHeight);
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ }
+ }
+ if (redrawNeeded) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceRedrawNeeded");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+
+ mPendingReportDraws++;
+ viewRoot.drawPending();
+ SurfaceCallbackHelper sch =
+ new SurfaceCallbackHelper(this::onDrawFinished);
+ sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+ }
+ }
+ } finally {
+ mIsCreating = false;
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ mSurface.release();
+ // If we are not in the stopped state, then the destruction of the Surface
+ // represents a visual change we need to display, and we should go ahead
+ // and destroy the SurfaceControl. However if we are in the stopped state,
+ // we can just leave the Surface around so it can be a part of animations,
+ // and we let the life-time be tied to the parent surface.
+ if (!mWindowStopped) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ }
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ if (DEBUG) Log.v(
+ TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+ + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+ + ", frame=" + mSurfaceFrame);
+ } else {
+ // Calculate the window position in case RT loses the window
+ // and we need to fallback to a UI-thread driven position update
+ getLocationInSurface(mLocation);
+ final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+ || mWindowSpaceTop != mLocation[1];
+ final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
+ || getHeight() != mScreenRect.height();
+ if (positionChanged || layoutSizeChanged) { // Only the position has changed
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
+ // in view local space.
+ mLocation[0] = getWidth();
+ mLocation[1] = getHeight();
+
+ mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+ mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, -1);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+ }
+
+ private void onDrawFinished() {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " "
+ + "finishedDrawing");
+ }
+
+ if (mDeferredDestroySurfaceControl != null) {
+ mDeferredDestroySurfaceControl.destroy();
+ mDeferredDestroySurfaceControl = null;
+ }
+
+ runOnUiThread(() -> {
+ performDrawFinished();
+ });
+ }
+
+ private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+
+ SurfaceControl.openTransaction();
+ try {
+ if (frameNumber > 0) {
+ mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
+ }
+ mSurfaceControl.setPosition(position.left, position.top);
+ mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ position.height() / (float) mSurfaceHeight);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
+ private Rect mRTLastReportedPosition = new Rect();
+
+ /**
+ * Called by native by a Rendering Worker thread to update the window position
+ * @hide
+ */
+ public final void updateSurfacePosition_renderWorker(long frameNumber,
+ int left, int top, int right, int bottom) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+ // its 2nd frame if RenderThread is running slowly could potentially see
+ // this as false, enter the branch, get pre-empted, then this comes along
+ // and reports a new position, then the UI thread resumes and reports
+ // its position. This could therefore be de-sync'd in that interval, but
+ // the synchronization would violate the rule that RT must never block
+ // on the UI thread which would open up potential deadlocks. The risk of
+ // a single-frame desync is therefore preferable for now.
+ mRtHandlingPositionUpdates = true;
+ if (mRTLastReportedPosition.left == left
+ && mRTLastReportedPosition.top == top
+ && mRTLastReportedPosition.right == right
+ && mRTLastReportedPosition.bottom == bottom) {
+ return;
+ }
+ try {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ frameNumber, left, top, right, bottom));
+ }
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+ // Now overwrite mRTLastReportedPosition with our values
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception from repositionChild", ex);
+ }
+ }
+
+ /**
+ * Called by native on RenderThread to notify that the view is no longer in the
+ * draw tree. UI thread is blocked at this point.
+ * @hide
+ */
+ public final void surfacePositionLost_uiRtSync(long frameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
+ System.identityHashCode(this), frameNumber));
+ }
+ mRTLastReportedPosition.setEmpty();
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+ if (mRtHandlingPositionUpdates) {
+ mRtHandlingPositionUpdates = false;
+ // This callback will happen while the UI thread is blocked, so we can
+ // safely access other member variables at this time.
+ // So do what the UI thread would have done if RT wasn't handling position
+ // updates.
+ if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, frameNumber);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+
+ private SurfaceHolder.Callback[] getSurfaceCallbacks() {
+ SurfaceHolder.Callback callbacks[];
+ synchronized (mCallbacks) {
+ callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+ mCallbacks.toArray(callbacks);
+ }
+ return callbacks;
+ }
+
+ /**
+ * This method still exists only for compatibility reasons because some applications have relied
+ * on this method via reflection. See Issue 36345857 for details.
+ *
+ * @deprecated No platform code is using this method anymore.
+ * @hide
+ */
+ @Deprecated
+ public void setWindowType(int type) {
+ if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+ throw new UnsupportedOperationException(
+ "SurfaceView#setWindowType() has never been a public API.");
+ }
+
+ if (type == TYPE_APPLICATION_PANEL) {
+ Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
+ + "just to make the SurfaceView to be placed on top of its window, you must "
+ + "call setZOrderOnTop(true) instead.", new Throwable());
+ setZOrderOnTop(true);
+ return;
+ }
+ Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
+ + "type=" + type, new Throwable());
+ }
+
+ private void runOnUiThread(Runnable runnable) {
+ Handler handler = getHandler();
+ if (handler != null && handler.getLooper() != Looper.myLooper()) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ /**
+ * Check to see if the surface has fixed size dimensions or if the surface's
+ * dimensions are dimensions are dependent on its current layout.
+ *
+ * @return true if the surface has dimensions that are fixed in size
+ * @hide
+ */
+ public boolean isFixedSize() {
+ return (mRequestedWidth != -1 || mRequestedHeight != -1);
+ }
+
+ private boolean isAboveParent() {
+ return mSubLayer >= 0;
+ }
+
+ private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private static final String LOG_TAG = "SurfaceHolder";
+
+ @Override
+ public boolean isCreating() {
+ return mIsCreating;
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ // This is a linear search, but in practice we'll
+ // have only a couple callbacks, so it doesn't matter.
+ if (mCallbacks.contains(callback) == false) {
+ mCallbacks.add(callback);
+ }
+ }
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ @Override
+ public void setFixedSize(int width, int height) {
+ if (mRequestedWidth != width || mRequestedHeight != height) {
+ mRequestedWidth = width;
+ mRequestedHeight = height;
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setSizeFromLayout() {
+ if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+ mRequestedWidth = mRequestedHeight = -1;
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setFormat(int format) {
+ // for backward compatibility reason, OPAQUE always
+ // means 565 for SurfaceView
+ if (format == PixelFormat.OPAQUE)
+ format = PixelFormat.RGB_565;
+
+ mRequestedFormat = format;
+ if (mSurfaceControl != null) {
+ updateSurface();
+ }
+ }
+
+ /**
+ * @deprecated setType is now ignored.
+ */
+ @Override
+ @Deprecated
+ public void setType(int type) { }
+
+ @Override
+ public void setKeepScreenOn(boolean screenOn) {
+ runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
+ }
+
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * The caller must redraw the entire surface.
+ * @return A canvas for drawing into the surface.
+ */
+ @Override
+ public Canvas lockCanvas() {
+ return internalLockCanvas(null, false);
+ }
+
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param inOutDirty A rectangle that represents the dirty region that the caller wants
+ * to redraw. This function may choose to expand the dirty rectangle if for example
+ * the surface has been resized or if the previous contents of the surface were
+ * not available. The caller must redraw the entire dirty region as represented
+ * by the contents of the inOutDirty rectangle upon return from this function.
+ * The caller may also pass <code>null</code> instead, in the case where the
+ * entire surface should be redrawn.
+ * @return A canvas for drawing into the surface.
+ */
+ @Override
+ public Canvas lockCanvas(Rect inOutDirty) {
+ return internalLockCanvas(inOutDirty, false);
+ }
+
+ @Override
+ public Canvas lockHardwareCanvas() {
+ return internalLockCanvas(null, true);
+ }
+
+ private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
+ mSurfaceLock.lock();
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+ + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
+
+ Canvas c = null;
+ if (!mDrawingStopped && mSurfaceControl != null) {
+ try {
+ if (hardware) {
+ c = mSurface.lockHardwareCanvas();
+ } else {
+ c = mSurface.lockCanvas(dirty);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception locking surface", e);
+ }
+ }
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
+ if (c != null) {
+ mLastLockTime = SystemClock.uptimeMillis();
+ return c;
+ }
+
+ // If the Surface is not ready to be drawn, then return null,
+ // but throttle calls to this function so it isn't called more
+ // than every 100ms.
+ long now = SystemClock.uptimeMillis();
+ long nextTime = mLastLockTime + 100;
+ if (nextTime > now) {
+ try {
+ Thread.sleep(nextTime-now);
+ } catch (InterruptedException e) {
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ mLastLockTime = now;
+ mSurfaceLock.unlock();
+
+ return null;
+ }
+
+ /**
+ * Posts the new contents of the {@link Canvas} to the surface and
+ * releases the {@link Canvas}.
+ *
+ * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+ */
+ @Override
+ public void unlockCanvasAndPost(Canvas canvas) {
+ mSurface.unlockCanvasAndPost(canvas);
+ mSurfaceLock.unlock();
+ }
+
+ @Override
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+ @Override
+ public Rect getSurfaceFrame() {
+ return mSurfaceFrame;
+ }
+ };
+
+ class SurfaceControlWithBackground extends SurfaceControl {
+ private SurfaceControl mBackgroundControl;
+ private boolean mOpaque = true;
+ public boolean mVisible = false;
+
+ public SurfaceControlWithBackground(SurfaceSession s,
+ String name, int w, int h, int format, int flags)
+ throws Exception {
+ super(s, name, w, h, format, flags);
+ mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
+ PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
+ mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ mBackgroundControl.setAlpha(alpha);
+ }
+
+ @Override
+ public void setLayer(int zorder) {
+ super.setLayer(zorder);
+ // -3 is below all other child layers as SurfaceView never goes below -2
+ mBackgroundControl.setLayer(-3);
+ }
+
+ @Override
+ public void setPosition(float x, float y) {
+ super.setPosition(x, y);
+ mBackgroundControl.setPosition(x, y);
+ }
+
+ @Override
+ public void setSize(int w, int h) {
+ super.setSize(w, h);
+ mBackgroundControl.setSize(w, h);
+ }
+
+ @Override
+ public void setWindowCrop(Rect crop) {
+ super.setWindowCrop(crop);
+ mBackgroundControl.setWindowCrop(crop);
+ }
+
+ @Override
+ public void setFinalCrop(Rect crop) {
+ super.setFinalCrop(crop);
+ mBackgroundControl.setFinalCrop(crop);
+ }
+
+ @Override
+ public void setLayerStack(int layerStack) {
+ super.setLayerStack(layerStack);
+ mBackgroundControl.setLayerStack(layerStack);
+ }
+
+ @Override
+ public void setOpaque(boolean isOpaque) {
+ super.setOpaque(isOpaque);
+ mOpaque = isOpaque;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void setSecure(boolean isSecure) {
+ super.setSecure(isSecure);
+ }
+
+ @Override
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ }
+
+ @Override
+ public void hide() {
+ super.hide();
+ mVisible = false;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ mVisible = true;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ mBackgroundControl.destroy();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ mBackgroundControl.release();
+ }
+
+ @Override
+ public void setTransparentRegionHint(Region region) {
+ super.setTransparentRegionHint(region);
+ mBackgroundControl.setTransparentRegionHint(region);
+ }
+
+ @Override
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ super.deferTransactionUntil(handle, frame);
+ mBackgroundControl.deferTransactionUntil(handle, frame);
+ }
+
+ @Override
+ public void deferTransactionUntil(Surface barrier, long frame) {
+ super.deferTransactionUntil(barrier, frame);
+ mBackgroundControl.deferTransactionUntil(barrier, frame);
+ }
+
+ void updateBackgroundVisibility() {
+ if (mOpaque && mVisible) {
+ mBackgroundControl.show();
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+ }
+}
diff --git a/android/view/TextureView.java b/android/view/TextureView.java
new file mode 100644
index 00000000..25dce998
--- /dev/null
+++ b/android/view/TextureView.java
@@ -0,0 +1,847 @@
+/*
+ * Copyright (C) 2011 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.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * <p>A TextureView can be used to display a content stream. Such a content
+ * stream can for instance be a video or an OpenGL scene. The content stream
+ * can come from the application's process as well as a remote process.</p>
+ *
+ * <p>TextureView can only be used in a hardware accelerated window. When
+ * rendered in software, TextureView will draw nothing.</p>
+ *
+ * <p>Unlike {@link SurfaceView}, TextureView does not create a separate
+ * window but behaves as a regular View. This key difference allows a
+ * TextureView to be moved, transformed, animated, etc. For instance, you
+ * can make a TextureView semi-translucent by calling
+ * <code>myView.setAlpha(0.5f)</code>.</p>
+ *
+ * <p>Using a TextureView is simple: all you need to do is get its
+ * {@link SurfaceTexture}. The {@link SurfaceTexture} can then be used to
+ * render content. The following example demonstrates how to render the
+ * camera preview into a TextureView:</p>
+ *
+ * <pre>
+ * public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
+ * private Camera mCamera;
+ * private TextureView mTextureView;
+ *
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ *
+ * mTextureView = new TextureView(this);
+ * mTextureView.setSurfaceTextureListener(this);
+ *
+ * setContentView(mTextureView);
+ * }
+ *
+ * public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ * mCamera = Camera.open();
+ *
+ * try {
+ * mCamera.setPreviewTexture(surface);
+ * mCamera.startPreview();
+ * } catch (IOException ioe) {
+ * // Something bad happened
+ * }
+ * }
+ *
+ * public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ * // Ignored, Camera does all the work for us
+ * }
+ *
+ * public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ * mCamera.stopPreview();
+ * mCamera.release();
+ * return true;
+ * }
+ *
+ * public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ * // Invoked every time there's a new Camera preview frame
+ * }
+ * }
+ * </pre>
+ *
+ * <p>A TextureView's SurfaceTexture can be obtained either by invoking
+ * {@link #getSurfaceTexture()} or by using a {@link SurfaceTextureListener}.
+ * It is important to know that a SurfaceTexture is available only after the
+ * TextureView is attached to a window (and {@link #onAttachedToWindow()} has
+ * been invoked.) It is therefore highly recommended you use a listener to
+ * be notified when the SurfaceTexture becomes available.</p>
+ *
+ * <p>It is important to note that only one producer can use the TextureView.
+ * For instance, if you use a TextureView to display the camera preview, you
+ * cannot use {@link #lockCanvas()} to draw onto the TextureView at the same
+ * time.</p>
+ *
+ * @see SurfaceView
+ * @see SurfaceTexture
+ */
+public class TextureView extends View {
+ private static final String LOG_TAG = "TextureView";
+
+ private HardwareLayer mLayer;
+ private SurfaceTexture mSurface;
+ private SurfaceTextureListener mListener;
+ private boolean mHadSurface;
+
+ private boolean mOpaque = true;
+
+ private final Matrix mMatrix = new Matrix();
+ private boolean mMatrixChanged;
+
+ private final Object[] mLock = new Object[0];
+ private boolean mUpdateLayer;
+ private boolean mUpdateSurface;
+
+ private Canvas mCanvas;
+ private int mSaveCount;
+
+ private final Object[] mNativeWindowLock = new Object[0];
+ // Set by native code, do not write!
+ private long mNativeWindow;
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ */
+ public TextureView(Context context) {
+ super(context);
+ }
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public TextureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ */
+ public TextureView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ /**
+ * Indicates whether the content of this TextureView is opaque. The
+ * content is assumed to be opaque by default.
+ *
+ * @param opaque True if the content of this TextureView is opaque,
+ * false otherwise
+ */
+ public void setOpaque(boolean opaque) {
+ if (opaque != mOpaque) {
+ mOpaque = opaque;
+ if (mLayer != null) {
+ updateLayerAndInvalidate();
+ }
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (!isHardwareAccelerated()) {
+ Log.w(LOG_TAG, "A TextureView or a subclass can only be "
+ + "used with hardware acceleration enabled.");
+ }
+
+ if (mHadSurface) {
+ invalidate(true);
+ mHadSurface = false;
+ }
+ }
+
+ /** @hide */
+ @Override
+ protected void onDetachedFromWindowInternal() {
+ destroyHardwareLayer();
+ releaseSurfaceTexture();
+ super.onDetachedFromWindowInternal();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void destroyHardwareResources() {
+ super.destroyHardwareResources();
+ destroyHardwareLayer();
+ }
+
+ private void destroyHardwareLayer() {
+ if (mLayer != null) {
+ mLayer.detachSurfaceTexture();
+ mLayer.destroy();
+ mLayer = null;
+ mMatrixChanged = true;
+ }
+ }
+
+ private void releaseSurfaceTexture() {
+ if (mSurface != null) {
+ boolean shouldRelease = true;
+
+ if (mListener != null) {
+ shouldRelease = mListener.onSurfaceTextureDestroyed(mSurface);
+ }
+
+ synchronized (mNativeWindowLock) {
+ nDestroyNativeWindow();
+ }
+
+ if (shouldRelease) {
+ mSurface.release();
+ }
+ mSurface = null;
+ mHadSurface = true;
+ }
+ }
+
+ /**
+ * The layer type of a TextureView is ignored since a TextureView is always
+ * considered to act as a hardware layer. The optional paint supplied to this
+ * method will however be taken into account when rendering the content of
+ * this TextureView.
+ *
+ * @param layerType The type of layer to use with this view, must be one of
+ * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+ * {@link #LAYER_TYPE_HARDWARE}
+ * @param paint The paint used to compose the layer. This argument is optional
+ * and can be null. It is ignored when the layer type is
+ * {@link #LAYER_TYPE_NONE}
+ */
+ @Override
+ public void setLayerType(int layerType, @Nullable Paint paint) {
+ setLayerPaint(paint);
+ }
+
+ @Override
+ public void setLayerPaint(@Nullable Paint paint) {
+ if (paint != mLayerPaint) {
+ mLayerPaint = paint;
+ invalidate();
+ }
+ }
+
+ /**
+ * Always returns {@link #LAYER_TYPE_HARDWARE}.
+ */
+ @Override
+ public int getLayerType() {
+ return LAYER_TYPE_HARDWARE;
+ }
+
+ /**
+ * Calling this method has no effect.
+ */
+ @Override
+ public void buildLayer() {
+ }
+
+ @Override
+ public void setForeground(Drawable foreground) {
+ if (foreground != null && !sTextureViewIgnoresDrawableSetters) {
+ throw new UnsupportedOperationException(
+ "TextureView doesn't support displaying a foreground drawable");
+ }
+ }
+
+ @Override
+ public void setBackgroundDrawable(Drawable background) {
+ if (background != null && !sTextureViewIgnoresDrawableSetters) {
+ throw new UnsupportedOperationException(
+ "TextureView doesn't support displaying a background drawable");
+ }
+ }
+
+ /**
+ * Subclasses of TextureView cannot do their own rendering
+ * with the {@link Canvas} object.
+ *
+ * @param canvas The Canvas to which the View is rendered.
+ */
+ @Override
+ public final void draw(Canvas canvas) {
+ // NOTE: Maintain this carefully (see View#draw)
+ mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
+
+ /* Simplify drawing to guarantee the layer is the only thing drawn - so e.g. no background,
+ scrolling, or fading edges. This guarantees all drawing is in the layer, so drawing
+ properties (alpha, layer paint) affect all of the content of a TextureView. */
+
+ if (canvas.isHardwareAccelerated()) {
+ DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+
+ HardwareLayer layer = getHardwareLayer();
+ if (layer != null) {
+ applyUpdate();
+ applyTransformMatrix();
+
+ mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
+ displayListCanvas.drawHardwareLayer(layer);
+ }
+ }
+ }
+
+ /**
+ * Subclasses of TextureView cannot do their own rendering
+ * with the {@link Canvas} object.
+ *
+ * @param canvas The Canvas to which the View is rendered.
+ */
+ @Override
+ protected final void onDraw(Canvas canvas) {
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mSurface != null) {
+ mSurface.setDefaultBufferSize(getWidth(), getHeight());
+ updateLayer();
+ if (mListener != null) {
+ mListener.onSurfaceTextureSizeChanged(mSurface, getWidth(), getHeight());
+ }
+ }
+ }
+
+ HardwareLayer getHardwareLayer() {
+ if (mLayer == null) {
+ if (mAttachInfo == null || mAttachInfo.mThreadedRenderer == null) {
+ return null;
+ }
+
+ mLayer = mAttachInfo.mThreadedRenderer.createTextureLayer();
+ boolean createNewSurface = (mSurface == null);
+ if (createNewSurface) {
+ // Create a new SurfaceTexture for the layer.
+ mSurface = new SurfaceTexture(false);
+ nCreateNativeWindow(mSurface);
+ }
+ mLayer.setSurfaceTexture(mSurface);
+ mSurface.setDefaultBufferSize(getWidth(), getHeight());
+ mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+
+ if (mListener != null && createNewSurface) {
+ mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
+ }
+ mLayer.setLayerPaint(mLayerPaint);
+ }
+
+ if (mUpdateSurface) {
+ // Someone has requested that we use a specific SurfaceTexture, so
+ // tell mLayer about it and set the SurfaceTexture to use the
+ // current view size.
+ mUpdateSurface = false;
+
+ // Since we are updating the layer, force an update to ensure its
+ // parameters are correct (width, height, transform, etc.)
+ updateLayer();
+ mMatrixChanged = true;
+
+ mLayer.setSurfaceTexture(mSurface);
+ mSurface.setDefaultBufferSize(getWidth(), getHeight());
+ }
+
+ return mLayer;
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (mSurface != null) {
+ // When the view becomes invisible, stop updating it, it's a waste of CPU
+ // To cancel updates, the easiest thing to do is simply to remove the
+ // updates listener
+ if (visibility == VISIBLE) {
+ if (mLayer != null) {
+ mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ }
+ updateLayerAndInvalidate();
+ } else {
+ mSurface.setOnFrameAvailableListener(null);
+ }
+ }
+ }
+
+ private void updateLayer() {
+ synchronized (mLock) {
+ mUpdateLayer = true;
+ }
+ }
+
+ private void updateLayerAndInvalidate() {
+ synchronized (mLock) {
+ mUpdateLayer = true;
+ }
+ invalidate();
+ }
+
+ private void applyUpdate() {
+ if (mLayer == null) {
+ return;
+ }
+
+ synchronized (mLock) {
+ if (mUpdateLayer) {
+ mUpdateLayer = false;
+ } else {
+ return;
+ }
+ }
+
+ mLayer.prepare(getWidth(), getHeight(), mOpaque);
+ mLayer.updateSurfaceTexture();
+
+ if (mListener != null) {
+ mListener.onSurfaceTextureUpdated(mSurface);
+ }
+ }
+
+ /**
+ * <p>Sets the transform to associate with this texture view.
+ * The specified transform applies to the underlying surface
+ * texture and does not affect the size or position of the view
+ * itself, only of its content.</p>
+ *
+ * <p>Some transforms might prevent the content from drawing
+ * all the pixels contained within this view's bounds. In such
+ * situations, make sure this texture view is not marked opaque.</p>
+ *
+ * @param transform The transform to apply to the content of
+ * this view.
+ *
+ * @see #getTransform(android.graphics.Matrix)
+ * @see #isOpaque()
+ * @see #setOpaque(boolean)
+ */
+ public void setTransform(Matrix transform) {
+ mMatrix.set(transform);
+ mMatrixChanged = true;
+ invalidateParentIfNeeded();
+ }
+
+ /**
+ * Returns the transform associated with this texture view.
+ *
+ * @param transform The {@link Matrix} in which to copy the current
+ * transform. Can be null.
+ *
+ * @return The specified matrix if not null or a new {@link Matrix}
+ * instance otherwise.
+ *
+ * @see #setTransform(android.graphics.Matrix)
+ */
+ public Matrix getTransform(Matrix transform) {
+ if (transform == null) {
+ transform = new Matrix();
+ }
+
+ transform.set(mMatrix);
+
+ return transform;
+ }
+
+ private void applyTransformMatrix() {
+ if (mMatrixChanged && mLayer != null) {
+ mLayer.setTransform(mMatrix);
+ mMatrixChanged = false;
+ }
+ }
+
+ /**
+ * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+ * of the associated surface texture. If the surface texture is not available,
+ * this method returns null.</p>
+ *
+ * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+ * pixel format and its dimensions are the same as this view's.</p>
+ *
+ * <p><strong>Do not</strong> invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+ *
+ * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
+ *
+ * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+ * texture is not available or the width &lt;= 0 or the height &lt;= 0
+ *
+ * @see #isAvailable()
+ * @see #getBitmap(android.graphics.Bitmap)
+ * @see #getBitmap(int, int)
+ */
+ public Bitmap getBitmap() {
+ return getBitmap(getWidth(), getHeight());
+ }
+
+ /**
+ * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+ * of the associated surface texture. If the surface texture is not available,
+ * this method returns null.</p>
+ *
+ * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+ * pixel format.</p>
+ *
+ * <p><strong>Do not</strong> invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+ *
+ * <p>If an error occurs during the copy, an empty bitmap will be returned.</p>
+ *
+ * @param width The width of the bitmap to create
+ * @param height The height of the bitmap to create
+ *
+ * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+ * texture is not available or width is &lt;= 0 or height is &lt;= 0
+ *
+ * @see #isAvailable()
+ * @see #getBitmap(android.graphics.Bitmap)
+ * @see #getBitmap()
+ */
+ public Bitmap getBitmap(int width, int height) {
+ if (isAvailable() && width > 0 && height > 0) {
+ return getBitmap(Bitmap.createBitmap(getResources().getDisplayMetrics(),
+ width, height, Bitmap.Config.ARGB_8888));
+ }
+ return null;
+ }
+
+ /**
+ * <p>Copies the content of this view's surface texture into the specified
+ * bitmap. If the surface texture is not available, the copy is not executed.
+ * The content of the surface texture will be scaled to fit exactly inside
+ * the specified bitmap.</p>
+ *
+ * <p><strong>Do not</strong> invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+ *
+ * <p>If an error occurs, the bitmap is left unchanged.</p>
+ *
+ * @param bitmap The bitmap to copy the content of the surface texture into,
+ * cannot be null, all configurations are supported
+ *
+ * @return The bitmap specified as a parameter
+ *
+ * @see #isAvailable()
+ * @see #getBitmap(int, int)
+ * @see #getBitmap()
+ *
+ * @throws IllegalStateException if the hardware rendering context cannot be
+ * acquired to capture the bitmap
+ */
+ public Bitmap getBitmap(Bitmap bitmap) {
+ if (bitmap != null && isAvailable()) {
+ applyUpdate();
+ applyTransformMatrix();
+
+ // This case can happen if the app invokes setSurfaceTexture() before
+ // we are able to create the hardware layer. We can safely initialize
+ // the layer here thanks to the validate() call at the beginning of
+ // this method
+ if (mLayer == null && mUpdateSurface) {
+ getHardwareLayer();
+ }
+
+ if (mLayer != null) {
+ mLayer.copyInto(bitmap);
+ }
+ }
+ return bitmap;
+ }
+
+ /**
+ * Returns true if the {@link SurfaceTexture} associated with this
+ * TextureView is available for rendering. When this method returns
+ * true, {@link #getSurfaceTexture()} returns a valid surface texture.
+ */
+ public boolean isAvailable() {
+ return mSurface != null;
+ }
+
+ /**
+ * <p>Start editing the pixels in the surface. The returned Canvas can be used
+ * to draw into the surface's bitmap. A null is returned if the surface has
+ * not been created or otherwise cannot be edited. You will usually need
+ * to implement
+ * {@link SurfaceTextureListener#onSurfaceTextureAvailable(android.graphics.SurfaceTexture, int, int)}
+ * to find out when the Surface is available for use.</p>
+ *
+ * <p>The content of the Surface is never preserved between unlockCanvas()
+ * and lockCanvas(), for this reason, every pixel within the Surface area
+ * must be written. The only exception to this rule is when a dirty
+ * rectangle is specified, in which case, non-dirty pixels will be
+ * preserved.</p>
+ *
+ * <p>This method can only be used if the underlying surface is not already
+ * owned by another producer. For instance, if the TextureView is being used
+ * to render the camera's preview you cannot invoke this method.</p>
+ *
+ * @return A Canvas used to draw into the surface.
+ *
+ * @see #lockCanvas(android.graphics.Rect)
+ * @see #unlockCanvasAndPost(android.graphics.Canvas)
+ */
+ public Canvas lockCanvas() {
+ return lockCanvas(null);
+ }
+
+ /**
+ * Just like {@link #lockCanvas()} but allows specification of a dirty
+ * rectangle. Every pixel within that rectangle must be written; however
+ * pixels outside the dirty rectangle will be preserved by the next call
+ * to lockCanvas().
+ *
+ * This method can return null if the underlying surface texture is not
+ * available (see {@link #isAvailable()} or if the surface texture is
+ * already connected to an image producer (for instance: the camera,
+ * OpenGL, a media player, etc.)
+ *
+ * @param dirty Area of the surface that will be modified.
+
+ * @return A Canvas used to draw into the surface.
+ *
+ * @see #lockCanvas()
+ * @see #unlockCanvasAndPost(android.graphics.Canvas)
+ * @see #isAvailable()
+ */
+ public Canvas lockCanvas(Rect dirty) {
+ if (!isAvailable()) return null;
+
+ if (mCanvas == null) {
+ mCanvas = new Canvas();
+ }
+
+ synchronized (mNativeWindowLock) {
+ if (!nLockCanvas(mNativeWindow, mCanvas, dirty)) {
+ return null;
+ }
+ }
+ mSaveCount = mCanvas.save();
+
+ return mCanvas;
+ }
+
+ /**
+ * Finish editing pixels in the surface. After this call, the surface's
+ * current pixels will be shown on the screen, but its content is lost,
+ * in particular there is no guarantee that the content of the Surface
+ * will remain unchanged when lockCanvas() is called again.
+ *
+ * @param canvas The Canvas previously returned by lockCanvas()
+ *
+ * @see #lockCanvas()
+ * @see #lockCanvas(android.graphics.Rect)
+ */
+ public void unlockCanvasAndPost(Canvas canvas) {
+ if (mCanvas != null && canvas == mCanvas) {
+ canvas.restoreToCount(mSaveCount);
+ mSaveCount = 0;
+
+ synchronized (mNativeWindowLock) {
+ nUnlockCanvasAndPost(mNativeWindow, mCanvas);
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link SurfaceTexture} used by this view. This method
+ * may return null if the view is not attached to a window or if the surface
+ * texture has not been initialized yet.
+ *
+ * @see #isAvailable()
+ */
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurface;
+ }
+
+ /**
+ * Set the {@link SurfaceTexture} for this view to use. If a {@link
+ * SurfaceTexture} is already being used by this view, it is immediately
+ * released and not usable any more. The {@link
+ * SurfaceTextureListener#onSurfaceTextureDestroyed} callback is <b>not</b>
+ * called for the previous {@link SurfaceTexture}. Similarly, the {@link
+ * SurfaceTextureListener#onSurfaceTextureAvailable} callback is <b>not</b>
+ * called for the {@link SurfaceTexture} passed to setSurfaceTexture.
+ *
+ * The {@link SurfaceTexture} object must be detached from all OpenGL ES
+ * contexts prior to calling this method.
+ *
+ * @param surfaceTexture The {@link SurfaceTexture} that the view should use.
+ * @see SurfaceTexture#detachFromGLContext()
+ */
+ public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
+ if (surfaceTexture == null) {
+ throw new NullPointerException("surfaceTexture must not be null");
+ }
+ if (surfaceTexture == mSurface) {
+ throw new IllegalArgumentException("Trying to setSurfaceTexture to " +
+ "the same SurfaceTexture that's already set.");
+ }
+ if (surfaceTexture.isReleased()) {
+ throw new IllegalArgumentException("Cannot setSurfaceTexture to a " +
+ "released SurfaceTexture");
+ }
+ if (mSurface != null) {
+ nDestroyNativeWindow();
+ mSurface.release();
+ }
+ mSurface = surfaceTexture;
+ nCreateNativeWindow(mSurface);
+
+ /*
+ * If the view is visible and we already made a layer, update the
+ * listener in the new surface to use the existing listener in the view.
+ * Otherwise this will be called when the view becomes visible or the
+ * layer is created
+ */
+ if (((mViewFlags & VISIBILITY_MASK) == VISIBLE) && mLayer != null) {
+ mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ }
+ mUpdateSurface = true;
+ invalidateParentIfNeeded();
+ }
+
+ /**
+ * Returns the {@link SurfaceTextureListener} currently associated with this
+ * texture view.
+ *
+ * @see #setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener)
+ * @see SurfaceTextureListener
+ */
+ public SurfaceTextureListener getSurfaceTextureListener() {
+ return mListener;
+ }
+
+ /**
+ * Sets the {@link SurfaceTextureListener} used to listen to surface
+ * texture events.
+ *
+ * @see #getSurfaceTextureListener()
+ * @see SurfaceTextureListener
+ */
+ public void setSurfaceTextureListener(SurfaceTextureListener listener) {
+ mListener = listener;
+ }
+
+ private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
+ new SurfaceTexture.OnFrameAvailableListener() {
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ updateLayer();
+ invalidate();
+ }
+ };
+
+ /**
+ * This listener can be used to be notified when the surface texture
+ * associated with this texture view is available.
+ */
+ public static interface SurfaceTextureListener {
+ /**
+ * Invoked when a {@link TextureView}'s SurfaceTexture is ready for use.
+ *
+ * @param surface The surface returned by
+ * {@link android.view.TextureView#getSurfaceTexture()}
+ * @param width The width of the surface
+ * @param height The height of the surface
+ */
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height);
+
+ /**
+ * Invoked when the {@link SurfaceTexture}'s buffers size changed.
+ *
+ * @param surface The surface returned by
+ * {@link android.view.TextureView#getSurfaceTexture()}
+ * @param width The new width of the surface
+ * @param height The new height of the surface
+ */
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height);
+
+ /**
+ * Invoked when the specified {@link SurfaceTexture} is about to be destroyed.
+ * If returns true, no rendering should happen inside the surface texture after this method
+ * is invoked. If returns false, the client needs to call {@link SurfaceTexture#release()}.
+ * Most applications should return true.
+ *
+ * @param surface The surface about to be destroyed
+ */
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface);
+
+ /**
+ * Invoked when the specified {@link SurfaceTexture} is updated through
+ * {@link SurfaceTexture#updateTexImage()}.
+ *
+ * @param surface The surface just updated
+ */
+ public void onSurfaceTextureUpdated(SurfaceTexture surface);
+ }
+
+ private native void nCreateNativeWindow(SurfaceTexture surface);
+ private native void nDestroyNativeWindow();
+
+ private static native boolean nLockCanvas(long nativeWindow, Canvas canvas, Rect dirty);
+ private static native void nUnlockCanvasAndPost(long nativeWindow, Canvas canvas);
+}
diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java
new file mode 100644
index 00000000..2166f6e4
--- /dev/null
+++ b/android/view/ThreadedRenderer.java
@@ -0,0 +1,1075 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.Log;
+import android.view.Surface.OutOfResourcesException;
+import android.view.View.AttachInfo;
+
+import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Threaded renderer that proxies the rendering to a render thread. Most calls
+ * are currently synchronous.
+ *
+ * The UI thread can block on the RenderThread, but RenderThread must never
+ * block on the UI thread.
+ *
+ * ThreadedRenderer creates an instance of RenderProxy. RenderProxy in turn creates
+ * and manages a CanvasContext on the RenderThread. The CanvasContext is fully managed
+ * by the lifecycle of the RenderProxy.
+ *
+ * Note that although currently the EGL context & surfaces are created & managed
+ * by the render thread, the goal is to move that into a shared structure that can
+ * be managed by both threads. EGLSurface creation & deletion should ideally be
+ * done on the UI thread and not the RenderThread to avoid stalling the
+ * RenderThread with surface buffer allocation.
+ *
+ * @hide
+ */
+public final class ThreadedRenderer {
+ private static final String LOG_TAG = "ThreadedRenderer";
+
+ /**
+ * Name of the file that holds the shaders cache.
+ */
+ private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
+
+ /**
+ * System property used to enable or disable threaded rendering profiling.
+ * The default value of this property is assumed to be false.
+ *
+ * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+ * output extra information about the time taken to execute by the last
+ * frames.
+ *
+ * Possible values:
+ * "true", to enable profiling
+ * "visual_bars", to enable profiling and visualize the results on screen
+ * "false", to disable profiling
+ *
+ * @see #PROFILE_PROPERTY_VISUALIZE_BARS
+ *
+ * @hide
+ */
+ public static final String PROFILE_PROPERTY = "debug.hwui.profile";
+
+ /**
+ * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
+ * value, profiling data will be visualized on screen as a bar chart.
+ *
+ * @hide
+ */
+ public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars";
+
+ /**
+ * System property used to specify the number of frames to be used
+ * when doing threaded rendering profiling.
+ * The default value of this property is #PROFILE_MAX_FRAMES.
+ *
+ * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+ * output extra information about the time taken to execute by the last
+ * frames.
+ *
+ * Possible values:
+ * "60", to set the limit of frames to 60
+ */
+ static final String PROFILE_MAXFRAMES_PROPERTY = "debug.hwui.profile.maxframes";
+
+ /**
+ * System property used to debug EGL configuration choice.
+ *
+ * Possible values:
+ * "choice", print the chosen configuration only
+ * "all", print all possible configurations
+ */
+ static final String PRINT_CONFIG_PROPERTY = "debug.hwui.print_config";
+
+ /**
+ * Turn on to draw dirty regions every other frame.
+ *
+ * Possible values:
+ * "true", to enable dirty regions debugging
+ * "false", to disable dirty regions debugging
+ *
+ * @hide
+ */
+ public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions";
+
+ /**
+ * Turn on to flash hardware layers when they update.
+ *
+ * Possible values:
+ * "true", to enable hardware layers updates debugging
+ * "false", to disable hardware layers updates debugging
+ *
+ * @hide
+ */
+ public static final String DEBUG_SHOW_LAYERS_UPDATES_PROPERTY =
+ "debug.hwui.show_layers_updates";
+
+ /**
+ * Controls overdraw debugging.
+ *
+ * Possible values:
+ * "false", to disable overdraw debugging
+ * "show", to show overdraw areas on screen
+ * "count", to display an overdraw counter
+ *
+ * @hide
+ */
+ public static final String DEBUG_OVERDRAW_PROPERTY = "debug.hwui.overdraw";
+
+ /**
+ * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this
+ * value, overdraw will be shown on screen by coloring pixels.
+ *
+ * @hide
+ */
+ public static final String OVERDRAW_PROPERTY_SHOW = "show";
+
+ /**
+ * Defines the rendering pipeline to be used by the ThreadedRenderer.
+ *
+ * Possible values:
+ * "opengl", will use the existing OpenGL renderer
+ * "skiagl", will use Skia's OpenGL renderer
+ * "skiavk", will use Skia's Vulkan renderer
+ *
+ * @hide
+ */
+ public static final String DEBUG_RENDERER_PROPERTY = "debug.hwui.renderer";
+
+ /**
+ * Turn on to debug non-rectangular clip operations.
+ *
+ * Possible values:
+ * "hide", to disable this debug mode
+ * "highlight", highlight drawing commands tested against a non-rectangular clip
+ * "stencil", renders the clip region on screen when set
+ *
+ * @hide
+ */
+ public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY =
+ "debug.hwui.show_non_rect_clip";
+
+ static {
+ // Try to check OpenGL support early if possible.
+ isAvailable();
+ }
+
+ /**
+ * A process can set this flag to false to prevent the use of threaded
+ * rendering.
+ *
+ * @hide
+ */
+ public static boolean sRendererDisabled = false;
+
+ /**
+ * Further threaded renderer disabling for the system process.
+ *
+ * @hide
+ */
+ public static boolean sSystemRendererDisabled = false;
+
+ /**
+ * Invoke this method to disable threaded rendering in the current process.
+ *
+ * @hide
+ */
+ public static void disable(boolean system) {
+ sRendererDisabled = true;
+ if (system) {
+ sSystemRendererDisabled = true;
+ }
+ }
+
+ public static boolean sTrimForeground = false;
+
+ /**
+ * Controls whether or not the renderer should aggressively trim
+ * memory. Note that this must not be set for any process that uses
+ * WebView! This should be only used by system_process or similar
+ * that do not go into the background.
+ */
+ public static void enableForegroundTrimming() {
+ sTrimForeground = true;
+ }
+
+ private static Boolean sSupportsOpenGL;
+
+ /**
+ * Indicates whether threaded rendering is available under any form for
+ * the view hierarchy.
+ *
+ * @return True if the view hierarchy can potentially be defer rendered,
+ * false otherwise
+ */
+ public static boolean isAvailable() {
+ if (sSupportsOpenGL != null) {
+ return sSupportsOpenGL.booleanValue();
+ }
+ if (SystemProperties.getInt("ro.kernel.qemu", 0) == 0) {
+ // Device is not an emulator.
+ sSupportsOpenGL = true;
+ return true;
+ }
+ int qemu_gles = SystemProperties.getInt("qemu.gles", -1);
+ if (qemu_gles == -1) {
+ // In this case, the value of the qemu.gles property is not ready
+ // because the SurfaceFlinger service may not start at this point.
+ return false;
+ }
+ // In the emulator this property will be set > 0 when OpenGL ES 2.0 is
+ // enabled, 0 otherwise. On old emulator versions it will be undefined.
+ sSupportsOpenGL = qemu_gles > 0;
+ return sSupportsOpenGL.booleanValue();
+ }
+
+ /**
+ * Sets the directory to use as a persistent storage for threaded rendering
+ * resources.
+ *
+ * @param cacheDir A directory the current process can write to
+ *
+ * @hide
+ */
+ public static void setupDiskCache(File cacheDir) {
+ ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ }
+
+ /**
+ * Creates a threaded renderer using OpenGL.
+ *
+ * @param translucent True if the surface is translucent, false otherwise
+ *
+ * @return A threaded renderer backed by OpenGL.
+ */
+ public static ThreadedRenderer create(Context context, boolean translucent, String name) {
+ ThreadedRenderer renderer = null;
+ if (isAvailable()) {
+ renderer = new ThreadedRenderer(context, translucent, name);
+ }
+ return renderer;
+ }
+
+ /**
+ * Invoke this method when the system is running out of memory. This
+ * method will attempt to recover as much memory as possible, based on
+ * the specified hint.
+ *
+ * @param level Hint about the amount of memory that should be trimmed,
+ * see {@link android.content.ComponentCallbacks}
+ */
+ public static void trimMemory(int level) {
+ nTrimMemory(level);
+ }
+
+ public static void overrideProperty(@NonNull String name, @NonNull String value) {
+ if (name == null || value == null) {
+ throw new IllegalArgumentException("name and value must be non-null");
+ }
+ nOverrideProperty(name, value);
+ }
+
+ // Keep in sync with DrawFrameTask.h SYNC_* flags
+ // Nothing interesting to report
+ private static final int SYNC_OK = 0;
+ // Needs a ViewRoot invalidate
+ private static final int SYNC_INVALIDATE_REQUIRED = 1 << 0;
+ // Spoiler: the reward is GPU-accelerated drawing, better find that Surface!
+ private static final int SYNC_LOST_SURFACE_REWARD_IF_FOUND = 1 << 1;
+ // setStopped is true, drawing is false
+ // TODO: Remove this and SYNC_LOST_SURFACE_REWARD_IF_FOUND?
+ // This flag isn't really used as there's nothing that we care to do
+ // in response, so it really just exists to differentiate from LOST_SURFACE
+ // but possibly both can just be deleted.
+ private static final int SYNC_CONTEXT_IS_STOPPED = 1 << 2;
+
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ };
+
+ private static final int FLAG_DUMP_FRAMESTATS = 1 << 0;
+ private static final int FLAG_DUMP_RESET = 1 << 1;
+
+ @IntDef(flag = true, value = {
+ FLAG_DUMP_FRAMESTATS, FLAG_DUMP_RESET })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DumpFlags {}
+
+ // Size of the rendered content.
+ private int mWidth, mHeight;
+
+ // Actual size of the drawing surface.
+ private int mSurfaceWidth, mSurfaceHeight;
+
+ // Insets between the drawing surface and rendered content. These are
+ // applied as translation when updating the root render node.
+ private int mInsetTop, mInsetLeft;
+
+ // Whether the surface has insets. Used to protect opacity.
+ private boolean mHasInsets;
+
+ // Light and shadow properties specified by the theme.
+ private final float mLightY;
+ private final float mLightZ;
+ private final float mLightRadius;
+ private final int mAmbientShadowAlpha;
+ private final int mSpotShadowAlpha;
+
+ private long mNativeProxy;
+ private boolean mInitialized = false;
+ private RenderNode mRootNode;
+ private boolean mRootNodeNeedsUpdate;
+
+ private boolean mEnabled;
+ private boolean mRequested = true;
+ private boolean mIsOpaque = false;
+
+ ThreadedRenderer(Context context, boolean translucent, String name) {
+ final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
+ mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
+ mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
+ mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
+ mAmbientShadowAlpha =
+ (int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
+ mSpotShadowAlpha = (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
+ a.recycle();
+
+ long rootNodePtr = nCreateRootRenderNode();
+ mRootNode = RenderNode.adopt(rootNodePtr);
+ mRootNode.setClipToBounds(false);
+ mIsOpaque = !translucent;
+ mNativeProxy = nCreateProxy(translucent, rootNodePtr);
+ nSetName(mNativeProxy, name);
+
+ ProcessInitializer.sInstance.init(context, mNativeProxy);
+
+ loadSystemProperties();
+ }
+
+ /**
+ * Destroys the threaded rendering context.
+ */
+ void destroy() {
+ mInitialized = false;
+ updateEnabledState(null);
+ nDestroy(mNativeProxy, mRootNode.mNativeRenderNode);
+ }
+
+ /**
+ * Indicates whether threaded rendering is currently enabled.
+ *
+ * @return True if threaded rendering is in use, false otherwise.
+ */
+ boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Indicates whether threaded rendering is currently enabled.
+ *
+ * @param enabled True if the threaded renderer is in use, false otherwise.
+ */
+ void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ /**
+ * Indicates whether threaded rendering is currently request but not
+ * necessarily enabled yet.
+ *
+ * @return True if requested, false otherwise.
+ */
+ boolean isRequested() {
+ return mRequested;
+ }
+
+ /**
+ * Indicates whether threaded rendering is currently requested but not
+ * necessarily enabled yet.
+ */
+ void setRequested(boolean requested) {
+ mRequested = requested;
+ }
+
+ private void updateEnabledState(Surface surface) {
+ if (surface == null || !surface.isValid()) {
+ setEnabled(false);
+ } else {
+ setEnabled(mInitialized);
+ }
+ }
+
+ /**
+ * Initializes the threaded renderer for the specified surface.
+ *
+ * @param surface The surface to render
+ *
+ * @return True if the initialization was successful, false otherwise.
+ */
+ boolean initialize(Surface surface) throws OutOfResourcesException {
+ boolean status = !mInitialized;
+ mInitialized = true;
+ updateEnabledState(surface);
+ nInitialize(mNativeProxy, surface);
+ return status;
+ }
+
+ /**
+ * Initializes the threaded renderer for the specified surface and setup the
+ * renderer for drawing, if needed. This is invoked when the ViewAncestor has
+ * potentially lost the threaded renderer. The threaded renderer should be
+ * reinitialized and setup when the render {@link #isRequested()} and
+ * {@link #isEnabled()}.
+ *
+ * @param width The width of the drawing surface.
+ * @param height The height of the drawing surface.
+ * @param attachInfo Information about the window.
+ * @param surface The surface to render
+ * @param surfaceInsets The drawing surface insets to apply
+ *
+ * @return true if the surface was initialized, false otherwise. Returning
+ * false might mean that the surface was already initialized.
+ */
+ boolean initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
+ Surface surface, Rect surfaceInsets) throws OutOfResourcesException {
+ if (isRequested()) {
+ // We lost the gl context, so recreate it.
+ if (!isEnabled()) {
+ if (initialize(surface)) {
+ setup(width, height, attachInfo, surfaceInsets);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Updates the threaded renderer for the specified surface.
+ *
+ * @param surface The surface to render
+ */
+ void updateSurface(Surface surface) throws OutOfResourcesException {
+ updateEnabledState(surface);
+ nUpdateSurface(mNativeProxy, surface);
+ }
+
+ /**
+ * Halts any current rendering into the surface. Use this if it is unclear whether
+ * or not the surface used by the ThreadedRenderer will be changing. It
+ * Suspends any rendering into the surface, but will not do any destruction.
+ *
+ * Any subsequent draws will override the pause, resuming normal operation.
+ */
+ boolean pauseSurface(Surface surface) {
+ return nPauseSurface(mNativeProxy, surface);
+ }
+
+ /**
+ * Hard stops or resumes rendering into the surface. This flag is used to
+ * determine whether or not it is safe to use the given surface *at all*
+ */
+ void setStopped(boolean stopped) {
+ nSetStopped(mNativeProxy, stopped);
+ }
+
+ /**
+ * Destroys all hardware rendering resources associated with the specified
+ * view hierarchy.
+ *
+ * @param view The root of the view hierarchy
+ */
+ void destroyHardwareResources(View view) {
+ destroyResources(view);
+ nDestroyHardwareResources(mNativeProxy);
+ }
+
+ private static void destroyResources(View view) {
+ view.destroyHardwareResources();
+ }
+
+ /**
+ * Detaches the layer's surface texture from the GL context and releases
+ * the texture id
+ */
+ void detachSurfaceTexture(long hardwareLayer) {
+ nDetachSurfaceTexture(mNativeProxy, hardwareLayer);
+ }
+
+ /**
+ * Sets up the renderer for drawing.
+ *
+ * @param width The width of the drawing surface.
+ * @param height The height of the drawing surface.
+ * @param attachInfo Information about the window.
+ * @param surfaceInsets The drawing surface insets to apply
+ */
+ void setup(int width, int height, AttachInfo attachInfo, Rect surfaceInsets) {
+ mWidth = width;
+ mHeight = height;
+
+ if (surfaceInsets != null && (surfaceInsets.left != 0 || surfaceInsets.right != 0
+ || surfaceInsets.top != 0 || surfaceInsets.bottom != 0)) {
+ mHasInsets = true;
+ mInsetLeft = surfaceInsets.left;
+ mInsetTop = surfaceInsets.top;
+ mSurfaceWidth = width + mInsetLeft + surfaceInsets.right;
+ mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom;
+
+ // If the surface has insets, it can't be opaque.
+ setOpaque(false);
+ } else {
+ mHasInsets = false;
+ mInsetLeft = 0;
+ mInsetTop = 0;
+ mSurfaceWidth = width;
+ mSurfaceHeight = height;
+ }
+
+ mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
+ nSetup(mNativeProxy, mLightRadius,
+ mAmbientShadowAlpha, mSpotShadowAlpha);
+
+ setLightCenter(attachInfo);
+ }
+
+ /**
+ * Updates the light position based on the position of the window.
+ *
+ * @param attachInfo Information about the window.
+ */
+ void setLightCenter(AttachInfo attachInfo) {
+ // Adjust light position for window offsets.
+ final Point displaySize = attachInfo.mPoint;
+ attachInfo.mDisplay.getRealSize(displaySize);
+ final float lightX = displaySize.x / 2f - attachInfo.mWindowLeft;
+ final float lightY = mLightY - attachInfo.mWindowTop;
+
+ nSetLightCenter(mNativeProxy, lightX, lightY, mLightZ);
+ }
+
+ /**
+ * Change the ThreadedRenderer's opacity
+ */
+ void setOpaque(boolean opaque) {
+ mIsOpaque = opaque && !mHasInsets;
+ nSetOpaque(mNativeProxy, mIsOpaque);
+ }
+
+ boolean isOpaque() {
+ return mIsOpaque;
+ }
+
+ /**
+ * Enable/disable wide gamut rendering on this renderer.
+ */
+ void setWideGamut(boolean wideGamut) {
+ nSetWideGamut(mNativeProxy, wideGamut);
+ }
+
+ /**
+ * Gets the current width of the surface. This is the width that the surface
+ * was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
+ *
+ * @return the current width of the surface
+ */
+ int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Gets the current height of the surface. This is the height that the surface
+ * was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
+ *
+ * @return the current width of the surface
+ */
+ int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Outputs extra debugging information in the specified file descriptor.
+ */
+ void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args) {
+ pw.flush();
+ int flags = 0;
+ for (int i = 0; i < args.length; i++) {
+ switch (args[i]) {
+ case "framestats":
+ flags |= FLAG_DUMP_FRAMESTATS;
+ break;
+ case "reset":
+ flags |= FLAG_DUMP_RESET;
+ break;
+ }
+ }
+ nDumpProfileInfo(mNativeProxy, fd, flags);
+ }
+
+ /**
+ * Loads system properties used by the renderer. This method is invoked
+ * whenever system properties are modified. Implementations can use this
+ * to trigger live updates of the renderer based on properties.
+ *
+ * @return True if a property has changed.
+ */
+ boolean loadSystemProperties() {
+ boolean changed = nLoadSystemProperties(mNativeProxy);
+ if (changed) {
+ invalidateRoot();
+ }
+ return changed;
+ }
+
+ private void updateViewTreeDisplayList(View view) {
+ view.mPrivateFlags |= View.PFLAG_DRAWN;
+ view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+ == View.PFLAG_INVALIDATED;
+ view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+ view.updateDisplayListIfDirty();
+ view.mRecreateDisplayList = false;
+ }
+
+ private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
+ updateViewTreeDisplayList(view);
+
+ if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
+ DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
+ try {
+ final int saveCount = canvas.save();
+ canvas.translate(mInsetLeft, mInsetTop);
+ callbacks.onPreDraw(canvas);
+
+ canvas.insertReorderBarrier();
+ canvas.drawRenderNode(view.updateDisplayListIfDirty());
+ canvas.insertInorderBarrier();
+
+ callbacks.onPostDraw(canvas);
+ canvas.restoreToCount(saveCount);
+ mRootNodeNeedsUpdate = false;
+ } finally {
+ mRootNode.end(canvas);
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ /**
+ * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the
+ * rendernode of the UI thread.
+ * @param node The node to add.
+ * @param placeFront If true, the render node will be placed in front of the content node,
+ * otherwise behind the content node.
+ */
+ public void addRenderNode(RenderNode node, boolean placeFront) {
+ nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront);
+ }
+
+ /**
+ * Only especially added render nodes can be removed.
+ * @param node The node which was added via addRenderNode which should get removed again.
+ */
+ public void removeRenderNode(RenderNode node) {
+ nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * Draws a particular render node. If the node is not the content node, only the additional
+ * nodes will get drawn and the content remains untouched.
+ * @param node The node to be drawn.
+ */
+ public void drawRenderNode(RenderNode node) {
+ nDrawRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * To avoid unnecessary overdrawing of the main content all additionally passed render nodes
+ * will be prevented to overdraw this area. It will be synchronized with the draw call.
+ * This should be updated in the content view's draw call.
+ * @param left The left side of the protected bounds.
+ * @param top The top side of the protected bounds.
+ * @param right The right side of the protected bounds.
+ * @param bottom The bottom side of the protected bounds.
+ */
+ public void setContentDrawBounds(int left, int top, int right, int bottom) {
+ nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
+ }
+
+ /**
+ * Interface used to receive callbacks whenever a view is drawn by
+ * a threaded renderer instance.
+ */
+ interface DrawCallbacks {
+ /**
+ * Invoked before a view is drawn by a threaded renderer.
+ * This method can be used to apply transformations to the
+ * canvas but no drawing command should be issued.
+ *
+ * @param canvas The Canvas used to render the view.
+ */
+ void onPreDraw(DisplayListCanvas canvas);
+
+ /**
+ * Invoked after a view is drawn by a threaded renderer.
+ * It is safe to invoke drawing commands from this method.
+ *
+ * @param canvas The Canvas used to render the view.
+ */
+ void onPostDraw(DisplayListCanvas canvas);
+ }
+
+ /**
+ * Indicates that the content drawn by DrawCallbacks needs to
+ * be updated, which will be done by the next call to draw()
+ */
+ void invalidateRoot() {
+ mRootNodeNeedsUpdate = true;
+ }
+
+ /**
+ * Draws the specified view.
+ *
+ * @param view The view to draw.
+ * @param attachInfo AttachInfo tied to the specified view.
+ * @param callbacks Callbacks invoked when drawing happens.
+ */
+ void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
+ attachInfo.mIgnoreDirtyState = true;
+
+ final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
+ choreographer.mFrameInfo.markDrawStart();
+
+ updateRootDisplayList(view, callbacks);
+
+ attachInfo.mIgnoreDirtyState = false;
+
+ // register animating rendernodes which started animating prior to renderer
+ // creation, which is typical for animators started prior to first draw
+ if (attachInfo.mPendingAnimatingRenderNodes != null) {
+ final int count = attachInfo.mPendingAnimatingRenderNodes.size();
+ for (int i = 0; i < count; i++) {
+ registerAnimatingRenderNode(
+ attachInfo.mPendingAnimatingRenderNodes.get(i));
+ }
+ attachInfo.mPendingAnimatingRenderNodes.clear();
+ // We don't need this anymore as subsequent calls to
+ // ViewRootImpl#attachRenderNodeAnimator will go directly to us.
+ attachInfo.mPendingAnimatingRenderNodes = null;
+ }
+
+ final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
+ int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
+ if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
+ setEnabled(false);
+ attachInfo.mViewRootImpl.mSurface.release();
+ // Invalidate since we failed to draw. This should fetch a Surface
+ // if it is still needed or do nothing if we are no longer drawing
+ attachInfo.mViewRootImpl.invalidate();
+ }
+ if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
+ attachInfo.mViewRootImpl.invalidate();
+ }
+ }
+
+ static void invokeFunctor(long functor, boolean waitForCompletion) {
+ nInvokeFunctor(functor, waitForCompletion);
+ }
+
+ /**
+ * Creates a new hardware layer. A hardware layer built by calling this
+ * method will be treated as a texture layer, instead of as a render target.
+ *
+ * @return A hardware layer
+ */
+ HardwareLayer createTextureLayer() {
+ long layer = nCreateTextureLayer(mNativeProxy);
+ return HardwareLayer.adoptTextureLayer(this, layer);
+ }
+
+
+ void buildLayer(RenderNode node) {
+ nBuildLayer(mNativeProxy, node.getNativeDisplayList());
+ }
+
+
+ boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) {
+ return nCopyLayerInto(mNativeProxy,
+ layer.getDeferredLayerUpdater(), bitmap);
+ }
+
+ /**
+ * Indicates that the specified hardware layer needs to be updated
+ * as soon as possible.
+ *
+ * @param layer The hardware layer that needs an update
+ */
+ void pushLayerUpdate(HardwareLayer layer) {
+ nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
+ }
+
+ /**
+ * Tells the HardwareRenderer that the layer is destroyed. The renderer
+ * should remove the layer from any update queues.
+ */
+ void onLayerDestroyed(HardwareLayer layer) {
+ nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
+ }
+
+ /**
+ * Blocks until all previously queued work has completed.
+ */
+ void fence() {
+ nFence(mNativeProxy);
+ }
+
+ /**
+ * Prevents any further drawing until draw() is called. This is a signal
+ * that the contents of the RenderNode tree are no longer safe to play back.
+ * In practice this usually means that there are Functor pointers in the
+ * display list that are no longer valid.
+ */
+ void stopDrawing() {
+ nStopDrawing(mNativeProxy);
+ }
+
+ /**
+ * Called by {@link ViewRootImpl} when a new performTraverals is scheduled.
+ */
+ public void notifyFramePending() {
+ nNotifyFramePending(mNativeProxy);
+ }
+
+
+ void registerAnimatingRenderNode(RenderNode animator) {
+ nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode);
+ }
+
+ void registerVectorDrawableAnimator(
+ AnimatedVectorDrawable.VectorDrawableAnimatorRT animator) {
+ nRegisterVectorDrawableAnimator(mRootNode.mNativeRenderNode,
+ animator.getAnimatorNativePtr());
+ }
+
+ public void serializeDisplayListTree() {
+ nSerializeDisplayListTree(mNativeProxy);
+ }
+
+ public static int copySurfaceInto(Surface surface, Rect srcRect, Bitmap bitmap) {
+ if (srcRect == null) {
+ // Empty rect means entire surface
+ return nCopySurfaceInto(surface, 0, 0, 0, 0, bitmap);
+ } else {
+ return nCopySurfaceInto(surface, srcRect.left, srcRect.top,
+ srcRect.right, srcRect.bottom, bitmap);
+ }
+ }
+
+ /**
+ * Creates a {@link android.graphics.Bitmap.Config#HARDWARE} bitmap from the given
+ * RenderNode. Note that the RenderNode should be created as a root node (so x/y of 0,0), and
+ * not the RenderNode from a View.
+ **/
+ public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) {
+ return nCreateHardwareBitmap(node.getNativeDisplayList(), width, height);
+ }
+
+ /**
+ * Sets whether or not high contrast text rendering is enabled. The setting is global
+ * but only affects content rendered after the change is made.
+ */
+ public static void setHighContrastText(boolean highContrastText) {
+ nSetHighContrastText(highContrastText);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nDeleteProxy(mNativeProxy);
+ mNativeProxy = 0;
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static class ProcessInitializer {
+ static ProcessInitializer sInstance = new ProcessInitializer();
+
+ private boolean mInitialized = false;
+
+ private Context mAppContext;
+ private IGraphicsStats mGraphicsStatsService;
+ private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
+ @Override
+ public void onRotateGraphicsStatsBuffer() throws RemoteException {
+ rotateBuffer();
+ }
+ };
+
+ private ProcessInitializer() {}
+
+ synchronized void init(Context context, long renderProxy) {
+ if (mInitialized) return;
+ mInitialized = true;
+ mAppContext = context.getApplicationContext();
+ initSched(renderProxy);
+ initGraphicsStats();
+ }
+
+ private void initSched(long renderProxy) {
+ try {
+ int tid = nGetRenderThreadTid(renderProxy);
+ ActivityManager.getService().setRenderThread(tid);
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Failed to set scheduler for RenderThread", t);
+ }
+ }
+
+ private void initGraphicsStats() {
+ try {
+ IBinder binder = ServiceManager.getService("graphicsstats");
+ if (binder == null) return;
+ mGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder);
+ requestBuffer();
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
+ }
+ }
+
+ private void rotateBuffer() {
+ nRotateProcessStatsBuffer();
+ requestBuffer();
+ }
+
+ private void requestBuffer() {
+ try {
+ final String pkg = mAppContext.getApplicationInfo().packageName;
+ ParcelFileDescriptor pfd = mGraphicsStatsService
+ .requestBufferForProcess(pkg, mGraphicsStatsCallback);
+ nSetProcessStatsBuffer(pfd.getFd());
+ pfd.close();
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
+ }
+ }
+ }
+
+ void addFrameMetricsObserver(FrameMetricsObserver observer) {
+ long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
+ observer.mNative = new VirtualRefBasePtr(nativeObserver);
+ }
+
+ void removeFrameMetricsObserver(FrameMetricsObserver observer) {
+ nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
+ observer.mNative = null;
+ }
+
+ /** Not actually public - internal use only. This doc to make lint happy */
+ public static native void disableVsync();
+
+ static native void setupShadersDiskCache(String cacheFile);
+
+ private static native void nRotateProcessStatsBuffer();
+ private static native void nSetProcessStatsBuffer(int fd);
+ private static native int nGetRenderThreadTid(long nativeProxy);
+
+ private static native long nCreateRootRenderNode();
+ private static native long nCreateProxy(boolean translucent, long rootRenderNode);
+ private static native void nDeleteProxy(long nativeProxy);
+
+ private static native boolean nLoadSystemProperties(long nativeProxy);
+ private static native void nSetName(long nativeProxy, String name);
+
+ private static native void nInitialize(long nativeProxy, Surface window);
+ private static native void nUpdateSurface(long nativeProxy, Surface window);
+ private static native boolean nPauseSurface(long nativeProxy, Surface window);
+ private static native void nSetStopped(long nativeProxy, boolean stopped);
+ private static native void nSetup(long nativeProxy,
+ float lightRadius, int ambientShadowAlpha, int spotShadowAlpha);
+ private static native void nSetLightCenter(long nativeProxy,
+ float lightX, float lightY, float lightZ);
+ private static native void nSetOpaque(long nativeProxy, boolean opaque);
+ private static native void nSetWideGamut(long nativeProxy, boolean wideGamut);
+ private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
+ private static native void nDestroy(long nativeProxy, long rootRenderNode);
+ private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
+ private static native void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator);
+
+ private static native void nInvokeFunctor(long functor, boolean waitForCompletion);
+
+ private static native long nCreateTextureLayer(long nativeProxy);
+ private static native void nBuildLayer(long nativeProxy, long node);
+ private static native boolean nCopyLayerInto(long nativeProxy, long layer, Bitmap bitmap);
+ private static native void nPushLayerUpdate(long nativeProxy, long layer);
+ private static native void nCancelLayerUpdate(long nativeProxy, long layer);
+ private static native void nDetachSurfaceTexture(long nativeProxy, long layer);
+
+ private static native void nDestroyHardwareResources(long nativeProxy);
+ private static native void nTrimMemory(int level);
+ private static native void nOverrideProperty(String name, String value);
+
+ private static native void nFence(long nativeProxy);
+ private static native void nStopDrawing(long nativeProxy);
+ private static native void nNotifyFramePending(long nativeProxy);
+
+ private static native void nSerializeDisplayListTree(long nativeProxy);
+
+ private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
+ @DumpFlags int dumpFlags);
+
+ private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
+ boolean placeFront);
+ private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode);
+ private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
+ private static native void nSetContentDrawBounds(long nativeProxy, int left,
+ int top, int right, int bottom);
+
+ private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer);
+ private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
+
+ private static native int nCopySurfaceInto(Surface surface,
+ int srcLeft, int srcTop, int srcRight, int srcBottom, Bitmap bitmap);
+
+ private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height);
+ private static native void nSetHighContrastText(boolean enabled);
+}
diff --git a/android/view/TouchDelegate.java b/android/view/TouchDelegate.java
new file mode 100644
index 00000000..cf36f436
--- /dev/null
+++ b/android/view/TouchDelegate.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2008 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;
+
+/**
+ * Helper class to handle situations where you want a view to have a larger touch area than its
+ * actual view bounds. The view whose touch area is changed is called the delegate view. This
+ * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
+ * instance that specifies the bounds that should be mapped to the delegate and the delegate
+ * view itself.
+ * <p>
+ * The ancestor should then forward all of its touch events received in its
+ * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+public class TouchDelegate {
+
+ /**
+ * View that should receive forwarded touch events
+ */
+ private View mDelegateView;
+
+ /**
+ * Bounds in local coordinates of the containing view that should be mapped to the delegate
+ * view. This rect is used for initial hit testing.
+ */
+ private Rect mBounds;
+
+ /**
+ * mBounds inflated to include some slop. This rect is to track whether the motion events
+ * should be considered to be be within the delegate view.
+ */
+ private Rect mSlopBounds;
+
+ /**
+ * True if the delegate had been targeted on a down event (intersected mBounds).
+ */
+ private boolean mDelegateTargeted;
+
+ /**
+ * The touchable region of the View extends above its actual extent.
+ */
+ public static final int ABOVE = 1;
+
+ /**
+ * The touchable region of the View extends below its actual extent.
+ */
+ public static final int BELOW = 2;
+
+ /**
+ * The touchable region of the View extends to the left of its
+ * actual extent.
+ */
+ public static final int TO_LEFT = 4;
+
+ /**
+ * The touchable region of the View extends to the right of its
+ * actual extent.
+ */
+ public static final int TO_RIGHT = 8;
+
+ private int mSlop;
+
+ /**
+ * Constructor
+ *
+ * @param bounds Bounds in local coordinates of the containing view that should be mapped to
+ * the delegate view
+ * @param delegateView The view that should receive motion events
+ */
+ public TouchDelegate(Rect bounds, View delegateView) {
+ mBounds = bounds;
+
+ mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
+ mSlopBounds = new Rect(bounds);
+ mSlopBounds.inset(-mSlop, -mSlop);
+ mDelegateView = delegateView;
+ }
+
+ /**
+ * Will forward touch events to the delegate view if the event is within the bounds
+ * specified in the constructor.
+ *
+ * @param event The touch event to forward
+ * @return True if the event was forwarded to the delegate, false otherwise.
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ boolean sendToDelegate = false;
+ boolean hit = true;
+ boolean handled = false;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ Rect bounds = mBounds;
+
+ if (bounds.contains(x, y)) {
+ mDelegateTargeted = true;
+ sendToDelegate = true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_MOVE:
+ sendToDelegate = mDelegateTargeted;
+ if (sendToDelegate) {
+ Rect slopBounds = mSlopBounds;
+ if (!slopBounds.contains(x, y)) {
+ hit = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ sendToDelegate = mDelegateTargeted;
+ mDelegateTargeted = false;
+ break;
+ }
+ if (sendToDelegate) {
+ final View delegateView = mDelegateView;
+
+ if (hit) {
+ // Offset event coordinates to be inside the target view
+ event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
+ } else {
+ // Offset event coordinates to be outside the target view (in case it does
+ // something like tracking pressed state)
+ int slop = mSlop;
+ event.setLocation(-(slop * 2), -(slop * 2));
+ }
+ handled = delegateView.dispatchTouchEvent(event);
+ }
+ return handled;
+ }
+}
diff --git a/android/view/VelocityTracker.java b/android/view/VelocityTracker.java
new file mode 100644
index 00000000..22b5ccac
--- /dev/null
+++ b/android/view/VelocityTracker.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2006 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.util.Pools.SynchronizedPool;
+
+/**
+ * Helper for tracking the velocity of touch events, for implementing
+ * flinging and other such gestures.
+ *
+ * Use {@link #obtain} to retrieve a new instance of the class when you are going
+ * to begin tracking. Put the motion events you receive into it with
+ * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call
+ * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
+ * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
+ */
+public final class VelocityTracker {
+ private static final SynchronizedPool<VelocityTracker> sPool =
+ new SynchronizedPool<VelocityTracker>(2);
+
+ private static final int ACTIVE_POINTER_ID = -1;
+
+ private long mPtr;
+ private final String mStrategy;
+
+ private static native long nativeInitialize(String strategy);
+ private static native void nativeDispose(long ptr);
+ private static native void nativeClear(long ptr);
+ private static native void nativeAddMovement(long ptr, MotionEvent event);
+ private static native void nativeComputeCurrentVelocity(long ptr, int units, float maxVelocity);
+ private static native float nativeGetXVelocity(long ptr, int id);
+ private static native float nativeGetYVelocity(long ptr, int id);
+ private static native boolean nativeGetEstimator(long ptr, int id, Estimator outEstimator);
+
+ /**
+ * Retrieve a new VelocityTracker object to watch the velocity of a
+ * motion. Be sure to call {@link #recycle} when done. You should
+ * generally only maintain an active object while tracking a movement,
+ * so that the VelocityTracker can be re-used elsewhere.
+ *
+ * @return Returns a new VelocityTracker.
+ */
+ static public VelocityTracker obtain() {
+ VelocityTracker instance = sPool.acquire();
+ return (instance != null) ? instance : new VelocityTracker(null);
+ }
+
+ /**
+ * Obtains a velocity tracker with the specified strategy.
+ * For testing and comparison purposes only.
+ *
+ * @param strategy The strategy, or null to use the default.
+ * @return The velocity tracker.
+ *
+ * @hide
+ */
+ public static VelocityTracker obtain(String strategy) {
+ if (strategy == null) {
+ return obtain();
+ }
+ return new VelocityTracker(strategy);
+ }
+
+ /**
+ * Return a VelocityTracker object back to be re-used by others. You must
+ * not touch the object after calling this function.
+ */
+ public void recycle() {
+ if (mStrategy == null) {
+ clear();
+ sPool.release(this);
+ }
+ }
+
+ private VelocityTracker(String strategy) {
+ mPtr = nativeInitialize(strategy);
+ mStrategy = strategy;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mPtr != 0) {
+ nativeDispose(mPtr);
+ mPtr = 0;
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Reset the velocity tracker back to its initial state.
+ */
+ public void clear() {
+ nativeClear(mPtr);
+ }
+
+ /**
+ * Add a user's movement to the tracker. You should call this for the
+ * initial {@link MotionEvent#ACTION_DOWN}, the following
+ * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
+ * final {@link MotionEvent#ACTION_UP}. You can, however, call this
+ * for whichever events you desire.
+ *
+ * @param event The MotionEvent you received and would like to track.
+ */
+ public void addMovement(MotionEvent event) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ nativeAddMovement(mPtr, event);
+ }
+
+ /**
+ * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
+ * velocity of Float.MAX_VALUE.
+ *
+ * @see #computeCurrentVelocity(int, float)
+ */
+ public void computeCurrentVelocity(int units) {
+ nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
+ }
+
+ /**
+ * Compute the current velocity based on the points that have been
+ * collected. Only call this when you actually want to retrieve velocity
+ * information, as it is relatively expensive. You can then retrieve
+ * the velocity with {@link #getXVelocity()} and
+ * {@link #getYVelocity()}.
+ *
+ * @param units The units you would like the velocity in. A value of 1
+ * provides pixels per millisecond, 1000 provides pixels per second, etc.
+ * @param maxVelocity The maximum velocity that can be computed by this method.
+ * This value must be declared in the same unit as the units parameter. This value
+ * must be positive.
+ */
+ public void computeCurrentVelocity(int units, float maxVelocity) {
+ nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
+ }
+
+ /**
+ * Retrieve the last computed X velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @return The previously computed X velocity.
+ */
+ public float getXVelocity() {
+ return nativeGetXVelocity(mPtr, ACTIVE_POINTER_ID);
+ }
+
+ /**
+ * Retrieve the last computed Y velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @return The previously computed Y velocity.
+ */
+ public float getYVelocity() {
+ return nativeGetYVelocity(mPtr, ACTIVE_POINTER_ID);
+ }
+
+ /**
+ * Retrieve the last computed X velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param id Which pointer's velocity to return.
+ * @return The previously computed X velocity.
+ */
+ public float getXVelocity(int id) {
+ return nativeGetXVelocity(mPtr, id);
+ }
+
+ /**
+ * Retrieve the last computed Y velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param id Which pointer's velocity to return.
+ * @return The previously computed Y velocity.
+ */
+ public float getYVelocity(int id) {
+ return nativeGetYVelocity(mPtr, id);
+ }
+
+ /**
+ * Get an estimator for the movements of a pointer using past movements of the
+ * pointer to predict future movements.
+ *
+ * It is not necessary to call {@link #computeCurrentVelocity(int)} before calling
+ * this method.
+ *
+ * @param id Which pointer's velocity to return.
+ * @param outEstimator The estimator to populate.
+ * @return True if an estimator was obtained, false if there is no information
+ * available about the pointer.
+ *
+ * @hide For internal use only. Not a final API.
+ */
+ public boolean getEstimator(int id, Estimator outEstimator) {
+ if (outEstimator == null) {
+ throw new IllegalArgumentException("outEstimator must not be null");
+ }
+ return nativeGetEstimator(mPtr, id, outEstimator);
+ }
+
+ /**
+ * An estimator for the movements of a pointer based on a polynomial model.
+ *
+ * The last recorded position of the pointer is at time zero seconds.
+ * Past estimated positions are at negative times and future estimated positions
+ * are at positive times.
+ *
+ * First coefficient is position (in pixels), second is velocity (in pixels per second),
+ * third is acceleration (in pixels per second squared).
+ *
+ * @hide For internal use only. Not a final API.
+ */
+ public static final class Estimator {
+ // Must match VelocityTracker::Estimator::MAX_DEGREE
+ private static final int MAX_DEGREE = 4;
+
+ /**
+ * Polynomial coefficients describing motion in X.
+ */
+ public final float[] xCoeff = new float[MAX_DEGREE + 1];
+
+ /**
+ * Polynomial coefficients describing motion in Y.
+ */
+ public final float[] yCoeff = new float[MAX_DEGREE + 1];
+
+ /**
+ * Polynomial degree, or zero if only position information is available.
+ */
+ public int degree;
+
+ /**
+ * Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
+ */
+ public float confidence;
+
+ /**
+ * Gets an estimate of the X position of the pointer at the specified time point.
+ * @param time The time point in seconds, 0 is the last recorded time.
+ * @return The estimated X coordinate.
+ */
+ public float estimateX(float time) {
+ return estimate(time, xCoeff);
+ }
+
+ /**
+ * Gets an estimate of the Y position of the pointer at the specified time point.
+ * @param time The time point in seconds, 0 is the last recorded time.
+ * @return The estimated Y coordinate.
+ */
+ public float estimateY(float time) {
+ return estimate(time, yCoeff);
+ }
+
+ /**
+ * Gets the X coefficient with the specified index.
+ * @param index The index of the coefficient to return.
+ * @return The X coefficient, or 0 if the index is greater than the degree.
+ */
+ public float getXCoeff(int index) {
+ return index <= degree ? xCoeff[index] : 0;
+ }
+
+ /**
+ * Gets the Y coefficient with the specified index.
+ * @param index The index of the coefficient to return.
+ * @return The Y coefficient, or 0 if the index is greater than the degree.
+ */
+ public float getYCoeff(int index) {
+ return index <= degree ? yCoeff[index] : 0;
+ }
+
+ private float estimate(float time, float[] c) {
+ float a = 0;
+ float scale = 1;
+ for (int i = 0; i <= degree; i++) {
+ a += c[i] * scale;
+ scale *= time;
+ }
+ return a;
+ }
+ }
+}
diff --git a/android/view/View.java b/android/view/View.java
new file mode 100644
index 00000000..e5bd5ac0
--- /dev/null
+++ b/android/view/View.java
@@ -0,0 +1,26480 @@
+/*
+ * Copyright (C) 2006 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 static java.lang.Math.max;
+
+import android.animation.AnimatorInflater;
+import android.animation.StateListAnimator;
+import android.annotation.CallSuper;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.FloatRange;
+import android.annotation.IdRes;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.TestApi;
+import android.annotation.UiThread;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Interpolator;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.LayoutDirection;
+import android.util.Log;
+import android.util.LongSparseLongArray;
+import android.util.Pools.SynchronizedPool;
+import android.util.Property;
+import android.util.SparseArray;
+import android.util.StateSet;
+import android.util.SuperNotCalledException;
+import android.util.TypedValue;
+import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
+import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
+import android.view.AccessibilityIterators.TextSegmentIterator;
+import android.view.AccessibilityIterators.WordTextSegmentIterator;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityEventSource;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Checkable;
+import android.widget.FrameLayout;
+import android.widget.ScrollBarDrawable;
+
+import com.android.internal.R;
+import com.android.internal.view.TooltipPopup;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.ScrollBarUtils;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+
+/**
+ * <p>
+ * This class represents the basic building block for user interface components. A View
+ * occupies a rectangular area on the screen and is responsible for drawing and
+ * event handling. View is the base class for <em>widgets</em>, which are
+ * used to create interactive UI components (buttons, text fields, etc.). The
+ * {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which
+ * are invisible containers that hold other Views (or other ViewGroups) and define
+ * their layout properties.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about using this class to develop your application's user interface,
+ * read the <a href="{@docRoot}guide/topics/ui/index.html">User Interface</a> developer guide.
+ * </div>
+ *
+ * <a name="Using"></a>
+ * <h3>Using Views</h3>
+ * <p>
+ * All of the views in a window are arranged in a single tree. You can add views
+ * either from code or by specifying a tree of views in one or more XML layout
+ * files. There are many specialized subclasses of views that act as controls or
+ * are capable of displaying text, images, or other content.
+ * </p>
+ * <p>
+ * Once you have created a tree of views, there are typically a few types of
+ * common operations you may wish to perform:
+ * <ul>
+ * <li><strong>Set properties:</strong> for example setting the text of a
+ * {@link android.widget.TextView}. The available properties and the methods
+ * that set them will vary among the different subclasses of views. Note that
+ * properties that are known at build time can be set in the XML layout
+ * files.</li>
+ * <li><strong>Set focus:</strong> The framework will handle moving focus in
+ * response to user input. To force focus to a specific view, call
+ * {@link #requestFocus}.</li>
+ * <li><strong>Set up listeners:</strong> Views allow clients to set listeners
+ * that will be notified when something interesting happens to the view. For
+ * example, all views will let you set a listener to be notified when the view
+ * gains or loses focus. You can register such a listener using
+ * {@link #setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}.
+ * Other view subclasses offer more specialized listeners. For example, a Button
+ * exposes a listener to notify clients when the button is clicked.</li>
+ * <li><strong>Set visibility:</strong> You can hide or show views using
+ * {@link #setVisibility(int)}.</li>
+ * </ul>
+ * </p>
+ * <p><em>
+ * Note: The Android framework is responsible for measuring, laying out and
+ * drawing views. You should not call methods that perform these actions on
+ * views yourself unless you are actually implementing a
+ * {@link android.view.ViewGroup}.
+ * </em></p>
+ *
+ * <a name="Lifecycle"></a>
+ * <h3>Implementing a Custom View</h3>
+ *
+ * <p>
+ * To implement a custom view, you will usually begin by providing overrides for
+ * some of the standard methods that the framework calls on all views. You do
+ * not need to override all of these methods. In fact, you can start by just
+ * overriding {@link #onDraw(android.graphics.Canvas)}.
+ * <table border="2" width="85%" align="center" cellpadding="5">
+ * <thead>
+ * <tr><th>Category</th> <th>Methods</th> <th>Description</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr>
+ * <td rowspan="2">Creation</td>
+ * <td>Constructors</td>
+ * <td>There is a form of the constructor that are called when the view
+ * is created from code and a form that is called when the view is
+ * inflated from a layout file. The second form should parse and apply
+ * any attributes defined in the layout file.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td><code>{@link #onFinishInflate()}</code></td>
+ * <td>Called after a view and all of its children has been inflated
+ * from XML.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td rowspan="3">Layout</td>
+ * <td><code>{@link #onMeasure(int, int)}</code></td>
+ * <td>Called to determine the size requirements for this view and all
+ * of its children.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td><code>{@link #onLayout(boolean, int, int, int, int)}</code></td>
+ * <td>Called when this view should assign a size and position to all
+ * of its children.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td><code>{@link #onSizeChanged(int, int, int, int)}</code></td>
+ * <td>Called when the size of this view has changed.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Drawing</td>
+ * <td><code>{@link #onDraw(android.graphics.Canvas)}</code></td>
+ * <td>Called when the view should render its content.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td rowspan="4">Event processing</td>
+ * <td><code>{@link #onKeyDown(int, KeyEvent)}</code></td>
+ * <td>Called when a new hardware key event occurs.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td><code>{@link #onKeyUp(int, KeyEvent)}</code></td>
+ * <td>Called when a hardware key up event occurs.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td><code>{@link #onTrackballEvent(MotionEvent)}</code></td>
+ * <td>Called when a trackball motion event occurs.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td><code>{@link #onTouchEvent(MotionEvent)}</code></td>
+ * <td>Called when a touch screen motion event occurs.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td rowspan="2">Focus</td>
+ * <td><code>{@link #onFocusChanged(boolean, int, android.graphics.Rect)}</code></td>
+ * <td>Called when the view gains or loses focus.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td><code>{@link #onWindowFocusChanged(boolean)}</code></td>
+ * <td>Called when the window containing the view gains or loses focus.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td rowspan="3">Attaching</td>
+ * <td><code>{@link #onAttachedToWindow()}</code></td>
+ * <td>Called when the view is attached to a window.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td><code>{@link #onDetachedFromWindow}</code></td>
+ * <td>Called when the view is detached from its window.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td><code>{@link #onWindowVisibilityChanged(int)}</code></td>
+ * <td>Called when the visibility of the window containing the view
+ * has changed.
+ * </td>
+ * </tr>
+ * </tbody>
+ *
+ * </table>
+ * </p>
+ *
+ * <a name="IDs"></a>
+ * <h3>IDs</h3>
+ * Views may have an integer id associated with them. These ids are typically
+ * assigned in the layout XML files, and are used to find specific views within
+ * the view tree. A common pattern is to:
+ * <ul>
+ * <li>Define a Button in the layout file and assign it a unique ID.
+ * <pre>
+ * &lt;Button
+ * android:id="@+id/my_button"
+ * android:layout_width="wrap_content"
+ * android:layout_height="wrap_content"
+ * android:text="@string/my_button_text"/&gt;
+ * </pre></li>
+ * <li>From the onCreate method of an Activity, find the Button
+ * <pre class="prettyprint">
+ * Button myButton = findViewById(R.id.my_button);
+ * </pre></li>
+ * </ul>
+ * <p>
+ * View IDs need not be unique throughout the tree, but it is good practice to
+ * ensure that they are at least unique within the part of the tree you are
+ * searching.
+ * </p>
+ *
+ * <a name="Position"></a>
+ * <h3>Position</h3>
+ * <p>
+ * The geometry of a view is that of a rectangle. A view has a location,
+ * expressed as a pair of <em>left</em> and <em>top</em> coordinates, and
+ * two dimensions, expressed as a width and a height. The unit for location
+ * and dimensions is the pixel.
+ * </p>
+ *
+ * <p>
+ * It is possible to retrieve the location of a view by invoking the methods
+ * {@link #getLeft()} and {@link #getTop()}. The former returns the left, or X,
+ * coordinate of the rectangle representing the view. The latter returns the
+ * top, or Y, coordinate of the rectangle representing the view. These methods
+ * both return the location of the view relative to its parent. For instance,
+ * when getLeft() returns 20, that means the view is located 20 pixels to the
+ * right of the left edge of its direct parent.
+ * </p>
+ *
+ * <p>
+ * In addition, several convenience methods are offered to avoid unnecessary
+ * computations, namely {@link #getRight()} and {@link #getBottom()}.
+ * These methods return the coordinates of the right and bottom edges of the
+ * rectangle representing the view. For instance, calling {@link #getRight()}
+ * is similar to the following computation: <code>getLeft() + getWidth()</code>
+ * (see <a href="#SizePaddingMargins">Size</a> for more information about the width.)
+ * </p>
+ *
+ * <a name="SizePaddingMargins"></a>
+ * <h3>Size, padding and margins</h3>
+ * <p>
+ * The size of a view is expressed with a width and a height. A view actually
+ * possess two pairs of width and height values.
+ * </p>
+ *
+ * <p>
+ * The first pair is known as <em>measured width</em> and
+ * <em>measured height</em>. These dimensions define how big a view wants to be
+ * within its parent (see <a href="#Layout">Layout</a> for more details.) The
+ * measured dimensions can be obtained by calling {@link #getMeasuredWidth()}
+ * and {@link #getMeasuredHeight()}.
+ * </p>
+ *
+ * <p>
+ * The second pair is simply known as <em>width</em> and <em>height</em>, or
+ * sometimes <em>drawing width</em> and <em>drawing height</em>. These
+ * dimensions define the actual size of the view on screen, at drawing time and
+ * after layout. These values may, but do not have to, be different from the
+ * measured width and height. The width and height can be obtained by calling
+ * {@link #getWidth()} and {@link #getHeight()}.
+ * </p>
+ *
+ * <p>
+ * To measure its dimensions, a view takes into account its padding. The padding
+ * is expressed in pixels for the left, top, right and bottom parts of the view.
+ * Padding can be used to offset the content of the view by a specific amount of
+ * pixels. For instance, a left padding of 2 will push the view's content by
+ * 2 pixels to the right of the left edge. Padding can be set using the
+ * {@link #setPadding(int, int, int, int)} or {@link #setPaddingRelative(int, int, int, int)}
+ * method and queried by calling {@link #getPaddingLeft()}, {@link #getPaddingTop()},
+ * {@link #getPaddingRight()}, {@link #getPaddingBottom()}, {@link #getPaddingStart()},
+ * {@link #getPaddingEnd()}.
+ * </p>
+ *
+ * <p>
+ * Even though a view can define a padding, it does not provide any support for
+ * margins. However, view groups provide such a support. Refer to
+ * {@link android.view.ViewGroup} and
+ * {@link android.view.ViewGroup.MarginLayoutParams} for further information.
+ * </p>
+ *
+ * <a name="Layout"></a>
+ * <h3>Layout</h3>
+ * <p>
+ * Layout is a two pass process: a measure pass and a layout pass. The measuring
+ * pass is implemented in {@link #measure(int, int)} and is a top-down traversal
+ * of the view tree. Each view pushes dimension specifications down the tree
+ * during the recursion. At the end of the measure pass, every view has stored
+ * its measurements. The second pass happens in
+ * {@link #layout(int,int,int,int)} and is also top-down. During
+ * this pass each parent is responsible for positioning all of its children
+ * using the sizes computed in the measure pass.
+ * </p>
+ *
+ * <p>
+ * When a view's measure() method returns, its {@link #getMeasuredWidth()} and
+ * {@link #getMeasuredHeight()} values must be set, along with those for all of
+ * that view's descendants. A view's measured width and measured height values
+ * must respect the constraints imposed by the view's parents. This guarantees
+ * that at the end of the measure pass, all parents accept all of their
+ * children's measurements. A parent view may call measure() more than once on
+ * its children. For example, the parent may measure each child once with
+ * unspecified dimensions to find out how big they want to be, then call
+ * measure() on them again with actual numbers if the sum of all the children's
+ * unconstrained sizes is too big or too small.
+ * </p>
+ *
+ * <p>
+ * The measure pass uses two classes to communicate dimensions. The
+ * {@link MeasureSpec} class is used by views to tell their parents how they
+ * want to be measured and positioned. The base LayoutParams class just
+ * describes how big the view wants to be for both width and height. For each
+ * dimension, it can specify one of:
+ * <ul>
+ * <li> an exact number
+ * <li>MATCH_PARENT, which means the view wants to be as big as its parent
+ * (minus padding)
+ * <li> WRAP_CONTENT, which means that the view wants to be just big enough to
+ * enclose its content (plus padding).
+ * </ul>
+ * There are subclasses of LayoutParams for different subclasses of ViewGroup.
+ * For example, AbsoluteLayout has its own subclass of LayoutParams which adds
+ * an X and Y value.
+ * </p>
+ *
+ * <p>
+ * MeasureSpecs are used to push requirements down the tree from parent to
+ * child. A MeasureSpec can be in one of three modes:
+ * <ul>
+ * <li>UNSPECIFIED: This is used by a parent to determine the desired dimension
+ * of a child view. For example, a LinearLayout may call measure() on its child
+ * with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how
+ * tall the child view wants to be given a width of 240 pixels.
+ * <li>EXACTLY: This is used by the parent to impose an exact size on the
+ * child. The child must use this size, and guarantee that all of its
+ * descendants will fit within this size.
+ * <li>AT_MOST: This is used by the parent to impose a maximum size on the
+ * child. The child must guarantee that it and all of its descendants will fit
+ * within this size.
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * To initiate a layout, call {@link #requestLayout}. This method is typically
+ * called by a view on itself when it believes that is can no longer fit within
+ * its current bounds.
+ * </p>
+ *
+ * <a name="Drawing"></a>
+ * <h3>Drawing</h3>
+ * <p>
+ * Drawing is handled by walking the tree and recording the drawing commands of
+ * any View that needs to update. After this, the drawing commands of the
+ * entire tree are issued to screen, clipped to the newly damaged area.
+ * </p>
+ *
+ * <p>
+ * The tree is largely recorded and drawn in order, with parents drawn before
+ * (i.e., behind) their children, with siblings drawn in the order they appear
+ * in the tree. If you set a background drawable for a View, then the View will
+ * draw it before calling back to its <code>onDraw()</code> method. The child
+ * drawing order can be overridden with
+ * {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean) custom child drawing order}
+ * in a ViewGroup, and with {@link #setZ(float)} custom Z values} set on Views.
+ * </p>
+ *
+ * <p>
+ * To force a view to draw, call {@link #invalidate()}.
+ * </p>
+ *
+ * <a name="EventHandlingThreading"></a>
+ * <h3>Event Handling and Threading</h3>
+ * <p>
+ * The basic cycle of a view is as follows:
+ * <ol>
+ * <li>An event comes in and is dispatched to the appropriate view. The view
+ * handles the event and notifies any listeners.</li>
+ * <li>If in the course of processing the event, the view's bounds may need
+ * to be changed, the view will call {@link #requestLayout()}.</li>
+ * <li>Similarly, if in the course of processing the event the view's appearance
+ * may need to be changed, the view will call {@link #invalidate()}.</li>
+ * <li>If either {@link #requestLayout()} or {@link #invalidate()} were called,
+ * the framework will take care of measuring, laying out, and drawing the tree
+ * as appropriate.</li>
+ * </ol>
+ * </p>
+ *
+ * <p><em>Note: The entire view tree is single threaded. You must always be on
+ * the UI thread when calling any method on any view.</em>
+ * If you are doing work on other threads and want to update the state of a view
+ * from that thread, you should use a {@link Handler}.
+ * </p>
+ *
+ * <a name="FocusHandling"></a>
+ * <h3>Focus Handling</h3>
+ * <p>
+ * The framework will handle routine focus movement in response to user input.
+ * This includes changing the focus as views are removed or hidden, or as new
+ * views become available. Views indicate their willingness to take focus
+ * through the {@link #isFocusable} method. To change whether a view can take
+ * focus, call {@link #setFocusable(boolean)}. When in touch mode (see notes below)
+ * views indicate whether they still would like focus via {@link #isFocusableInTouchMode}
+ * and can change this via {@link #setFocusableInTouchMode(boolean)}.
+ * </p>
+ * <p>
+ * Focus movement is based on an algorithm which finds the nearest neighbor in a
+ * given direction. In rare cases, the default algorithm may not match the
+ * intended behavior of the developer. In these situations, you can provide
+ * explicit overrides by using these XML attributes in the layout file:
+ * <pre>
+ * nextFocusDown
+ * nextFocusLeft
+ * nextFocusRight
+ * nextFocusUp
+ * </pre>
+ * </p>
+ *
+ *
+ * <p>
+ * To get a particular view to take focus, call {@link #requestFocus()}.
+ * </p>
+ *
+ * <a name="TouchMode"></a>
+ * <h3>Touch Mode</h3>
+ * <p>
+ * When a user is navigating a user interface via directional keys such as a D-pad, it is
+ * necessary to give focus to actionable items such as buttons so the user can see
+ * what will take input. If the device has touch capabilities, however, and the user
+ * begins interacting with the interface by touching it, it is no longer necessary to
+ * always highlight, or give focus to, a particular view. This motivates a mode
+ * for interaction named 'touch mode'.
+ * </p>
+ * <p>
+ * For a touch capable device, once the user touches the screen, the device
+ * will enter touch mode. From this point onward, only views for which
+ * {@link #isFocusableInTouchMode} is true will be focusable, such as text editing widgets.
+ * Other views that are touchable, like buttons, will not take focus when touched; they will
+ * only fire the on click listeners.
+ * </p>
+ * <p>
+ * Any time a user hits a directional key, such as a D-pad direction, the view device will
+ * exit touch mode, and find a view to take focus, so that the user may resume interacting
+ * with the user interface without touching the screen again.
+ * </p>
+ * <p>
+ * The touch mode state is maintained across {@link android.app.Activity}s. Call
+ * {@link #isInTouchMode} to see whether the device is currently in touch mode.
+ * </p>
+ *
+ * <a name="Scrolling"></a>
+ * <h3>Scrolling</h3>
+ * <p>
+ * The framework provides basic support for views that wish to internally
+ * scroll their content. This includes keeping track of the X and Y scroll
+ * offset as well as mechanisms for drawing scrollbars. See
+ * {@link #scrollBy(int, int)}, {@link #scrollTo(int, int)}, and
+ * {@link #awakenScrollBars()} for more details.
+ * </p>
+ *
+ * <a name="Tags"></a>
+ * <h3>Tags</h3>
+ * <p>
+ * Unlike IDs, tags are not used to identify views. Tags are essentially an
+ * extra piece of information that can be associated with a view. They are most
+ * often used as a convenience to store data related to views in the views
+ * themselves rather than by putting them in a separate structure.
+ * </p>
+ * <p>
+ * Tags may be specified with character sequence values in layout XML as either
+ * a single tag using the {@link android.R.styleable#View_tag android:tag}
+ * attribute or multiple tags using the {@code <tag>} child element:
+ * <pre>
+ * &lt;View ...
+ * android:tag="@string/mytag_value" /&gt;
+ * &lt;View ...&gt;
+ * &lt;tag android:id="@+id/mytag"
+ * android:value="@string/mytag_value" /&gt;
+ * &lt;/View>
+ * </pre>
+ * </p>
+ * <p>
+ * Tags may also be specified with arbitrary objects from code using
+ * {@link #setTag(Object)} or {@link #setTag(int, Object)}.
+ * </p>
+ *
+ * <a name="Themes"></a>
+ * <h3>Themes</h3>
+ * <p>
+ * By default, Views are created using the theme of the Context object supplied
+ * to their constructor; however, a different theme may be specified by using
+ * the {@link android.R.styleable#View_theme android:theme} attribute in layout
+ * XML or by passing a {@link ContextThemeWrapper} to the constructor from
+ * code.
+ * </p>
+ * <p>
+ * When the {@link android.R.styleable#View_theme android:theme} attribute is
+ * used in XML, the specified theme is applied on top of the inflation
+ * context's theme (see {@link LayoutInflater}) and used for the view itself as
+ * well as any child elements.
+ * </p>
+ * <p>
+ * In the following example, both views will be created using the Material dark
+ * color scheme; however, because an overlay theme is used which only defines a
+ * subset of attributes, the value of
+ * {@link android.R.styleable#Theme_colorAccent android:colorAccent} defined on
+ * the inflation context's theme (e.g. the Activity theme) will be preserved.
+ * <pre>
+ * &lt;LinearLayout
+ * ...
+ * android:theme="@android:theme/ThemeOverlay.Material.Dark"&gt;
+ * &lt;View ...&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ * </p>
+ *
+ * <a name="Properties"></a>
+ * <h3>Properties</h3>
+ * <p>
+ * The View class exposes an {@link #ALPHA} property, as well as several transform-related
+ * properties, such as {@link #TRANSLATION_X} and {@link #TRANSLATION_Y}. These properties are
+ * available both in the {@link Property} form as well as in similarly-named setter/getter
+ * methods (such as {@link #setAlpha(float)} for {@link #ALPHA}). These properties can
+ * be used to set persistent state associated with these rendering-related properties on the view.
+ * The properties and methods can also be used in conjunction with
+ * {@link android.animation.Animator Animator}-based animations, described more in the
+ * <a href="#Animation">Animation</a> section.
+ * </p>
+ *
+ * <a name="Animation"></a>
+ * <h3>Animation</h3>
+ * <p>
+ * Starting with Android 3.0, the preferred way of animating views is to use the
+ * {@link android.animation} package APIs. These {@link android.animation.Animator Animator}-based
+ * classes change actual properties of the View object, such as {@link #setAlpha(float) alpha} and
+ * {@link #setTranslationX(float) translationX}. This behavior is contrasted to that of the pre-3.0
+ * {@link android.view.animation.Animation Animation}-based classes, which instead animate only
+ * how the view is drawn on the display. In particular, the {@link ViewPropertyAnimator} class
+ * makes animating these View properties particularly easy and efficient.
+ * </p>
+ * <p>
+ * Alternatively, you can use the pre-3.0 animation classes to animate how Views are rendered.
+ * You can attach an {@link Animation} object to a view using
+ * {@link #setAnimation(Animation)} or
+ * {@link #startAnimation(Animation)}. The animation can alter the scale,
+ * rotation, translation and alpha of a view over time. If the animation is
+ * attached to a view that has children, the animation will affect the entire
+ * subtree rooted by that node. When an animation is started, the framework will
+ * take care of redrawing the appropriate views until the animation completes.
+ * </p>
+ *
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ * <p>
+ * Sometimes it is essential that an application be able to verify that an action
+ * is being performed with the full knowledge and consent of the user, such as
+ * granting a permission request, making a purchase or clicking on an advertisement.
+ * Unfortunately, a malicious application could try to spoof the user into
+ * performing these actions, unaware, by concealing the intended purpose of the view.
+ * As a remedy, the framework offers a touch filtering mechanism that can be used to
+ * improve the security of views that provide access to sensitive functionality.
+ * </p><p>
+ * To enable touch filtering, call {@link #setFilterTouchesWhenObscured(boolean)} or set the
+ * android:filterTouchesWhenObscured layout attribute to true. When enabled, the framework
+ * will discard touches that are received whenever the view's window is obscured by
+ * another visible window. As a result, the view will not receive touches whenever a
+ * toast, dialog or other window appears above the view's window.
+ * </p><p>
+ * For more fine-grained control over security, consider overriding the
+ * {@link #onFilterTouchEventForSecurity(MotionEvent)} method to implement your own
+ * security policy. See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
+ * </p>
+ *
+ * @attr ref android.R.styleable#View_alpha
+ * @attr ref android.R.styleable#View_background
+ * @attr ref android.R.styleable#View_clickable
+ * @attr ref android.R.styleable#View_contentDescription
+ * @attr ref android.R.styleable#View_drawingCacheQuality
+ * @attr ref android.R.styleable#View_duplicateParentState
+ * @attr ref android.R.styleable#View_id
+ * @attr ref android.R.styleable#View_requiresFadingEdge
+ * @attr ref android.R.styleable#View_fadeScrollbars
+ * @attr ref android.R.styleable#View_fadingEdgeLength
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ * @attr ref android.R.styleable#View_fitsSystemWindows
+ * @attr ref android.R.styleable#View_isScrollContainer
+ * @attr ref android.R.styleable#View_focusable
+ * @attr ref android.R.styleable#View_focusableInTouchMode
+ * @attr ref android.R.styleable#View_focusedByDefault
+ * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+ * @attr ref android.R.styleable#View_keepScreenOn
+ * @attr ref android.R.styleable#View_keyboardNavigationCluster
+ * @attr ref android.R.styleable#View_layerType
+ * @attr ref android.R.styleable#View_layoutDirection
+ * @attr ref android.R.styleable#View_longClickable
+ * @attr ref android.R.styleable#View_minHeight
+ * @attr ref android.R.styleable#View_minWidth
+ * @attr ref android.R.styleable#View_nextClusterForward
+ * @attr ref android.R.styleable#View_nextFocusDown
+ * @attr ref android.R.styleable#View_nextFocusLeft
+ * @attr ref android.R.styleable#View_nextFocusRight
+ * @attr ref android.R.styleable#View_nextFocusUp
+ * @attr ref android.R.styleable#View_onClick
+ * @attr ref android.R.styleable#View_padding
+ * @attr ref android.R.styleable#View_paddingHorizontal
+ * @attr ref android.R.styleable#View_paddingVertical
+ * @attr ref android.R.styleable#View_paddingBottom
+ * @attr ref android.R.styleable#View_paddingLeft
+ * @attr ref android.R.styleable#View_paddingRight
+ * @attr ref android.R.styleable#View_paddingTop
+ * @attr ref android.R.styleable#View_paddingStart
+ * @attr ref android.R.styleable#View_paddingEnd
+ * @attr ref android.R.styleable#View_saveEnabled
+ * @attr ref android.R.styleable#View_rotation
+ * @attr ref android.R.styleable#View_rotationX
+ * @attr ref android.R.styleable#View_rotationY
+ * @attr ref android.R.styleable#View_scaleX
+ * @attr ref android.R.styleable#View_scaleY
+ * @attr ref android.R.styleable#View_scrollX
+ * @attr ref android.R.styleable#View_scrollY
+ * @attr ref android.R.styleable#View_scrollbarSize
+ * @attr ref android.R.styleable#View_scrollbarStyle
+ * @attr ref android.R.styleable#View_scrollbars
+ * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade
+ * @attr ref android.R.styleable#View_scrollbarFadeDuration
+ * @attr ref android.R.styleable#View_scrollbarTrackHorizontal
+ * @attr ref android.R.styleable#View_scrollbarThumbHorizontal
+ * @attr ref android.R.styleable#View_scrollbarThumbVertical
+ * @attr ref android.R.styleable#View_scrollbarTrackVertical
+ * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
+ * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
+ * @attr ref android.R.styleable#View_stateListAnimator
+ * @attr ref android.R.styleable#View_transitionName
+ * @attr ref android.R.styleable#View_soundEffectsEnabled
+ * @attr ref android.R.styleable#View_tag
+ * @attr ref android.R.styleable#View_textAlignment
+ * @attr ref android.R.styleable#View_textDirection
+ * @attr ref android.R.styleable#View_transformPivotX
+ * @attr ref android.R.styleable#View_transformPivotY
+ * @attr ref android.R.styleable#View_translationX
+ * @attr ref android.R.styleable#View_translationY
+ * @attr ref android.R.styleable#View_translationZ
+ * @attr ref android.R.styleable#View_visibility
+ * @attr ref android.R.styleable#View_theme
+ *
+ * @see android.view.ViewGroup
+ */
+@UiThread
+public class View implements Drawable.Callback, KeyEvent.Callback,
+ AccessibilityEventSource {
+ private static final boolean DBG = false;
+
+ /** @hide */
+ public static boolean DEBUG_DRAW = false;
+
+ /**
+ * The logging tag used by this class with android.util.Log.
+ */
+ protected static final String VIEW_LOG_TAG = "View";
+
+ /**
+ * When set to true, apps will draw debugging information about their layouts.
+ *
+ * @hide
+ */
+ public static final String DEBUG_LAYOUT_PROPERTY = "debug.layout";
+
+ /**
+ * When set to true, this view will save its attribute data.
+ *
+ * @hide
+ */
+ public static boolean mDebugViewAttributes = false;
+
+ /**
+ * Used to mark a View that has no ID.
+ */
+ public static final int NO_ID = -1;
+
+ /**
+ * Last ID that is given to Views that are no part of activities.
+ *
+ * {@hide}
+ */
+ public static final int LAST_APP_AUTOFILL_ID = Integer.MAX_VALUE / 2;
+
+ /**
+ * Attribute to find the autofilled highlight
+ *
+ * @see #getAutofilledDrawable()
+ */
+ private static final int[] AUTOFILL_HIGHLIGHT_ATTR =
+ new int[]{android.R.attr.autofilledHighlight};
+
+ /**
+ * Signals that compatibility booleans have been initialized according to
+ * target SDK versions.
+ */
+ private static boolean sCompatibilityDone = false;
+
+ /**
+ * Use the old (broken) way of building MeasureSpecs.
+ */
+ private static boolean sUseBrokenMakeMeasureSpec = false;
+
+ /**
+ * Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
+ */
+ static boolean sUseZeroUnspecifiedMeasureSpec = false;
+
+ /**
+ * Ignore any optimizations using the measure cache.
+ */
+ private static boolean sIgnoreMeasureCache = false;
+
+ /**
+ * Ignore an optimization that skips unnecessary EXACTLY layout passes.
+ */
+ private static boolean sAlwaysRemeasureExactly = false;
+
+ /**
+ * Relax constraints around whether setLayoutParams() must be called after
+ * modifying the layout params.
+ */
+ private static boolean sLayoutParamsAlwaysChanged = false;
+
+ /**
+ * Allow setForeground/setBackground to be called (and ignored) on a textureview,
+ * without throwing
+ */
+ static boolean sTextureViewIgnoresDrawableSetters = false;
+
+ /**
+ * Prior to N, some ViewGroups would not convert LayoutParams properly even though both extend
+ * MarginLayoutParams. For instance, converting LinearLayout.LayoutParams to
+ * RelativeLayout.LayoutParams would lose margin information. This is fixed on N but target API
+ * check is implemented for backwards compatibility.
+ *
+ * {@hide}
+ */
+ protected static boolean sPreserveMarginParamsInLayoutParamConversion;
+
+ /**
+ * Prior to N, when drag enters into child of a view that has already received an
+ * ACTION_DRAG_ENTERED event, the parent doesn't get a ACTION_DRAG_EXITED event.
+ * ACTION_DRAG_LOCATION and ACTION_DROP were delivered to the parent of a view that returned
+ * false from its event handler for these events.
+ * Starting from N, the parent will get ACTION_DRAG_EXITED event before the child gets its
+ * ACTION_DRAG_ENTERED. ACTION_DRAG_LOCATION and ACTION_DROP are never propagated to the parent.
+ * sCascadedDragDrop is true for pre-N apps for backwards compatibility implementation.
+ */
+ static boolean sCascadedDragDrop;
+
+ /**
+ * Prior to O, auto-focusable didn't exist and widgets such as ListView use hasFocusable
+ * to determine things like whether or not to permit item click events. We can't break
+ * apps that do this just because more things (clickable things) are now auto-focusable
+ * and they would get different results, so give old behavior to old apps.
+ */
+ static boolean sHasFocusableExcludeAutoFocusable;
+
+ /**
+ * Prior to O, auto-focusable didn't exist and views marked as clickable weren't implicitly
+ * made focusable by default. As a result, apps could (incorrectly) change the clickable
+ * setting of views off the UI thread. Now that clickable can effect the focusable state,
+ * changing the clickable attribute off the UI thread will cause an exception (since changing
+ * the focusable state checks). In order to prevent apps from crashing, we will handle this
+ * specific case and just not notify parents on new focusables resulting from marking views
+ * clickable from outside the UI thread.
+ */
+ private static boolean sAutoFocusableOffUIThreadWontNotifyParents;
+
+ /** @hide */
+ @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Focusable {}
+
+ /**
+ * This view does not want keystrokes.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
+ */
+ public static final int NOT_FOCUSABLE = 0x00000000;
+
+ /**
+ * This view wants keystrokes.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
+ */
+ public static final int FOCUSABLE = 0x00000001;
+
+ /**
+ * This view determines focusability automatically. This is the default.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
+ */
+ public static final int FOCUSABLE_AUTO = 0x00000010;
+
+ /**
+ * Mask for use with setFlags indicating bits used for focus.
+ */
+ private static final int FOCUSABLE_MASK = 0x00000011;
+
+ /**
+ * This view will adjust its padding to fit sytem windows (e.g. status bar)
+ */
+ private static final int FITS_SYSTEM_WINDOWS = 0x00000002;
+
+ /** @hide */
+ @IntDef({VISIBLE, INVISIBLE, GONE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Visibility {}
+
+ /**
+ * This view is visible.
+ * Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
+ * android:visibility}.
+ */
+ public static final int VISIBLE = 0x00000000;
+
+ /**
+ * This view is invisible, but it still takes up space for layout purposes.
+ * Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
+ * android:visibility}.
+ */
+ public static final int INVISIBLE = 0x00000004;
+
+ /**
+ * This view is invisible, and it doesn't take any space for layout
+ * purposes. Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
+ * android:visibility}.
+ */
+ public static final int GONE = 0x00000008;
+
+ /**
+ * Mask for use with setFlags indicating bits used for visibility.
+ * {@hide}
+ */
+ static final int VISIBILITY_MASK = 0x0000000C;
+
+ private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE};
+
+ /**
+ * Hint indicating that this view can be autofilled with an email address.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_EMAIL_ADDRESS}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+
+ /**
+ * Hint indicating that this view can be autofilled with a user's real name.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_NAME}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_NAME = "name";
+
+ /**
+ * Hint indicating that this view can be autofilled with a username.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_USERNAME}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_USERNAME = "username";
+
+ /**
+ * Hint indicating that this view can be autofilled with a password.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_PASSWORD}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_PASSWORD = "password";
+
+ /**
+ * Hint indicating that this view can be autofilled with a phone number.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_PHONE}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_PHONE = "phone";
+
+ /**
+ * Hint indicating that this view can be autofilled with a postal address.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_POSTAL_ADDRESS}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+
+ /**
+ * Hint indicating that this view can be autofilled with a postal code.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_POSTAL_CODE}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+
+ /**
+ * Hint indicating that this view can be autofilled with a credit card number.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_NUMBER}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
+
+ /**
+ * Hint indicating that this view can be autofilled with a credit card security code.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
+
+ /**
+ * Hint indicating that this view can be autofilled with a credit card expiration date.
+ *
+ * <p>It should be used when the credit card expiration date is represented by just one view;
+ * if it is represented by more than one (for example, one view for the month and another view
+ * for the year), then each of these views should use the hint specific for the unit
+ * ({@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH},
+ * or {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}).
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE =
+ "creditCardExpirationDate";
+
+ /**
+ * Hint indicating that this view can be autofilled with a credit card expiration month.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH =
+ "creditCardExpirationMonth";
+
+ /**
+ * Hint indicating that this view can be autofilled with a credit card expiration year.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR =
+ "creditCardExpirationYear";
+
+ /**
+ * Hint indicating that this view can be autofilled with a credit card expiration day.
+ *
+ * <p>Can be used with either {@link #setAutofillHints(String[])} or
+ * <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
+ * value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY}</code>).
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
+ */
+ public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay";
+
+ /**
+ * Hints for the autofill services that describes the content of the view.
+ */
+ private @Nullable String[] mAutofillHints;
+
+ /**
+ * Autofill id, lazily created on calls to {@link #getAutofillId()}.
+ */
+ private AutofillId mAutofillId;
+
+ /** @hide */
+ @IntDef({
+ AUTOFILL_TYPE_NONE,
+ AUTOFILL_TYPE_TEXT,
+ AUTOFILL_TYPE_TOGGLE,
+ AUTOFILL_TYPE_LIST,
+ AUTOFILL_TYPE_DATE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutofillType {}
+
+ /**
+ * Autofill type for views that cannot be autofilled.
+ *
+ * <p>Typically used when the view is read-only; for example, a text label.
+ *
+ * @see #getAutofillType()
+ */
+ public static final int AUTOFILL_TYPE_NONE = 0;
+
+ /**
+ * Autofill type for a text field, which is filled by a {@link CharSequence}.
+ *
+ * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+ * {@link AutofillValue#forText(CharSequence)}, and the value passed to autofill a
+ * {@link View} can be fetched through {@link AutofillValue#getTextValue()}.
+ *
+ * @see #getAutofillType()
+ */
+ public static final int AUTOFILL_TYPE_TEXT = 1;
+
+ /**
+ * Autofill type for a togglable field, which is filled by a {@code boolean}.
+ *
+ * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+ * {@link AutofillValue#forToggle(boolean)}, and the value passed to autofill a
+ * {@link View} can be fetched through {@link AutofillValue#getToggleValue()}.
+ *
+ * @see #getAutofillType()
+ */
+ public static final int AUTOFILL_TYPE_TOGGLE = 2;
+
+ /**
+ * Autofill type for a selection list field, which is filled by an {@code int}
+ * representing the element index inside the list (starting at {@code 0}).
+ *
+ * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+ * {@link AutofillValue#forList(int)}, and the value passed to autofill a
+ * {@link View} can be fetched through {@link AutofillValue#getListValue()}.
+ *
+ * <p>The available options in the selection list are typically provided by
+ * {@link android.app.assist.AssistStructure.ViewNode#getAutofillOptions()}.
+ *
+ * @see #getAutofillType()
+ */
+ public static final int AUTOFILL_TYPE_LIST = 3;
+
+
+ /**
+ * Autofill type for a field that contains a date, which is represented by a long representing
+ * the number of milliseconds since the standard base time known as "the epoch", namely
+ * January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}.
+ *
+ * <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
+ * {@link AutofillValue#forDate(long)}, and the values passed to
+ * autofill a {@link View} can be fetched through {@link AutofillValue#getDateValue()}.
+ *
+ * @see #getAutofillType()
+ */
+ public static final int AUTOFILL_TYPE_DATE = 4;
+
+ /** @hide */
+ @IntDef({
+ IMPORTANT_FOR_AUTOFILL_AUTO,
+ IMPORTANT_FOR_AUTOFILL_YES,
+ IMPORTANT_FOR_AUTOFILL_NO,
+ IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS,
+ IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutofillImportance {}
+
+ /**
+ * Automatically determine whether a view is important for autofill.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
+ */
+ public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0x0;
+
+ /**
+ * The view is important for autofill, and its children (if any) will be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
+ */
+ public static final int IMPORTANT_FOR_AUTOFILL_YES = 0x1;
+
+ /**
+ * The view is not important for autofill, but its children (if any) will be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
+ */
+ public static final int IMPORTANT_FOR_AUTOFILL_NO = 0x2;
+
+ /**
+ * The view is important for autofill, but its children (if any) will not be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
+ */
+ public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 0x4;
+
+ /**
+ * The view is not important for autofill, and its children (if any) will not be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
+ */
+ public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 0x8;
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ value = {AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutofillFlags {}
+
+ /**
+ * Flag requesting you to add views that are marked as not important for autofill
+ * (see {@link #setImportantForAutofill(int)}) to a {@link ViewStructure}.
+ */
+ public static final int AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x1;
+
+ /**
+ * This view is enabled. Interpretation varies by subclass.
+ * Use with ENABLED_MASK when calling setFlags.
+ * {@hide}
+ */
+ static final int ENABLED = 0x00000000;
+
+ /**
+ * This view is disabled. Interpretation varies by subclass.
+ * Use with ENABLED_MASK when calling setFlags.
+ * {@hide}
+ */
+ static final int DISABLED = 0x00000020;
+
+ /**
+ * Mask for use with setFlags indicating bits used for indicating whether
+ * this view is enabled
+ * {@hide}
+ */
+ static final int ENABLED_MASK = 0x00000020;
+
+ /**
+ * This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be
+ * called and further optimizations will be performed. It is okay to have
+ * this flag set and a background. Use with DRAW_MASK when calling setFlags.
+ * {@hide}
+ */
+ static final int WILL_NOT_DRAW = 0x00000080;
+
+ /**
+ * Mask for use with setFlags indicating bits used for indicating whether
+ * this view is will draw
+ * {@hide}
+ */
+ static final int DRAW_MASK = 0x00000080;
+
+ /**
+ * <p>This view doesn't show scrollbars.</p>
+ * {@hide}
+ */
+ static final int SCROLLBARS_NONE = 0x00000000;
+
+ /**
+ * <p>This view shows horizontal scrollbars.</p>
+ * {@hide}
+ */
+ static final int SCROLLBARS_HORIZONTAL = 0x00000100;
+
+ /**
+ * <p>This view shows vertical scrollbars.</p>
+ * {@hide}
+ */
+ static final int SCROLLBARS_VERTICAL = 0x00000200;
+
+ /**
+ * <p>Mask for use with setFlags indicating bits used for indicating which
+ * scrollbars are enabled.</p>
+ * {@hide}
+ */
+ static final int SCROLLBARS_MASK = 0x00000300;
+
+ /**
+ * Indicates that the view should filter touches when its window is obscured.
+ * Refer to the class comments for more information about this security feature.
+ * {@hide}
+ */
+ static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
+
+ /**
+ * Set for framework elements that use FITS_SYSTEM_WINDOWS, to indicate
+ * that they are optional and should be skipped if the window has
+ * requested system UI flags that ignore those insets for layout.
+ */
+ static final int OPTIONAL_FITS_SYSTEM_WINDOWS = 0x00000800;
+
+ /**
+ * <p>This view doesn't show fading edges.</p>
+ * {@hide}
+ */
+ static final int FADING_EDGE_NONE = 0x00000000;
+
+ /**
+ * <p>This view shows horizontal fading edges.</p>
+ * {@hide}
+ */
+ static final int FADING_EDGE_HORIZONTAL = 0x00001000;
+
+ /**
+ * <p>This view shows vertical fading edges.</p>
+ * {@hide}
+ */
+ static final int FADING_EDGE_VERTICAL = 0x00002000;
+
+ /**
+ * <p>Mask for use with setFlags indicating bits used for indicating which
+ * fading edges are enabled.</p>
+ * {@hide}
+ */
+ static final int FADING_EDGE_MASK = 0x00003000;
+
+ /**
+ * <p>Indicates this view can be clicked. When clickable, a View reacts
+ * to clicks by notifying the OnClickListener.<p>
+ * {@hide}
+ */
+ static final int CLICKABLE = 0x00004000;
+
+ /**
+ * <p>Indicates this view is caching its drawing into a bitmap.</p>
+ * {@hide}
+ */
+ static final int DRAWING_CACHE_ENABLED = 0x00008000;
+
+ /**
+ * <p>Indicates that no icicle should be saved for this view.<p>
+ * {@hide}
+ */
+ static final int SAVE_DISABLED = 0x000010000;
+
+ /**
+ * <p>Mask for use with setFlags indicating bits used for the saveEnabled
+ * property.</p>
+ * {@hide}
+ */
+ static final int SAVE_DISABLED_MASK = 0x000010000;
+
+ /**
+ * <p>Indicates that no drawing cache should ever be created for this view.<p>
+ * {@hide}
+ */
+ static final int WILL_NOT_CACHE_DRAWING = 0x000020000;
+
+ /**
+ * <p>Indicates this view can take / keep focus when int touch mode.</p>
+ * {@hide}
+ */
+ static final int FOCUSABLE_IN_TOUCH_MODE = 0x00040000;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH, DRAWING_CACHE_QUALITY_AUTO})
+ public @interface DrawingCacheQuality {}
+
+ /**
+ * <p>Enables low quality mode for the drawing cache.</p>
+ */
+ public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000;
+
+ /**
+ * <p>Enables high quality mode for the drawing cache.</p>
+ */
+ public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000;
+
+ /**
+ * <p>Enables automatic quality mode for the drawing cache.</p>
+ */
+ public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000;
+
+ private static final int[] DRAWING_CACHE_QUALITY_FLAGS = {
+ DRAWING_CACHE_QUALITY_AUTO, DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH
+ };
+
+ /**
+ * <p>Mask for use with setFlags indicating bits used for the cache
+ * quality property.</p>
+ * {@hide}
+ */
+ static final int DRAWING_CACHE_QUALITY_MASK = 0x00180000;
+
+ /**
+ * <p>
+ * Indicates this view can be long clicked. When long clickable, a View
+ * reacts to long clicks by notifying the OnLongClickListener or showing a
+ * context menu.
+ * </p>
+ * {@hide}
+ */
+ static final int LONG_CLICKABLE = 0x00200000;
+
+ /**
+ * <p>Indicates that this view gets its drawable states from its direct parent
+ * and ignores its original internal states.</p>
+ *
+ * @hide
+ */
+ static final int DUPLICATE_PARENT_STATE = 0x00400000;
+
+ /**
+ * <p>
+ * Indicates this view can be context clicked. When context clickable, a View reacts to a
+ * context click (e.g. a primary stylus button press or right mouse click) by notifying the
+ * OnContextClickListener.
+ * </p>
+ * {@hide}
+ */
+ static final int CONTEXT_CLICKABLE = 0x00800000;
+
+
+ /** @hide */
+ @IntDef({
+ SCROLLBARS_INSIDE_OVERLAY,
+ SCROLLBARS_INSIDE_INSET,
+ SCROLLBARS_OUTSIDE_OVERLAY,
+ SCROLLBARS_OUTSIDE_INSET
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScrollBarStyle {}
+
+ /**
+ * The scrollbar style to display the scrollbars inside the content area,
+ * without increasing the padding. The scrollbars will be overlaid with
+ * translucency on the view's content.
+ */
+ public static final int SCROLLBARS_INSIDE_OVERLAY = 0;
+
+ /**
+ * The scrollbar style to display the scrollbars inside the padded area,
+ * increasing the padding of the view. The scrollbars will not overlap the
+ * content area of the view.
+ */
+ public static final int SCROLLBARS_INSIDE_INSET = 0x01000000;
+
+ /**
+ * The scrollbar style to display the scrollbars at the edge of the view,
+ * without increasing the padding. The scrollbars will be overlaid with
+ * translucency.
+ */
+ public static final int SCROLLBARS_OUTSIDE_OVERLAY = 0x02000000;
+
+ /**
+ * The scrollbar style to display the scrollbars at the edge of the view,
+ * increasing the padding of the view. The scrollbars will only overlap the
+ * background, if any.
+ */
+ public static final int SCROLLBARS_OUTSIDE_INSET = 0x03000000;
+
+ /**
+ * Mask to check if the scrollbar style is overlay or inset.
+ * {@hide}
+ */
+ static final int SCROLLBARS_INSET_MASK = 0x01000000;
+
+ /**
+ * Mask to check if the scrollbar style is inside or outside.
+ * {@hide}
+ */
+ static final int SCROLLBARS_OUTSIDE_MASK = 0x02000000;
+
+ /**
+ * Mask for scrollbar style.
+ * {@hide}
+ */
+ static final int SCROLLBARS_STYLE_MASK = 0x03000000;
+
+ /**
+ * View flag indicating that the screen should remain on while the
+ * window containing this view is visible to the user. This effectively
+ * takes care of automatically setting the WindowManager's
+ * {@link WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}.
+ */
+ public static final int KEEP_SCREEN_ON = 0x04000000;
+
+ /**
+ * View flag indicating whether this view should have sound effects enabled
+ * for events such as clicking and touching.
+ */
+ public static final int SOUND_EFFECTS_ENABLED = 0x08000000;
+
+ /**
+ * View flag indicating whether this view should have haptic feedback
+ * enabled for events such as long presses.
+ */
+ public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000;
+
+ /**
+ * <p>Indicates that the view hierarchy should stop saving state when
+ * it reaches this view. If state saving is initiated immediately at
+ * the view, it will be allowed.
+ * {@hide}
+ */
+ static final int PARENT_SAVE_DISABLED = 0x20000000;
+
+ /**
+ * <p>Mask for use with setFlags indicating bits used for PARENT_SAVE_DISABLED.</p>
+ * {@hide}
+ */
+ static final int PARENT_SAVE_DISABLED_MASK = 0x20000000;
+
+ private static Paint sDebugPaint;
+
+ /**
+ * <p>Indicates this view can display a tooltip on hover or long press.</p>
+ * {@hide}
+ */
+ static final int TOOLTIP = 0x40000000;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ FOCUSABLES_ALL,
+ FOCUSABLES_TOUCH_MODE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusableMode {}
+
+ /**
+ * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+ * should add all focusable Views regardless if they are focusable in touch mode.
+ */
+ public static final int FOCUSABLES_ALL = 0x00000000;
+
+ /**
+ * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+ * should add only Views focusable in touch mode.
+ */
+ public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
+
+ /** @hide */
+ @IntDef({
+ FOCUS_BACKWARD,
+ FOCUS_FORWARD,
+ FOCUS_LEFT,
+ FOCUS_UP,
+ FOCUS_RIGHT,
+ FOCUS_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusDirection {}
+
+ /** @hide */
+ @IntDef({
+ FOCUS_LEFT,
+ FOCUS_UP,
+ FOCUS_RIGHT,
+ FOCUS_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move focus to the previous selectable
+ * item.
+ */
+ public static final int FOCUS_BACKWARD = 0x00000001;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move focus to the next selectable
+ * item.
+ */
+ public static final int FOCUS_FORWARD = 0x00000002;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move focus to the left.
+ */
+ public static final int FOCUS_LEFT = 0x00000011;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move focus up.
+ */
+ public static final int FOCUS_UP = 0x00000021;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move focus to the right.
+ */
+ public static final int FOCUS_RIGHT = 0x00000042;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move focus down.
+ */
+ public static final int FOCUS_DOWN = 0x00000082;
+
+ /**
+ * Bits of {@link #getMeasuredWidthAndState()} and
+ * {@link #getMeasuredWidthAndState()} that provide the actual measured size.
+ */
+ public static final int MEASURED_SIZE_MASK = 0x00ffffff;
+
+ /**
+ * Bits of {@link #getMeasuredWidthAndState()} and
+ * {@link #getMeasuredWidthAndState()} that provide the additional state bits.
+ */
+ public static final int MEASURED_STATE_MASK = 0xff000000;
+
+ /**
+ * Bit shift of {@link #MEASURED_STATE_MASK} to get to the height bits
+ * for functions that combine both width and height into a single int,
+ * such as {@link #getMeasuredState()} and the childState argument of
+ * {@link #resolveSizeAndState(int, int, int)}.
+ */
+ public static final int MEASURED_HEIGHT_STATE_SHIFT = 16;
+
+ /**
+ * Bit of {@link #getMeasuredWidthAndState()} and
+ * {@link #getMeasuredWidthAndState()} that indicates the measured size
+ * is smaller that the space the view would like to have.
+ */
+ public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;
+
+ /**
+ * Base View state sets
+ */
+ // Singles
+ /**
+ * Indicates the view has no states set. States are used with
+ * {@link android.graphics.drawable.Drawable} to change the drawing of the
+ * view depending on its state.
+ *
+ * @see android.graphics.drawable.Drawable
+ * @see #getDrawableState()
+ */
+ protected static final int[] EMPTY_STATE_SET;
+ /**
+ * Indicates the view is enabled. States are used with
+ * {@link android.graphics.drawable.Drawable} to change the drawing of the
+ * view depending on its state.
+ *
+ * @see android.graphics.drawable.Drawable
+ * @see #getDrawableState()
+ */
+ protected static final int[] ENABLED_STATE_SET;
+ /**
+ * Indicates the view is focused. States are used with
+ * {@link android.graphics.drawable.Drawable} to change the drawing of the
+ * view depending on its state.
+ *
+ * @see android.graphics.drawable.Drawable
+ * @see #getDrawableState()
+ */
+ protected static final int[] FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is selected. States are used with
+ * {@link android.graphics.drawable.Drawable} to change the drawing of the
+ * view depending on its state.
+ *
+ * @see android.graphics.drawable.Drawable
+ * @see #getDrawableState()
+ */
+ protected static final int[] SELECTED_STATE_SET;
+ /**
+ * Indicates the view is pressed. States are used with
+ * {@link android.graphics.drawable.Drawable} to change the drawing of the
+ * view depending on its state.
+ *
+ * @see android.graphics.drawable.Drawable
+ * @see #getDrawableState()
+ */
+ protected static final int[] PRESSED_STATE_SET;
+ /**
+ * Indicates the view's window has focus. States are used with
+ * {@link android.graphics.drawable.Drawable} to change the drawing of the
+ * view depending on its state.
+ *
+ * @see android.graphics.drawable.Drawable
+ * @see #getDrawableState()
+ */
+ protected static final int[] WINDOW_FOCUSED_STATE_SET;
+ // Doubles
+ /**
+ * Indicates the view is enabled and has the focus.
+ *
+ * @see #ENABLED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ */
+ protected static final int[] ENABLED_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is enabled and selected.
+ *
+ * @see #ENABLED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ */
+ protected static final int[] ENABLED_SELECTED_STATE_SET;
+ /**
+ * Indicates the view is enabled and that its window has focus.
+ *
+ * @see #ENABLED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is focused and selected.
+ *
+ * @see #FOCUSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ */
+ protected static final int[] FOCUSED_SELECTED_STATE_SET;
+ /**
+ * Indicates the view has the focus and that its window has the focus.
+ *
+ * @see #FOCUSED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is selected and that its window has the focus.
+ *
+ * @see #SELECTED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] SELECTED_WINDOW_FOCUSED_STATE_SET;
+ // Triples
+ /**
+ * Indicates the view is enabled, focused and selected.
+ *
+ * @see #ENABLED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ */
+ protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET;
+ /**
+ * Indicates the view is enabled, focused and its window has the focus.
+ *
+ * @see #ENABLED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is enabled, selected and its window has the focus.
+ *
+ * @see #ENABLED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is focused, selected and its window has the focus.
+ *
+ * @see #FOCUSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is enabled, focused, selected and its window
+ * has the focus.
+ *
+ * @see #ENABLED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed and its window has the focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed and selected.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ */
+ protected static final int[] PRESSED_SELECTED_STATE_SET;
+ /**
+ * Indicates the view is pressed, selected and its window has the focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed and focused.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed, focused and its window has the focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed, focused and selected.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_FOCUSED_SELECTED_STATE_SET;
+ /**
+ * Indicates the view is pressed, focused, selected and its window has the focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed and enabled.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_STATE_SET;
+ /**
+ * Indicates the view is pressed, enabled and its window has the focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed, enabled and selected.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_SELECTED_STATE_SET;
+ /**
+ * Indicates the view is pressed, enabled, selected and its window has the
+ * focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed, enabled and focused.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed, enabled, focused and its window has the
+ * focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * Indicates the view is pressed, enabled, focused and selected.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET;
+ /**
+ * Indicates the view is pressed, enabled, focused, selected and its window
+ * has the focus.
+ *
+ * @see #PRESSED_STATE_SET
+ * @see #ENABLED_STATE_SET
+ * @see #SELECTED_STATE_SET
+ * @see #FOCUSED_STATE_SET
+ * @see #WINDOW_FOCUSED_STATE_SET
+ */
+ protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+
+ static {
+ EMPTY_STATE_SET = StateSet.get(0);
+
+ WINDOW_FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_WINDOW_FOCUSED);
+
+ SELECTED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_SELECTED);
+ SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED);
+
+ FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED);
+
+ ENABLED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_ENABLED);
+ ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED);
+
+ PRESSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_PRESSED);
+ PRESSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ }
+
+ /**
+ * Accessibility event types that are dispatched for text population.
+ */
+ private static final int POPULATING_ACCESSIBILITY_EVENT_TYPES =
+ AccessibilityEvent.TYPE_VIEW_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_SELECTED
+ | AccessibilityEvent.TYPE_VIEW_FOCUSED
+ | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+ | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+ | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
+
+ static final int DEBUG_CORNERS_COLOR = Color.rgb(63, 127, 255);
+
+ static final int DEBUG_CORNERS_SIZE_DIP = 8;
+
+ /**
+ * Temporary Rect currently for use in setBackground(). This will probably
+ * be extended in the future to hold our own class with more than just
+ * a Rect. :)
+ */
+ static final ThreadLocal<Rect> sThreadLocal = new ThreadLocal<Rect>();
+
+ /**
+ * Map used to store views' tags.
+ */
+ private SparseArray<Object> mKeyedTags;
+
+ /**
+ * The next available accessibility id.
+ */
+ private static int sNextAccessibilityViewId;
+
+ /**
+ * The animation currently associated with this view.
+ * @hide
+ */
+ protected Animation mCurrentAnimation = null;
+
+ /**
+ * Width as measured during measure pass.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "measurement")
+ int mMeasuredWidth;
+
+ /**
+ * Height as measured during measure pass.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "measurement")
+ int mMeasuredHeight;
+
+ /**
+ * Flag to indicate that this view was marked INVALIDATED, or had its display list
+ * invalidated, prior to the current drawing iteration. If true, the view must re-draw
+ * its display list. This flag, used only when hw accelerated, allows us to clear the
+ * flag while retaining this information until it's needed (at getDisplayList() time and
+ * in drawChild(), when we decide to draw a view's children's display lists into our own).
+ *
+ * {@hide}
+ */
+ boolean mRecreateDisplayList = false;
+
+ /**
+ * The view's identifier.
+ * {@hide}
+ *
+ * @see #setId(int)
+ * @see #getId()
+ */
+ @IdRes
+ @ViewDebug.ExportedProperty(resolveId = true)
+ int mID = NO_ID;
+
+ /** The ID of this view for autofill purposes.
+ * <ul>
+ * <li>== {@link #NO_ID}: ID has not been assigned yet
+ * <li>&le; {@link #LAST_APP_AUTOFILL_ID}: View is not part of a activity. The ID is
+ * unique in the process. This might change
+ * over activity lifecycle events.
+ * <li>&gt; {@link #LAST_APP_AUTOFILL_ID}: View is part of a activity. The ID is
+ * unique in the activity. This stays the same
+ * over activity lifecycle events.
+ */
+ private int mAutofillViewId = NO_ID;
+
+ // ID for accessibility purposes. This ID must be unique for every window
+ private int mAccessibilityViewId = NO_ID;
+
+ private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
+
+ /**
+ * The view's tag.
+ * {@hide}
+ *
+ * @see #setTag(Object)
+ * @see #getTag()
+ */
+ protected Object mTag = null;
+
+ // for mPrivateFlags:
+ /** {@hide} */
+ static final int PFLAG_WANTS_FOCUS = 0x00000001;
+ /** {@hide} */
+ static final int PFLAG_FOCUSED = 0x00000002;
+ /** {@hide} */
+ static final int PFLAG_SELECTED = 0x00000004;
+ /** {@hide} */
+ static final int PFLAG_IS_ROOT_NAMESPACE = 0x00000008;
+ /** {@hide} */
+ static final int PFLAG_HAS_BOUNDS = 0x00000010;
+ /** {@hide} */
+ static final int PFLAG_DRAWN = 0x00000020;
+ /**
+ * When this flag is set, this view is running an animation on behalf of its
+ * children and should therefore not cancel invalidate requests, even if they
+ * lie outside of this view's bounds.
+ *
+ * {@hide}
+ */
+ static final int PFLAG_DRAW_ANIMATION = 0x00000040;
+ /** {@hide} */
+ static final int PFLAG_SKIP_DRAW = 0x00000080;
+ /** {@hide} */
+ static final int PFLAG_REQUEST_TRANSPARENT_REGIONS = 0x00000200;
+ /** {@hide} */
+ static final int PFLAG_DRAWABLE_STATE_DIRTY = 0x00000400;
+ /** {@hide} */
+ static final int PFLAG_MEASURED_DIMENSION_SET = 0x00000800;
+ /** {@hide} */
+ static final int PFLAG_FORCE_LAYOUT = 0x00001000;
+ /** {@hide} */
+ static final int PFLAG_LAYOUT_REQUIRED = 0x00002000;
+
+ private static final int PFLAG_PRESSED = 0x00004000;
+
+ /** {@hide} */
+ static final int PFLAG_DRAWING_CACHE_VALID = 0x00008000;
+ /**
+ * Flag used to indicate that this view should be drawn once more (and only once
+ * more) after its animation has completed.
+ * {@hide}
+ */
+ static final int PFLAG_ANIMATION_STARTED = 0x00010000;
+
+ private static final int PFLAG_SAVE_STATE_CALLED = 0x00020000;
+
+ /**
+ * Indicates that the View returned true when onSetAlpha() was called and that
+ * the alpha must be restored.
+ * {@hide}
+ */
+ static final int PFLAG_ALPHA_SET = 0x00040000;
+
+ /**
+ * Set by {@link #setScrollContainer(boolean)}.
+ */
+ static final int PFLAG_SCROLL_CONTAINER = 0x00080000;
+
+ /**
+ * Set by {@link #setScrollContainer(boolean)}.
+ */
+ static final int PFLAG_SCROLL_CONTAINER_ADDED = 0x00100000;
+
+ /**
+ * View flag indicating whether this view was invalidated (fully or partially.)
+ *
+ * @hide
+ */
+ static final int PFLAG_DIRTY = 0x00200000;
+
+ /**
+ * View flag indicating whether this view was invalidated by an opaque
+ * invalidate request.
+ *
+ * @hide
+ */
+ static final int PFLAG_DIRTY_OPAQUE = 0x00400000;
+
+ /**
+ * Mask for {@link #PFLAG_DIRTY} and {@link #PFLAG_DIRTY_OPAQUE}.
+ *
+ * @hide
+ */
+ static final int PFLAG_DIRTY_MASK = 0x00600000;
+
+ /**
+ * Indicates whether the background is opaque.
+ *
+ * @hide
+ */
+ static final int PFLAG_OPAQUE_BACKGROUND = 0x00800000;
+
+ /**
+ * Indicates whether the scrollbars are opaque.
+ *
+ * @hide
+ */
+ static final int PFLAG_OPAQUE_SCROLLBARS = 0x01000000;
+
+ /**
+ * Indicates whether the view is opaque.
+ *
+ * @hide
+ */
+ static final int PFLAG_OPAQUE_MASK = 0x01800000;
+
+ /**
+ * Indicates a prepressed state;
+ * the short time between ACTION_DOWN and recognizing
+ * a 'real' press. Prepressed is used to recognize quick taps
+ * even when they are shorter than ViewConfiguration.getTapTimeout().
+ *
+ * @hide
+ */
+ private static final int PFLAG_PREPRESSED = 0x02000000;
+
+ /**
+ * Indicates whether the view is temporarily detached.
+ *
+ * @hide
+ */
+ static final int PFLAG_CANCEL_NEXT_UP_EVENT = 0x04000000;
+
+ /**
+ * Indicates that we should awaken scroll bars once attached
+ *
+ * PLEASE NOTE: This flag is now unused as we now send onVisibilityChanged
+ * during window attachment and it is no longer needed. Feel free to repurpose it.
+ *
+ * @hide
+ */
+ private static final int PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
+
+ /**
+ * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT.
+ * @hide
+ */
+ private static final int PFLAG_HOVERED = 0x10000000;
+
+ /**
+ * no longer needed, should be reused
+ */
+ private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000;
+
+ /** {@hide} */
+ static final int PFLAG_ACTIVATED = 0x40000000;
+
+ /**
+ * Indicates that this view was specifically invalidated, not just dirtied because some
+ * child view was invalidated. The flag is used to determine when we need to recreate
+ * a view's display list (as opposed to just returning a reference to its existing
+ * display list).
+ *
+ * @hide
+ */
+ static final int PFLAG_INVALIDATED = 0x80000000;
+
+ /**
+ * Masks for mPrivateFlags2, as generated by dumpFlags():
+ *
+ * |-------|-------|-------|-------|
+ * 1 PFLAG2_DRAG_CAN_ACCEPT
+ * 1 PFLAG2_DRAG_HOVERED
+ * 11 PFLAG2_LAYOUT_DIRECTION_MASK
+ * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL
+ * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED
+ * 11 PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK
+ * 1 PFLAG2_TEXT_DIRECTION_FLAGS[1]
+ * 1 PFLAG2_TEXT_DIRECTION_FLAGS[2]
+ * 11 PFLAG2_TEXT_DIRECTION_FLAGS[3]
+ * 1 PFLAG2_TEXT_DIRECTION_FLAGS[4]
+ * 1 1 PFLAG2_TEXT_DIRECTION_FLAGS[5]
+ * 11 PFLAG2_TEXT_DIRECTION_FLAGS[6]
+ * 111 PFLAG2_TEXT_DIRECTION_FLAGS[7]
+ * 111 PFLAG2_TEXT_DIRECTION_MASK
+ * 1 PFLAG2_TEXT_DIRECTION_RESOLVED
+ * 1 PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT
+ * 111 PFLAG2_TEXT_DIRECTION_RESOLVED_MASK
+ * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[1]
+ * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[2]
+ * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[3]
+ * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[4]
+ * 1 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[5]
+ * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[6]
+ * 111 PFLAG2_TEXT_ALIGNMENT_MASK
+ * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED
+ * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT
+ * 111 PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK
+ * 111 PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK
+ * 11 PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK
+ * 1 PFLAG2_ACCESSIBILITY_FOCUSED
+ * 1 PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED
+ * 1 PFLAG2_VIEW_QUICK_REJECTED
+ * 1 PFLAG2_PADDING_RESOLVED
+ * 1 PFLAG2_DRAWABLE_RESOLVED
+ * 1 PFLAG2_HAS_TRANSIENT_STATE
+ * |-------|-------|-------|-------|
+ */
+
+ /**
+ * Indicates that this view has reported that it can accept the current drag's content.
+ * Cleared when the drag operation concludes.
+ * @hide
+ */
+ static final int PFLAG2_DRAG_CAN_ACCEPT = 0x00000001;
+
+ /**
+ * Indicates that this view is currently directly under the drag location in a
+ * drag-and-drop operation involving content that it can accept. Cleared when
+ * the drag exits the view, or when the drag operation concludes.
+ * @hide
+ */
+ static final int PFLAG2_DRAG_HOVERED = 0x00000002;
+
+ /** @hide */
+ @IntDef({
+ LAYOUT_DIRECTION_LTR,
+ LAYOUT_DIRECTION_RTL,
+ LAYOUT_DIRECTION_INHERIT,
+ LAYOUT_DIRECTION_LOCALE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ // Not called LayoutDirection to avoid conflict with android.util.LayoutDirection
+ public @interface LayoutDir {}
+
+ /** @hide */
+ @IntDef({
+ LAYOUT_DIRECTION_LTR,
+ LAYOUT_DIRECTION_RTL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResolvedLayoutDir {}
+
+ /**
+ * A flag to indicate that the layout direction of this view has not been defined yet.
+ * @hide
+ */
+ public static final int LAYOUT_DIRECTION_UNDEFINED = LayoutDirection.UNDEFINED;
+
+ /**
+ * Horizontal layout direction of this view is from Left to Right.
+ * Use with {@link #setLayoutDirection}.
+ */
+ public static final int LAYOUT_DIRECTION_LTR = LayoutDirection.LTR;
+
+ /**
+ * Horizontal layout direction of this view is from Right to Left.
+ * Use with {@link #setLayoutDirection}.
+ */
+ public static final int LAYOUT_DIRECTION_RTL = LayoutDirection.RTL;
+
+ /**
+ * Horizontal layout direction of this view is inherited from its parent.
+ * Use with {@link #setLayoutDirection}.
+ */
+ public static final int LAYOUT_DIRECTION_INHERIT = LayoutDirection.INHERIT;
+
+ /**
+ * Horizontal layout direction of this view is from deduced from the default language
+ * script for the locale. Use with {@link #setLayoutDirection}.
+ */
+ public static final int LAYOUT_DIRECTION_LOCALE = LayoutDirection.LOCALE;
+
+ /**
+ * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
+ * @hide
+ */
+ static final int PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT = 2;
+
+ /**
+ * Mask for use with private flags indicating bits used for horizontal layout direction.
+ * @hide
+ */
+ static final int PFLAG2_LAYOUT_DIRECTION_MASK = 0x00000003 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+ /**
+ * Indicates whether the view horizontal layout direction has been resolved and drawn to the
+ * right-to-left direction.
+ * @hide
+ */
+ static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL = 4 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+ /**
+ * Indicates whether the view horizontal layout direction has been resolved.
+ * @hide
+ */
+ static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED = 8 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+ /**
+ * Mask for use with private flags indicating bits used for resolved horizontal layout direction.
+ * @hide
+ */
+ static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C
+ << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+
+ /*
+ * Array of horizontal layout direction flags for mapping attribute "layoutDirection" to correct
+ * flag value.
+ * @hide
+ */
+ private static final int[] LAYOUT_DIRECTION_FLAGS = {
+ LAYOUT_DIRECTION_LTR,
+ LAYOUT_DIRECTION_RTL,
+ LAYOUT_DIRECTION_INHERIT,
+ LAYOUT_DIRECTION_LOCALE
+ };
+
+ /**
+ * Default horizontal layout direction.
+ */
+ private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
+
+ /**
+ * Default horizontal layout direction.
+ * @hide
+ */
+ static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR;
+
+ /**
+ * Text direction is inherited through {@link ViewGroup}
+ */
+ public static final int TEXT_DIRECTION_INHERIT = 0;
+
+ /**
+ * Text direction is using "first strong algorithm". The first strong directional character
+ * determines the paragraph direction. If there is no strong directional character, the
+ * paragraph direction is the view's resolved layout direction.
+ */
+ public static final int TEXT_DIRECTION_FIRST_STRONG = 1;
+
+ /**
+ * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains
+ * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters.
+ * If there are neither, the paragraph direction is the view's resolved layout direction.
+ */
+ public static final int TEXT_DIRECTION_ANY_RTL = 2;
+
+ /**
+ * Text direction is forced to LTR.
+ */
+ public static final int TEXT_DIRECTION_LTR = 3;
+
+ /**
+ * Text direction is forced to RTL.
+ */
+ public static final int TEXT_DIRECTION_RTL = 4;
+
+ /**
+ * Text direction is coming from the system Locale.
+ */
+ public static final int TEXT_DIRECTION_LOCALE = 5;
+
+ /**
+ * Text direction is using "first strong algorithm". The first strong directional character
+ * determines the paragraph direction. If there is no strong directional character, the
+ * paragraph direction is LTR.
+ */
+ public static final int TEXT_DIRECTION_FIRST_STRONG_LTR = 6;
+
+ /**
+ * Text direction is using "first strong algorithm". The first strong directional character
+ * determines the paragraph direction. If there is no strong directional character, the
+ * paragraph direction is RTL.
+ */
+ public static final int TEXT_DIRECTION_FIRST_STRONG_RTL = 7;
+
+ /**
+ * Default text direction is inherited
+ */
+ private static final int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT;
+
+ /**
+ * Default resolved text direction
+ * @hide
+ */
+ static final int TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_FIRST_STRONG;
+
+ /**
+ * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED)
+ * @hide
+ */
+ static final int PFLAG2_TEXT_DIRECTION_MASK_SHIFT = 6;
+
+ /**
+ * Mask for use with private flags indicating bits used for text direction.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_DIRECTION_MASK = 0x00000007
+ << PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
+
+ /**
+ * Array of text direction flags for mapping attribute "textDirection" to correct
+ * flag value.
+ * @hide
+ */
+ private static final int[] PFLAG2_TEXT_DIRECTION_FLAGS = {
+ TEXT_DIRECTION_INHERIT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_ANY_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_FIRST_STRONG_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_FIRST_STRONG_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT
+ };
+
+ /**
+ * Indicates whether the view text direction has been resolved.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED = 0x00000008
+ << PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
+
+ /**
+ * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
+ * @hide
+ */
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10;
+
+ /**
+ * Mask for use with private flags indicating bits used for resolved text direction.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK = 0x00000007
+ << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+
+ /**
+ * Indicates whether the view text direction has been resolved to the "first strong" heuristic.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT =
+ TEXT_DIRECTION_RESOLVED_DEFAULT << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+
+ /** @hide */
+ @IntDef({
+ TEXT_ALIGNMENT_INHERIT,
+ TEXT_ALIGNMENT_GRAVITY,
+ TEXT_ALIGNMENT_CENTER,
+ TEXT_ALIGNMENT_TEXT_START,
+ TEXT_ALIGNMENT_TEXT_END,
+ TEXT_ALIGNMENT_VIEW_START,
+ TEXT_ALIGNMENT_VIEW_END
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TextAlignment {}
+
+ /**
+ * Default text alignment. The text alignment of this View is inherited from its parent.
+ * Use with {@link #setTextAlignment(int)}
+ */
+ public static final int TEXT_ALIGNMENT_INHERIT = 0;
+
+ /**
+ * Default for the root view. The gravity determines the text alignment, ALIGN_NORMAL,
+ * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s text direction.
+ *
+ * Use with {@link #setTextAlignment(int)}
+ */
+ public static final int TEXT_ALIGNMENT_GRAVITY = 1;
+
+ /**
+ * Align to the start of the paragraph, e.g. ALIGN_NORMAL.
+ *
+ * Use with {@link #setTextAlignment(int)}
+ */
+ public static final int TEXT_ALIGNMENT_TEXT_START = 2;
+
+ /**
+ * Align to the end of the paragraph, e.g. ALIGN_OPPOSITE.
+ *
+ * Use with {@link #setTextAlignment(int)}
+ */
+ public static final int TEXT_ALIGNMENT_TEXT_END = 3;
+
+ /**
+ * Center the paragraph, e.g. ALIGN_CENTER.
+ *
+ * Use with {@link #setTextAlignment(int)}
+ */
+ public static final int TEXT_ALIGNMENT_CENTER = 4;
+
+ /**
+ * Align to the start of the view, which is ALIGN_LEFT if the view’s resolved
+ * layoutDirection is LTR, and ALIGN_RIGHT otherwise.
+ *
+ * Use with {@link #setTextAlignment(int)}
+ */
+ public static final int TEXT_ALIGNMENT_VIEW_START = 5;
+
+ /**
+ * Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved
+ * layoutDirection is LTR, and ALIGN_LEFT otherwise.
+ *
+ * Use with {@link #setTextAlignment(int)}
+ */
+ public static final int TEXT_ALIGNMENT_VIEW_END = 6;
+
+ /**
+ * Default text alignment is inherited
+ */
+ private static final int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
+
+ /**
+ * Default resolved text alignment
+ * @hide
+ */
+ static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
+
+ /**
+ * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
+ * @hide
+ */
+ static final int PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT = 13;
+
+ /**
+ * Mask for use with private flags indicating bits used for text alignment.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_ALIGNMENT_MASK = 0x00000007 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
+
+ /**
+ * Array of text direction flags for mapping attribute "textAlignment" to correct
+ * flag value.
+ * @hide
+ */
+ private static final int[] PFLAG2_TEXT_ALIGNMENT_FLAGS = {
+ TEXT_ALIGNMENT_INHERIT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_TEXT_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_TEXT_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_CENTER << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_VIEW_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_VIEW_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT
+ };
+
+ /**
+ * Indicates whether the view text alignment has been resolved.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED = 0x00000008 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
+
+ /**
+ * Bit shift to get the resolved text alignment.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17;
+
+ /**
+ * Mask for use with private flags indicating bits used for text alignment.
+ * @hide
+ */
+ static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007
+ << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+
+ /**
+ * Indicates whether if the view text alignment has been resolved to gravity
+ */
+ private static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT =
+ TEXT_ALIGNMENT_RESOLVED_DEFAULT << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+
+ // Accessiblity constants for mPrivateFlags2
+
+ /**
+ * Shift for the bits in {@link #mPrivateFlags2} related to the
+ * "importantForAccessibility" attribute.
+ */
+ static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20;
+
+ /**
+ * Automatically determine whether a view is important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0x00000000;
+
+ /**
+ * The view is important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 0x00000001;
+
+ /**
+ * The view is not important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002;
+
+ /**
+ * The view is not important for accessibility, nor are any of its
+ * descendant views.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 0x00000004;
+
+ /**
+ * The default whether the view is important for accessibility.
+ */
+ static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+
+ /**
+ * Mask for obtaining the bits which specify how to determine
+ * whether a view is important for accessibility.
+ */
+ static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ | IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO
+ | IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+ << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+
+ /**
+ * Shift for the bits in {@link #mPrivateFlags2} related to the
+ * "accessibilityLiveRegion" attribute.
+ */
+ static final int PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT = 23;
+
+ /**
+ * Live region mode specifying that accessibility services should not
+ * automatically announce changes to this view. This is the default live
+ * region mode for most views.
+ * <p>
+ * Use with {@link #setAccessibilityLiveRegion(int)}.
+ */
+ public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0x00000000;
+
+ /**
+ * Live region mode specifying that accessibility services should announce
+ * changes to this view.
+ * <p>
+ * Use with {@link #setAccessibilityLiveRegion(int)}.
+ */
+ public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
+
+ /**
+ * Live region mode specifying that accessibility services should interrupt
+ * ongoing speech to immediately announce changes to this view.
+ * <p>
+ * Use with {@link #setAccessibilityLiveRegion(int)}.
+ */
+ public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 0x00000002;
+
+ /**
+ * The default whether the view is important for accessibility.
+ */
+ static final int ACCESSIBILITY_LIVE_REGION_DEFAULT = ACCESSIBILITY_LIVE_REGION_NONE;
+
+ /**
+ * Mask for obtaining the bits which specify a view's accessibility live
+ * region mode.
+ */
+ static final int PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK = (ACCESSIBILITY_LIVE_REGION_NONE
+ | ACCESSIBILITY_LIVE_REGION_POLITE | ACCESSIBILITY_LIVE_REGION_ASSERTIVE)
+ << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT;
+
+ /**
+ * Flag indicating whether a view has accessibility focus.
+ */
+ static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x04000000;
+
+ /**
+ * Flag whether the accessibility state of the subtree rooted at this view changed.
+ */
+ static final int PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED = 0x08000000;
+
+ /**
+ * Flag indicating whether a view failed the quickReject() check in draw(). This condition
+ * is used to check whether later changes to the view's transform should invalidate the
+ * view to force the quickReject test to run again.
+ */
+ static final int PFLAG2_VIEW_QUICK_REJECTED = 0x10000000;
+
+ /**
+ * Flag indicating that start/end padding has been resolved into left/right padding
+ * for use in measurement, layout, drawing, etc. This is set by {@link #resolvePadding()}
+ * and checked by {@link #measure(int, int)} to determine if padding needs to be resolved
+ * during measurement. In some special cases this is required such as when an adapter-based
+ * view measures prospective children without attaching them to a window.
+ */
+ static final int PFLAG2_PADDING_RESOLVED = 0x20000000;
+
+ /**
+ * Flag indicating that the start/end drawables has been resolved into left/right ones.
+ */
+ static final int PFLAG2_DRAWABLE_RESOLVED = 0x40000000;
+
+ /**
+ * Indicates that the view is tracking some sort of transient state
+ * that the app should not need to be aware of, but that the framework
+ * should take special care to preserve.
+ */
+ static final int PFLAG2_HAS_TRANSIENT_STATE = 0x80000000;
+
+ /**
+ * Group of bits indicating that RTL properties resolution is done.
+ */
+ static final int ALL_RTL_PROPERTIES_RESOLVED = PFLAG2_LAYOUT_DIRECTION_RESOLVED |
+ PFLAG2_TEXT_DIRECTION_RESOLVED |
+ PFLAG2_TEXT_ALIGNMENT_RESOLVED |
+ PFLAG2_PADDING_RESOLVED |
+ PFLAG2_DRAWABLE_RESOLVED;
+
+ // There are a couple of flags left in mPrivateFlags2
+
+ /* End of masks for mPrivateFlags2 */
+
+ /**
+ * Masks for mPrivateFlags3, as generated by dumpFlags():
+ *
+ * |-------|-------|-------|-------|
+ * 1 PFLAG3_VIEW_IS_ANIMATING_TRANSFORM
+ * 1 PFLAG3_VIEW_IS_ANIMATING_ALPHA
+ * 1 PFLAG3_IS_LAID_OUT
+ * 1 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
+ * 1 PFLAG3_CALLED_SUPER
+ * 1 PFLAG3_APPLYING_INSETS
+ * 1 PFLAG3_FITTING_SYSTEM_WINDOWS
+ * 1 PFLAG3_NESTED_SCROLLING_ENABLED
+ * 1 PFLAG3_SCROLL_INDICATOR_TOP
+ * 1 PFLAG3_SCROLL_INDICATOR_BOTTOM
+ * 1 PFLAG3_SCROLL_INDICATOR_LEFT
+ * 1 PFLAG3_SCROLL_INDICATOR_RIGHT
+ * 1 PFLAG3_SCROLL_INDICATOR_START
+ * 1 PFLAG3_SCROLL_INDICATOR_END
+ * 1 PFLAG3_ASSIST_BLOCKED
+ * 1 PFLAG3_CLUSTER
+ * 1 PFLAG3_IS_AUTOFILLED
+ * 1 PFLAG3_FINGER_DOWN
+ * 1 PFLAG3_FOCUSED_BY_DEFAULT
+ * 1111 PFLAG3_IMPORTANT_FOR_AUTOFILL
+ * 1 PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE
+ * 1 PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED
+ * 1 PFLAG3_TEMPORARY_DETACH
+ * 1 PFLAG3_NO_REVEAL_ON_FOCUS
+ * 1 PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT
+ * |-------|-------|-------|-------|
+ */
+
+ /**
+ * Flag indicating that view has a transform animation set on it. This is used to track whether
+ * an animation is cleared between successive frames, in order to tell the associated
+ * DisplayList to clear its animation matrix.
+ */
+ static final int PFLAG3_VIEW_IS_ANIMATING_TRANSFORM = 0x1;
+
+ /**
+ * Flag indicating that view has an alpha animation set on it. This is used to track whether an
+ * animation is cleared between successive frames, in order to tell the associated
+ * DisplayList to restore its alpha value.
+ */
+ static final int PFLAG3_VIEW_IS_ANIMATING_ALPHA = 0x2;
+
+ /**
+ * Flag indicating that the view has been through at least one layout since it
+ * was last attached to a window.
+ */
+ static final int PFLAG3_IS_LAID_OUT = 0x4;
+
+ /**
+ * Flag indicating that a call to measure() was skipped and should be done
+ * instead when layout() is invoked.
+ */
+ 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;
+
+ /**
+ * Flag indicating that we're in the process of applying window insets.
+ */
+ static final int PFLAG3_APPLYING_INSETS = 0x20;
+
+ /**
+ * Flag indicating that we're in the process of fitting system windows using the old method.
+ */
+ static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x40;
+
+ /**
+ * Flag indicating that nested scrolling is enabled for this view.
+ * The view will optionally cooperate with views up its parent chain to allow for
+ * integrated nested scrolling along the same axis.
+ */
+ static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80;
+
+ /**
+ * Flag indicating that the bottom scroll indicator should be displayed
+ * when this view can scroll up.
+ */
+ static final int PFLAG3_SCROLL_INDICATOR_TOP = 0x0100;
+
+ /**
+ * Flag indicating that the bottom scroll indicator should be displayed
+ * when this view can scroll down.
+ */
+ static final int PFLAG3_SCROLL_INDICATOR_BOTTOM = 0x0200;
+
+ /**
+ * Flag indicating that the left scroll indicator should be displayed
+ * when this view can scroll left.
+ */
+ static final int PFLAG3_SCROLL_INDICATOR_LEFT = 0x0400;
+
+ /**
+ * Flag indicating that the right scroll indicator should be displayed
+ * when this view can scroll right.
+ */
+ static final int PFLAG3_SCROLL_INDICATOR_RIGHT = 0x0800;
+
+ /**
+ * Flag indicating that the start scroll indicator should be displayed
+ * when this view can scroll in the start direction.
+ */
+ static final int PFLAG3_SCROLL_INDICATOR_START = 0x1000;
+
+ /**
+ * Flag indicating that the end scroll indicator should be displayed
+ * when this view can scroll in the end direction.
+ */
+ static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
+
+ static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
+
+ static final int SCROLL_INDICATORS_NONE = 0x0000;
+
+ /**
+ * Mask for use with setFlags indicating bits used for indicating which
+ * scroll indicators are enabled.
+ */
+ static final int SCROLL_INDICATORS_PFLAG3_MASK = PFLAG3_SCROLL_INDICATOR_TOP
+ | PFLAG3_SCROLL_INDICATOR_BOTTOM | PFLAG3_SCROLL_INDICATOR_LEFT
+ | PFLAG3_SCROLL_INDICATOR_RIGHT | PFLAG3_SCROLL_INDICATOR_START
+ | PFLAG3_SCROLL_INDICATOR_END;
+
+ /**
+ * Left-shift required to translate between public scroll indicator flags
+ * and internal PFLAGS3 flags. When used as a right-shift, translates
+ * PFLAGS3 flags to public flags.
+ */
+ static final int SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT = 8;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {
+ SCROLL_INDICATOR_TOP,
+ SCROLL_INDICATOR_BOTTOM,
+ SCROLL_INDICATOR_LEFT,
+ SCROLL_INDICATOR_RIGHT,
+ SCROLL_INDICATOR_START,
+ SCROLL_INDICATOR_END,
+ })
+ public @interface ScrollIndicators {}
+
+ /**
+ * Scroll indicator direction for the top edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_TOP =
+ PFLAG3_SCROLL_INDICATOR_TOP >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+ /**
+ * Scroll indicator direction for the bottom edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_BOTTOM =
+ PFLAG3_SCROLL_INDICATOR_BOTTOM >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+ /**
+ * Scroll indicator direction for the left edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_LEFT =
+ PFLAG3_SCROLL_INDICATOR_LEFT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+ /**
+ * Scroll indicator direction for the right edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_RIGHT =
+ PFLAG3_SCROLL_INDICATOR_RIGHT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+ /**
+ * Scroll indicator direction for the starting edge of the view.
+ * <p>
+ * Resolved according to the view's layout direction, see
+ * {@link #getLayoutDirection()} for more information.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_START =
+ PFLAG3_SCROLL_INDICATOR_START >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+ /**
+ * Scroll indicator direction for the ending edge of the view.
+ * <p>
+ * Resolved according to the view's layout direction, see
+ * {@link #getLayoutDirection()} for more information.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_END =
+ PFLAG3_SCROLL_INDICATOR_END >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+
+ /**
+ * <p>Indicates that we are allowing {@link ViewStructure} to traverse
+ * into this view.<p>
+ */
+ static final int PFLAG3_ASSIST_BLOCKED = 0x4000;
+
+ /**
+ * Flag indicating that the view is a root of a keyboard navigation cluster.
+ *
+ * @see #isKeyboardNavigationCluster()
+ * @see #setKeyboardNavigationCluster(boolean)
+ */
+ private static final int PFLAG3_CLUSTER = 0x8000;
+
+ /**
+ * Flag indicating that the view is autofilled
+ *
+ * @see #isAutofilled()
+ * @see #setAutofilled(boolean)
+ */
+ private static final int PFLAG3_IS_AUTOFILLED = 0x10000;
+
+ /**
+ * Indicates that the user is currently touching the screen.
+ * Currently used for the tooltip positioning only.
+ */
+ private static final int PFLAG3_FINGER_DOWN = 0x20000;
+
+ /**
+ * Flag indicating that this view is the default-focus view.
+ *
+ * @see #isFocusedByDefault()
+ * @see #setFocusedByDefault(boolean)
+ */
+ private static final int PFLAG3_FOCUSED_BY_DEFAULT = 0x40000;
+
+ /**
+ * Shift for the bits in {@link #mPrivateFlags3} related to the
+ * "importantForAutofill" attribute.
+ */
+ static final int PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT = 19;
+
+ /**
+ * Mask for obtaining the bits which specify how to determine
+ * whether a view is important for autofill.
+ */
+ static final int PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK = (IMPORTANT_FOR_AUTOFILL_AUTO
+ | IMPORTANT_FOR_AUTOFILL_YES | IMPORTANT_FOR_AUTOFILL_NO
+ | IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
+ | IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS)
+ << PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT;
+
+ /**
+ * Whether this view has rendered elements that overlap (see {@link
+ * #hasOverlappingRendering()}, {@link #forceHasOverlappingRendering(boolean)}, and
+ * {@link #getHasOverlappingRendering()} ). The value in this bit is only valid when
+ * PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED has been set. Otherwise, the value is
+ * determined by whatever {@link #hasOverlappingRendering()} returns.
+ */
+ private static final int PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE = 0x800000;
+
+ /**
+ * Whether {@link #forceHasOverlappingRendering(boolean)} has been called. When true, value
+ * in PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE is valid.
+ */
+ private static final int PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED = 0x1000000;
+
+ /**
+ * Flag indicating that the view is temporarily detached from the parent view.
+ *
+ * @see #onStartTemporaryDetach()
+ * @see #onFinishTemporaryDetach()
+ */
+ static final int PFLAG3_TEMPORARY_DETACH = 0x2000000;
+
+ /**
+ * Flag indicating that the view does not wish to be revealed within its parent
+ * hierarchy when it gains focus. Expressed in the negative since the historical
+ * default behavior is to reveal on focus; this flag suppresses that behavior.
+ *
+ * @see #setRevealOnFocusHint(boolean)
+ * @see #getRevealOnFocusHint()
+ */
+ private static final int PFLAG3_NO_REVEAL_ON_FOCUS = 0x4000000;
+
+ /**
+ * Flag indicating that when layout is completed we should notify
+ * that the view was entered for autofill purposes. To minimize
+ * showing autofill for views not visible to the user we evaluate
+ * user visibility which cannot be done until the view is laid out.
+ */
+ static final int PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT = 0x8000000;
+
+ /* End of masks for mPrivateFlags3 */
+
+ /**
+ * Always allow a user to over-scroll this view, provided it is a
+ * view that can scroll.
+ *
+ * @see #getOverScrollMode()
+ * @see #setOverScrollMode(int)
+ */
+ public static final int OVER_SCROLL_ALWAYS = 0;
+
+ /**
+ * Allow a user to over-scroll this view only if the content is large
+ * enough to meaningfully scroll, provided it is a view that can scroll.
+ *
+ * @see #getOverScrollMode()
+ * @see #setOverScrollMode(int)
+ */
+ public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1;
+
+ /**
+ * Never allow a user to over-scroll this view.
+ *
+ * @see #getOverScrollMode()
+ * @see #setOverScrollMode(int)
+ */
+ public static final int OVER_SCROLL_NEVER = 2;
+
+ /**
+ * Special constant for {@link #setSystemUiVisibility(int)}: View has
+ * requested the system UI (status bar) to be visible (the default).
+ *
+ * @see #setSystemUiVisibility(int)
+ */
+ public static final int SYSTEM_UI_FLAG_VISIBLE = 0;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: View has requested the
+ * system UI to enter an unobtrusive "low profile" mode.
+ *
+ * <p>This is for use in games, book readers, video players, or any other
+ * "immersive" application where the usual system chrome is deemed too distracting.
+ *
+ * <p>In low profile mode, the status bar and/or navigation icons may dim.
+ *
+ * @see #setSystemUiVisibility(int)
+ */
+ public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: View has requested that the
+ * system navigation be temporarily hidden.
+ *
+ * <p>This is an even less obtrusive state than that called for by
+ * {@link #SYSTEM_UI_FLAG_LOW_PROFILE}; on devices that draw essential navigation controls
+ * (Home, Back, and the like) on screen, <code>SYSTEM_UI_FLAG_HIDE_NAVIGATION</code> will cause
+ * those to disappear. This is useful (in conjunction with the
+ * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN FLAG_LAYOUT_IN_SCREEN}
+ * window flags) for displaying content using every last pixel on the display.
+ *
+ * <p>There is a limitation: because navigation controls are so important, the least user
+ * interaction will cause them to reappear immediately. When this happens, both
+ * this flag and {@link #SYSTEM_UI_FLAG_FULLSCREEN} will be cleared automatically,
+ * so that both elements reappear at the same time.
+ *
+ * @see #setSystemUiVisibility(int)
+ */
+ public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: View has requested to go
+ * into the normal fullscreen mode so that its content can take over the screen
+ * while still allowing the user to interact with the application.
+ *
+ * <p>This has the same visual effect as
+ * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN
+ * WindowManager.LayoutParams.FLAG_FULLSCREEN},
+ * meaning that non-critical screen decorations (such as the status bar) will be
+ * hidden while the user is in the View's window, focusing the experience on
+ * that content. Unlike the window flag, if you are using ActionBar in
+ * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY
+ * Window.FEATURE_ACTION_BAR_OVERLAY}, then enabling this flag will also
+ * hide the action bar.
+ *
+ * <p>This approach to going fullscreen is best used over the window flag when
+ * it is a transient state -- that is, the application does this at certain
+ * points in its user interaction where it wants to allow the user to focus
+ * on content, but not as a continuous state. For situations where the application
+ * would like to simply stay full screen the entire time (such as a game that
+ * wants to take over the screen), the
+ * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN window flag}
+ * is usually a better approach. The state set here will be removed by the system
+ * in various situations (such as the user moving to another application) like
+ * the other system UI states.
+ *
+ * <p>When using this flag, the application should provide some easy facility
+ * for the user to go out of it. A common example would be in an e-book
+ * reader, where tapping on the screen brings back whatever screen and UI
+ * decorations that had been hidden while the user was immersed in reading
+ * the book.
+ *
+ * @see #setSystemUiVisibility(int)
+ */
+ public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: When using other layout
+ * flags, we would like a stable view of the content insets given to
+ * {@link #fitSystemWindows(Rect)}. This means that the insets seen there
+ * will always represent the worst case that the application can expect
+ * as a continuous state. In the stock Android UI this is the space for
+ * the system bar, nav bar, and status bar, but not more transient elements
+ * such as an input method.
+ *
+ * The stable layout your UI sees is based on the system UI modes you can
+ * switch to. That is, if you specify {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}
+ * then you will get a stable layout for changes of the
+ * {@link #SYSTEM_UI_FLAG_FULLSCREEN} mode; if you specify
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} and
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}, then you can transition
+ * to {@link #SYSTEM_UI_FLAG_FULLSCREEN} and {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}
+ * with a stable layout. (Note that you should avoid using
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION} by itself.)
+ *
+ * If you have set the window flag {@link WindowManager.LayoutParams#FLAG_FULLSCREEN}
+ * to hide the status bar (instead of using {@link #SYSTEM_UI_FLAG_FULLSCREEN}),
+ * then a hidden status bar will be considered a "stable" state for purposes
+ * here. This allows your UI to continually hide the status bar, while still
+ * using the system UI flags to hide the action bar while still retaining
+ * a stable layout. Note that changing the window fullscreen flag will never
+ * provide a stable layout for a clean transition.
+ *
+ * <p>If you are using ActionBar in
+ * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY
+ * Window.FEATURE_ACTION_BAR_OVERLAY}, this flag will also impact the
+ * insets it adds to those given to the application.
+ */
+ public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: View would like its window
+ * to be laid out as if it has requested
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, even if it currently hasn't. This
+ * allows it to avoid artifacts when switching in and out of that mode, at
+ * the expense that some of its user interface may be covered by screen
+ * decorations when they are shown. You can perform layout of your inner
+ * UI elements to account for the navigation system UI through the
+ * {@link #fitSystemWindows(Rect)} method.
+ */
+ public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: View would like its window
+ * to be laid out as if it has requested
+ * {@link #SYSTEM_UI_FLAG_FULLSCREEN}, even if it currently hasn't. This
+ * allows it to avoid artifacts when switching in and out of that mode, at
+ * the expense that some of its user interface may be covered by screen
+ * decorations when they are shown. You can perform layout of your inner
+ * UI elements to account for non-fullscreen system UI through the
+ * {@link #fitSystemWindows(Rect)} method.
+ */
+ public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: View would like to remain interactive when
+ * hiding the navigation bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. If this flag is
+ * not set, {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} will be force cleared by the system on any
+ * user interaction.
+ * <p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only
+ * has an effect when used in combination with that flag.</p>
+ */
+ public static final int SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: View would like to remain interactive when
+ * hiding the status bar with {@link #SYSTEM_UI_FLAG_FULLSCREEN} and/or hiding the navigation
+ * bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. Use this flag to create an immersive
+ * experience while also hiding the system bars. If this flag is not set,
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} will be force cleared by the system on any user
+ * interaction, and {@link #SYSTEM_UI_FLAG_FULLSCREEN} will be force-cleared by the system
+ * if the user swipes from the top of the screen.
+ * <p>When system bars are hidden in immersive mode, they can be revealed temporarily with
+ * system gestures, such as swiping from the top of the screen. These transient system bars
+ * will overlay app’s content, may have some degree of transparency, and will automatically
+ * hide after a short timeout.
+ * </p><p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_FULLSCREEN} and
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only has an effect when used in combination
+ * with one or both of those flags.</p>
+ */
+ public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
+ * is compatible with light status bar backgrounds.
+ *
+ * <p>For this to take effect, the window must request
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+ * FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
+ * FLAG_TRANSLUCENT_STATUS}.
+ *
+ * @see android.R.attr#windowLightStatusBar
+ */
+ public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
+
+ /**
+ * This flag was previously used for a private API. DO NOT reuse it for a public API as it might
+ * trigger undefined behavior on older platforms with apps compiled against a new SDK.
+ */
+ private static final int SYSTEM_UI_RESERVED_LEGACY1 = 0x00004000;
+
+ /**
+ * This flag was previously used for a private API. DO NOT reuse it for a public API as it might
+ * trigger undefined behavior on older platforms with apps compiled against a new SDK.
+ */
+ private static final int SYSTEM_UI_RESERVED_LEGACY2 = 0x00010000;
+
+ /**
+ * Flag for {@link #setSystemUiVisibility(int)}: Requests the navigation bar to draw in a mode
+ * that is compatible with light navigation bar backgrounds.
+ *
+ * <p>For this to take effect, the window must request
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+ * FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION
+ * FLAG_TRANSLUCENT_NAVIGATION}.
+ *
+ * @see android.R.attr#windowLightNavigationBar
+ */
+ public static final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 0x00000010;
+
+ /**
+ * @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead.
+ */
+ @Deprecated
+ public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE;
+
+ /**
+ * @deprecated Use {@link #SYSTEM_UI_FLAG_VISIBLE} instead.
+ */
+ @Deprecated
+ public static final int STATUS_BAR_VISIBLE = SYSTEM_UI_FLAG_VISIBLE;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to make the status bar not expandable. Unless you also
+ * set {@link #STATUS_BAR_DISABLE_NOTIFICATION_ICONS}, new notifications will continue to show.
+ */
+ public static final int STATUS_BAR_DISABLE_EXPAND = 0x00010000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to hide notification icons and scrolling ticker text.
+ */
+ public static final int STATUS_BAR_DISABLE_NOTIFICATION_ICONS = 0x00020000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to disable incoming notification alerts. This will not block
+ * icons, but it will block sound, vibrating and other visual or aural notifications.
+ */
+ public static final int STATUS_BAR_DISABLE_NOTIFICATION_ALERTS = 0x00040000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to hide only the scrolling ticker. Note that
+ * {@link #STATUS_BAR_DISABLE_NOTIFICATION_ICONS} implies
+ * {@link #STATUS_BAR_DISABLE_NOTIFICATION_TICKER}.
+ */
+ public static final int STATUS_BAR_DISABLE_NOTIFICATION_TICKER = 0x00080000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to hide the center system info area.
+ */
+ public static final int STATUS_BAR_DISABLE_SYSTEM_INFO = 0x00100000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to hide only the home button. Don't use this
+ * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+ */
+ public static final int STATUS_BAR_DISABLE_HOME = 0x00200000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to hide only the back button. Don't use this
+ * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+ */
+ public static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to hide only the clock. You might use this if your activity has
+ * its own clock making the status bar's clock redundant.
+ */
+ public static final int STATUS_BAR_DISABLE_CLOCK = 0x00800000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to hide only the recent apps button. Don't use this
+ * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+ */
+ public static final int STATUS_BAR_DISABLE_RECENT = 0x01000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to disable the global search gesture. Don't use this
+ * unless you're a special part of the system UI (i.e., setup wizard, keyguard).
+ */
+ public static final int STATUS_BAR_DISABLE_SEARCH = 0x02000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the status bar is displayed in transient mode.
+ */
+ public static final int STATUS_BAR_TRANSIENT = 0x04000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the navigation bar is displayed in transient mode.
+ */
+ public static final int NAVIGATION_BAR_TRANSIENT = 0x08000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the hidden status bar would like to be shown.
+ */
+ public static final int STATUS_BAR_UNHIDE = 0x10000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the hidden navigation bar would like to be shown.
+ */
+ public static final int NAVIGATION_BAR_UNHIDE = 0x20000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the status bar is displayed in translucent mode.
+ */
+ public static final int STATUS_BAR_TRANSLUCENT = 0x40000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the navigation bar is displayed in translucent mode.
+ */
+ public static final int NAVIGATION_BAR_TRANSLUCENT = 0x80000000;
+
+ /**
+ * @hide
+ *
+ * Makes navigation bar transparent (but not the status bar).
+ */
+ public static final int NAVIGATION_BAR_TRANSPARENT = 0x00008000;
+
+ /**
+ * @hide
+ *
+ * Makes status bar transparent (but not the navigation bar).
+ */
+ public static final int STATUS_BAR_TRANSPARENT = 0x00000008;
+
+ /**
+ * @hide
+ *
+ * Makes both status bar and navigation bar transparent.
+ */
+ public static final int SYSTEM_UI_TRANSPARENT = NAVIGATION_BAR_TRANSPARENT
+ | STATUS_BAR_TRANSPARENT;
+
+ /**
+ * @hide
+ */
+ public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00003FF7;
+
+ /**
+ * These are the system UI flags that can be cleared by events outside
+ * of an application. Currently this is just the ability to tap on the
+ * screen while hiding the navigation bar to have it return.
+ * @hide
+ */
+ public static final int SYSTEM_UI_CLEARABLE_FLAGS =
+ SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | SYSTEM_UI_FLAG_FULLSCREEN;
+
+ /**
+ * Flags that can impact the layout in relation to system UI.
+ */
+ public static final int SYSTEM_UI_LAYOUT_FLAGS =
+ SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = { FIND_VIEWS_WITH_TEXT, FIND_VIEWS_WITH_CONTENT_DESCRIPTION })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FindViewFlags {}
+
+ /**
+ * Find views that render the specified text.
+ *
+ * @see #findViewsWithText(ArrayList, CharSequence, int)
+ */
+ public static final int FIND_VIEWS_WITH_TEXT = 0x00000001;
+
+ /**
+ * Find find views that contain the specified content description.
+ *
+ * @see #findViewsWithText(ArrayList, CharSequence, int)
+ */
+ public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 0x00000002;
+
+ /**
+ * Find views that contain {@link AccessibilityNodeProvider}. Such
+ * a View is a root of virtual view hierarchy and may contain the searched
+ * text. If this flag is set Views with providers are automatically
+ * added and it is a responsibility of the client to call the APIs of
+ * the provider to determine whether the virtual tree rooted at this View
+ * contains the text, i.e. getting the list of {@link AccessibilityNodeInfo}s
+ * representing the virtual views with this text.
+ *
+ * @see #findViewsWithText(ArrayList, CharSequence, int)
+ *
+ * @hide
+ */
+ public static final int FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS = 0x00000004;
+
+ /**
+ * The undefined cursor position.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
+
+ /**
+ * Indicates that the screen has changed state and is now off.
+ *
+ * @see #onScreenStateChanged(int)
+ */
+ public static final int SCREEN_STATE_OFF = 0x0;
+
+ /**
+ * Indicates that the screen has changed state and is now on.
+ *
+ * @see #onScreenStateChanged(int)
+ */
+ public static final int SCREEN_STATE_ON = 0x1;
+
+ /**
+ * Indicates no axis of view scrolling.
+ */
+ public static final int SCROLL_AXIS_NONE = 0;
+
+ /**
+ * Indicates scrolling along the horizontal axis.
+ */
+ public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0;
+
+ /**
+ * Indicates scrolling along the vertical axis.
+ */
+ public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+
+ /**
+ * Controls the over-scroll mode for this view.
+ * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
+ * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
+ * and {@link #OVER_SCROLL_NEVER}.
+ */
+ private int mOverScrollMode;
+
+ /**
+ * The parent this view is attached to.
+ * {@hide}
+ *
+ * @see #getParent()
+ */
+ protected ViewParent mParent;
+
+ /**
+ * {@hide}
+ */
+ AttachInfo mAttachInfo;
+
+ /**
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(mask = PFLAG_FORCE_LAYOUT, equals = PFLAG_FORCE_LAYOUT,
+ name = "FORCE_LAYOUT"),
+ @ViewDebug.FlagToString(mask = PFLAG_LAYOUT_REQUIRED, equals = PFLAG_LAYOUT_REQUIRED,
+ name = "LAYOUT_REQUIRED"),
+ @ViewDebug.FlagToString(mask = PFLAG_DRAWING_CACHE_VALID, equals = PFLAG_DRAWING_CACHE_VALID,
+ name = "DRAWING_CACHE_INVALID", outputIf = false),
+ @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "DRAWN", outputIf = true),
+ @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "NOT_DRAWN", outputIf = false),
+ @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY_OPAQUE, name = "DIRTY_OPAQUE"),
+ @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY, name = "DIRTY")
+ }, formatToHexString = true)
+
+ /* @hide */
+ public int mPrivateFlags;
+ int mPrivateFlags2;
+ int mPrivateFlags3;
+
+ /**
+ * This view's request for the visibility of the status bar.
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
+ equals = SYSTEM_UI_FLAG_LOW_PROFILE,
+ name = "SYSTEM_UI_FLAG_LOW_PROFILE", outputIf = true),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+ equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+ name = "SYSTEM_UI_FLAG_HIDE_NAVIGATION", outputIf = true),
+ @ViewDebug.FlagToString(mask = PUBLIC_STATUS_BAR_VISIBILITY_MASK,
+ equals = SYSTEM_UI_FLAG_VISIBLE,
+ name = "SYSTEM_UI_FLAG_VISIBLE", outputIf = true)
+ }, formatToHexString = true)
+ int mSystemUiVisibility;
+
+ /**
+ * Reference count for transient state.
+ * @see #setHasTransientState(boolean)
+ */
+ int mTransientStateCount = 0;
+
+ /**
+ * Count of how many windows this view has been attached to.
+ */
+ int mWindowAttachCount;
+
+ /**
+ * The layout parameters associated with this view and used by the parent
+ * {@link android.view.ViewGroup} to determine how this view should be
+ * laid out.
+ * {@hide}
+ */
+ protected ViewGroup.LayoutParams mLayoutParams;
+
+ /**
+ * The view flags hold various views states.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(formatToHexString = true)
+ int mViewFlags;
+
+ static class TransformationInfo {
+ /**
+ * The transform matrix for the View. This transform is calculated internally
+ * based on the translation, rotation, and scale properties.
+ *
+ * Do *not* use this variable directly; instead call getMatrix(), which will
+ * load the value from the View's RenderNode.
+ */
+ private final Matrix mMatrix = new Matrix();
+
+ /**
+ * The inverse transform matrix for the View. This transform is calculated
+ * internally based on the translation, rotation, and scale properties.
+ *
+ * Do *not* use this variable directly; instead call getInverseMatrix(),
+ * which will load the value from the View's RenderNode.
+ */
+ private Matrix mInverseMatrix;
+
+ /**
+ * The opacity of the View. This is a value from 0 to 1, where 0 means
+ * completely transparent and 1 means completely opaque.
+ */
+ @ViewDebug.ExportedProperty
+ float mAlpha = 1f;
+
+ /**
+ * The opacity of the view as manipulated by the Fade transition. This is a hidden
+ * property only used by transitions, which is composited with the other alpha
+ * values to calculate the final visual alpha value.
+ */
+ float mTransitionAlpha = 1f;
+ }
+
+ /** @hide */
+ public TransformationInfo mTransformationInfo;
+
+ /**
+ * Current clip bounds. to which all drawing of this view are constrained.
+ */
+ Rect mClipBounds = null;
+
+ private boolean mLastIsOpaque;
+
+ /**
+ * The distance in pixels from the left edge of this view's parent
+ * to the left edge of this view.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ protected int mLeft;
+ /**
+ * The distance in pixels from the left edge of this view's parent
+ * to the right edge of this view.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ protected int mRight;
+ /**
+ * The distance in pixels from the top edge of this view's parent
+ * to the top edge of this view.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ protected int mTop;
+ /**
+ * The distance in pixels from the top edge of this view's parent
+ * to the bottom edge of this view.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ protected int mBottom;
+
+ /**
+ * The offset, in pixels, by which the content of this view is scrolled
+ * horizontally.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "scrolling")
+ protected int mScrollX;
+ /**
+ * The offset, in pixels, by which the content of this view is scrolled
+ * vertically.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "scrolling")
+ protected int mScrollY;
+
+ /**
+ * The left padding in pixels, that is the distance in pixels between the
+ * left edge of this view and the left edge of its content.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ protected int mPaddingLeft = 0;
+ /**
+ * The right padding in pixels, that is the distance in pixels between the
+ * right edge of this view and the right edge of its content.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ protected int mPaddingRight = 0;
+ /**
+ * The top padding in pixels, that is the distance in pixels between the
+ * top edge of this view and the top edge of its content.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ protected int mPaddingTop;
+ /**
+ * The bottom padding in pixels, that is the distance in pixels between the
+ * bottom edge of this view and the bottom edge of its content.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ protected int mPaddingBottom;
+
+ /**
+ * The layout insets in pixels, that is the distance in pixels between the
+ * visible edges of this view its bounds.
+ */
+ private Insets mLayoutInsets;
+
+ /**
+ * Briefly describes the view and is primarily used for accessibility support.
+ */
+ private CharSequence mContentDescription;
+
+ /**
+ * Specifies the id of a view for which this view serves as a label for
+ * accessibility purposes.
+ */
+ private int mLabelForId = View.NO_ID;
+
+ /**
+ * Predicate for matching labeled view id with its label for
+ * accessibility purposes.
+ */
+ private MatchLabelForPredicate mMatchLabelForPredicate;
+
+ /**
+ * Specifies a view before which this one is visited in accessibility traversal.
+ */
+ private int mAccessibilityTraversalBeforeId = NO_ID;
+
+ /**
+ * Specifies a view after which this one is visited in accessibility traversal.
+ */
+ private int mAccessibilityTraversalAfterId = NO_ID;
+
+ /**
+ * Predicate for matching a view by its id.
+ */
+ private MatchIdPredicate mMatchIdPredicate;
+
+ /**
+ * Cache the paddingRight set by the user to append to the scrollbar's size.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ protected int mUserPaddingRight;
+
+ /**
+ * Cache the paddingBottom set by the user to append to the scrollbar's size.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ protected int mUserPaddingBottom;
+
+ /**
+ * Cache the paddingLeft set by the user to append to the scrollbar's size.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ protected int mUserPaddingLeft;
+
+ /**
+ * Cache the paddingStart set by the user to append to the scrollbar's size.
+ *
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ int mUserPaddingStart;
+
+ /**
+ * Cache the paddingEnd set by the user to append to the scrollbar's size.
+ *
+ */
+ @ViewDebug.ExportedProperty(category = "padding")
+ int mUserPaddingEnd;
+
+ /**
+ * Cache initial left padding.
+ *
+ * @hide
+ */
+ int mUserPaddingLeftInitial;
+
+ /**
+ * Cache initial right padding.
+ *
+ * @hide
+ */
+ int mUserPaddingRightInitial;
+
+ /**
+ * Default undefined padding
+ */
+ private static final int UNDEFINED_PADDING = Integer.MIN_VALUE;
+
+ /**
+ * Cache if a left padding has been defined
+ */
+ private boolean mLeftPaddingDefined = false;
+
+ /**
+ * Cache if a right padding has been defined
+ */
+ private boolean mRightPaddingDefined = false;
+
+ /**
+ * @hide
+ */
+ int mOldWidthMeasureSpec = Integer.MIN_VALUE;
+ /**
+ * @hide
+ */
+ int mOldHeightMeasureSpec = Integer.MIN_VALUE;
+
+ private LongSparseLongArray mMeasureCache;
+
+ @ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_")
+ private Drawable mBackground;
+ private TintInfo mBackgroundTint;
+
+ @ViewDebug.ExportedProperty(deepExport = true, prefix = "fg_")
+ private ForegroundInfo mForegroundInfo;
+
+ private Drawable mScrollIndicatorDrawable;
+
+ /**
+ * RenderNode used for backgrounds.
+ * <p>
+ * When non-null and valid, this is expected to contain an up-to-date copy
+ * of the background drawable. It is cleared on temporary detach, and reset
+ * on cleanup.
+ */
+ private RenderNode mBackgroundRenderNode;
+
+ private int mBackgroundResource;
+ private boolean mBackgroundSizeChanged;
+
+ /** The default focus highlight.
+ * @see #mDefaultFocusHighlightEnabled
+ * @see Drawable#hasFocusStateSpecified()
+ */
+ private Drawable mDefaultFocusHighlight;
+ private Drawable mDefaultFocusHighlightCache;
+ private boolean mDefaultFocusHighlightSizeChanged;
+ /**
+ * True if the default focus highlight is needed on the target device.
+ */
+ private static boolean sUseDefaultFocusHighlight;
+
+ private String mTransitionName;
+
+ static class TintInfo {
+ ColorStateList mTintList;
+ PorterDuff.Mode mTintMode;
+ boolean mHasTintMode;
+ boolean mHasTintList;
+ }
+
+ private static class ForegroundInfo {
+ private Drawable mDrawable;
+ private TintInfo mTintInfo;
+ private int mGravity = Gravity.FILL;
+ private boolean mInsidePadding = true;
+ private boolean mBoundsChanged = true;
+ private final Rect mSelfBounds = new Rect();
+ private final Rect mOverlayBounds = new Rect();
+ }
+
+ static class ListenerInfo {
+ /**
+ * Listener used to dispatch focus change events.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnFocusChangeListener mOnFocusChangeListener;
+
+ /**
+ * Listeners for layout change events.
+ */
+ private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
+
+ protected OnScrollChangeListener mOnScrollChangeListener;
+
+ /**
+ * Listeners for attach events.
+ */
+ private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
+
+ /**
+ * Listener used to dispatch click events.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ public OnClickListener mOnClickListener;
+
+ /**
+ * Listener used to dispatch long click events.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnLongClickListener mOnLongClickListener;
+
+ /**
+ * Listener used to dispatch context click events. This field should be made private, so it
+ * is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnContextClickListener mOnContextClickListener;
+
+ /**
+ * Listener used to build the context menu.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnCreateContextMenuListener mOnCreateContextMenuListener;
+
+ private OnKeyListener mOnKeyListener;
+
+ private OnTouchListener mOnTouchListener;
+
+ private OnHoverListener mOnHoverListener;
+
+ private OnGenericMotionListener mOnGenericMotionListener;
+
+ private OnDragListener mOnDragListener;
+
+ private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
+
+ OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
+
+ OnCapturedPointerListener mOnCapturedPointerListener;
+ }
+
+ ListenerInfo mListenerInfo;
+
+ private static class TooltipInfo {
+ /**
+ * Text to be displayed in a tooltip popup.
+ */
+ @Nullable
+ CharSequence mTooltipText;
+
+ /**
+ * View-relative position of the tooltip anchor point.
+ */
+ int mAnchorX;
+ int mAnchorY;
+
+ /**
+ * The tooltip popup.
+ */
+ @Nullable
+ TooltipPopup mTooltipPopup;
+
+ /**
+ * Set to true if the tooltip was shown as a result of a long click.
+ */
+ boolean mTooltipFromLongClick;
+
+ /**
+ * Keep these Runnables so that they can be used to reschedule.
+ */
+ Runnable mShowTooltipRunnable;
+ Runnable mHideTooltipRunnable;
+ }
+
+ TooltipInfo mTooltipInfo;
+
+ // Temporary values used to hold (x,y) coordinates when delegating from the
+ // two-arg performLongClick() method to the legacy no-arg version.
+ private float mLongClickX = Float.NaN;
+ private float mLongClickY = Float.NaN;
+
+ /**
+ * The application environment this view lives in.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(deepExport = true)
+ protected Context mContext;
+
+ private final Resources mResources;
+
+ private ScrollabilityCache mScrollCache;
+
+ private int[] mDrawableState = null;
+
+ ViewOutlineProvider mOutlineProvider = ViewOutlineProvider.BACKGROUND;
+
+ /**
+ * Animator that automatically runs based on state changes.
+ */
+ private StateListAnimator mStateListAnimator;
+
+ /**
+ * When this view has focus and the next focus is {@link #FOCUS_LEFT},
+ * the user may specify which view to go to next.
+ */
+ private int mNextFocusLeftId = View.NO_ID;
+
+ /**
+ * When this view has focus and the next focus is {@link #FOCUS_RIGHT},
+ * the user may specify which view to go to next.
+ */
+ private int mNextFocusRightId = View.NO_ID;
+
+ /**
+ * When this view has focus and the next focus is {@link #FOCUS_UP},
+ * the user may specify which view to go to next.
+ */
+ private int mNextFocusUpId = View.NO_ID;
+
+ /**
+ * When this view has focus and the next focus is {@link #FOCUS_DOWN},
+ * the user may specify which view to go to next.
+ */
+ private int mNextFocusDownId = View.NO_ID;
+
+ /**
+ * When this view has focus and the next focus is {@link #FOCUS_FORWARD},
+ * the user may specify which view to go to next.
+ */
+ int mNextFocusForwardId = View.NO_ID;
+
+ /**
+ * User-specified next keyboard navigation cluster in the {@link #FOCUS_FORWARD} direction.
+ *
+ * @see #findUserSetNextKeyboardNavigationCluster(View, int)
+ */
+ int mNextClusterForwardId = View.NO_ID;
+
+ /**
+ * Whether this View should use a default focus highlight when it gets focused but doesn't
+ * have {@link android.R.attr#state_focused} defined in its background.
+ */
+ boolean mDefaultFocusHighlightEnabled = true;
+
+ private CheckForLongPress mPendingCheckForLongPress;
+ private CheckForTap mPendingCheckForTap = null;
+ private PerformClick mPerformClick;
+ private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
+
+ private UnsetPressedState mUnsetPressedState;
+
+ /**
+ * Whether the long press's action has been invoked. The tap's action is invoked on the
+ * up event while a long press is invoked as soon as the long press duration is reached, so
+ * a long press could be performed before the tap is checked, in which case the tap's action
+ * should not be invoked.
+ */
+ private boolean mHasPerformedLongPress;
+
+ /**
+ * Whether a context click button is currently pressed down. This is true when the stylus is
+ * touching the screen and the primary button has been pressed, or if a mouse's right button is
+ * pressed. This is false once the button is released or if the stylus has been lifted.
+ */
+ private boolean mInContextButtonPress;
+
+ /**
+ * Whether the next up event should be ignored for the purposes of gesture recognition. This is
+ * true after a stylus button press has occured, when the next up event should not be recognized
+ * as a tap.
+ */
+ private boolean mIgnoreNextUpEvent;
+
+ /**
+ * The minimum height of the view. We'll try our best to have the height
+ * of this view to at least this amount.
+ */
+ @ViewDebug.ExportedProperty(category = "measurement")
+ private int mMinHeight;
+
+ /**
+ * The minimum width of the view. We'll try our best to have the width
+ * of this view to at least this amount.
+ */
+ @ViewDebug.ExportedProperty(category = "measurement")
+ private int mMinWidth;
+
+ /**
+ * The delegate to handle touch events that are physically in this view
+ * but should be handled by another view.
+ */
+ private TouchDelegate mTouchDelegate = null;
+
+ /**
+ * Solid color to use as a background when creating the drawing cache. Enables
+ * the cache to use 16 bit bitmaps instead of 32 bit.
+ */
+ private int mDrawingCacheBackgroundColor = 0;
+
+ /**
+ * Special tree observer used when mAttachInfo is null.
+ */
+ private ViewTreeObserver mFloatingTreeObserver;
+
+ /**
+ * Cache the touch slop from the context that created the view.
+ */
+ private int mTouchSlop;
+
+ /**
+ * Object that handles automatic animation of view properties.
+ */
+ private ViewPropertyAnimator mAnimator = null;
+
+ /**
+ * List of registered FrameMetricsObservers.
+ */
+ private ArrayList<FrameMetricsObserver> mFrameMetricsObservers;
+
+ /**
+ * Flag indicating that a drag can cross window boundaries. When
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
+ * with this flag set, all visible applications with targetSdkVersion >=
+ * {@link android.os.Build.VERSION_CODES#N API 24} will be able to participate
+ * in the drag operation and receive the dragged content.
+ *
+ * <p>If this is the only flag set, then the drag recipient will only have access to text data
+ * and intents contained in the {@link ClipData} object. Access to URIs contained in the
+ * {@link ClipData} is determined by other DRAG_FLAG_GLOBAL_* flags</p>
+ */
+ public static final int DRAG_FLAG_GLOBAL = 1 << 8; // 256
+
+ /**
+ * When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
+ * request read access to the content URI(s) contained in the {@link ClipData} object.
+ * @see android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION
+ */
+ public static final int DRAG_FLAG_GLOBAL_URI_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+ /**
+ * When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
+ * request write access to the content URI(s) contained in the {@link ClipData} object.
+ * @see android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ */
+ public static final int DRAG_FLAG_GLOBAL_URI_WRITE = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+
+ /**
+ * When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
+ * #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant can be persisted across device
+ * reboots until explicitly revoked with
+ * {@link android.content.Context#revokeUriPermission(Uri, int)} Context.revokeUriPermission}.
+ * @see android.content.Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ */
+ public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION =
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
+
+ /**
+ * When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
+ * #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant applies to any URI that is a prefix
+ * match against the original granted URI.
+ * @see android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
+ */
+ public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION =
+ Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+ /**
+ * Flag indicating that the drag shadow will be opaque. When
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
+ * with this flag set, the drag shadow will be opaque, otherwise, it will be semitransparent.
+ */
+ public static final int DRAG_FLAG_OPAQUE = 1 << 9;
+
+ /**
+ * Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
+ */
+ private float mVerticalScrollFactor;
+
+ /**
+ * Position of the vertical scroll bar.
+ */
+ private int mVerticalScrollbarPosition;
+
+ /**
+ * Position the scroll bar at the default position as determined by the system.
+ */
+ public static final int SCROLLBAR_POSITION_DEFAULT = 0;
+
+ /**
+ * Position the scroll bar along the left edge.
+ */
+ public static final int SCROLLBAR_POSITION_LEFT = 1;
+
+ /**
+ * Position the scroll bar along the right edge.
+ */
+ public static final int SCROLLBAR_POSITION_RIGHT = 2;
+
+ /**
+ * Indicates that the view does not have a layer.
+ *
+ * @see #getLayerType()
+ * @see #setLayerType(int, android.graphics.Paint)
+ * @see #LAYER_TYPE_SOFTWARE
+ * @see #LAYER_TYPE_HARDWARE
+ */
+ public static final int LAYER_TYPE_NONE = 0;
+
+ /**
+ * <p>Indicates that the view has a software layer. A software layer is backed
+ * by a bitmap and causes the view to be rendered using Android's software
+ * rendering pipeline, even if hardware acceleration is enabled.</p>
+ *
+ * <p>Software layers have various usages:</p>
+ * <p>When the application is not using hardware acceleration, a software layer
+ * is useful to apply a specific color filter and/or blending mode and/or
+ * translucency to a view and all its children.</p>
+ * <p>When the application is using hardware acceleration, a software layer
+ * is useful to render drawing primitives not supported by the hardware
+ * accelerated pipeline. It can also be used to cache a complex view tree
+ * into a texture and reduce the complexity of drawing operations. For instance,
+ * when animating a complex view tree with a translation, a software layer can
+ * be used to render the view tree only once.</p>
+ * <p>Software layers should be avoided when the affected view tree updates
+ * often. Every update will require to re-render the software layer, which can
+ * potentially be slow (particularly when hardware acceleration is turned on
+ * since the layer will have to be uploaded into a hardware texture after every
+ * update.)</p>
+ *
+ * @see #getLayerType()
+ * @see #setLayerType(int, android.graphics.Paint)
+ * @see #LAYER_TYPE_NONE
+ * @see #LAYER_TYPE_HARDWARE
+ */
+ public static final int LAYER_TYPE_SOFTWARE = 1;
+
+ /**
+ * <p>Indicates that the view has a hardware layer. A hardware layer is backed
+ * by a hardware specific texture (generally Frame Buffer Objects or FBO on
+ * OpenGL hardware) and causes the view to be rendered using Android's hardware
+ * rendering pipeline, but only if hardware acceleration is turned on for the
+ * view hierarchy. When hardware acceleration is turned off, hardware layers
+ * behave exactly as {@link #LAYER_TYPE_SOFTWARE software layers}.</p>
+ *
+ * <p>A hardware layer is useful to apply a specific color filter and/or
+ * blending mode and/or translucency to a view and all its children.</p>
+ * <p>A hardware layer can be used to cache a complex view tree into a
+ * texture and reduce the complexity of drawing operations. For instance,
+ * when animating a complex view tree with a translation, a hardware layer can
+ * be used to render the view tree only once.</p>
+ * <p>A hardware layer can also be used to increase the rendering quality when
+ * rotation transformations are applied on a view. It can also be used to
+ * prevent potential clipping issues when applying 3D transforms on a view.</p>
+ *
+ * @see #getLayerType()
+ * @see #setLayerType(int, android.graphics.Paint)
+ * @see #LAYER_TYPE_NONE
+ * @see #LAYER_TYPE_SOFTWARE
+ */
+ public static final int LAYER_TYPE_HARDWARE = 2;
+
+ @ViewDebug.ExportedProperty(category = "drawing", mapping = {
+ @ViewDebug.IntToString(from = LAYER_TYPE_NONE, to = "NONE"),
+ @ViewDebug.IntToString(from = LAYER_TYPE_SOFTWARE, to = "SOFTWARE"),
+ @ViewDebug.IntToString(from = LAYER_TYPE_HARDWARE, to = "HARDWARE")
+ })
+ int mLayerType = LAYER_TYPE_NONE;
+ Paint mLayerPaint;
+
+ /**
+ * Set to true when drawing cache is enabled and cannot be created.
+ *
+ * @hide
+ */
+ public boolean mCachingFailed;
+ private Bitmap mDrawingCache;
+ private Bitmap mUnscaledDrawingCache;
+
+ /**
+ * RenderNode holding View properties, potentially holding a DisplayList of View content.
+ * <p>
+ * When non-null and valid, this is expected to contain an up-to-date copy
+ * of the View content. Its DisplayList content is cleared on temporary detach and reset on
+ * cleanup.
+ */
+ final RenderNode mRenderNode;
+
+ /**
+ * Set to true when the view is sending hover accessibility events because it
+ * is the innermost hovered view.
+ */
+ private boolean mSendingHoverAccessibilityEvents;
+
+ /**
+ * Delegate for injecting accessibility functionality.
+ */
+ AccessibilityDelegate mAccessibilityDelegate;
+
+ /**
+ * The view's overlay layer. Developers get a reference to the overlay via getOverlay()
+ * and add/remove objects to/from the overlay directly through the Overlay methods.
+ */
+ ViewOverlay mOverlay;
+
+ /**
+ * The currently active parent view for receiving delegated nested scrolling events.
+ * This is set by {@link #startNestedScroll(int)} during a touch interaction and cleared
+ * by {@link #stopNestedScroll()} at the same point where we clear
+ * requestDisallowInterceptTouchEvent.
+ */
+ private ViewParent mNestedScrollingParent;
+
+ /**
+ * Consistency verifier for debugging purposes.
+ * @hide
+ */
+ protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
+ private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+
+ private int[] mTempNestedScrollConsumed;
+
+ /**
+ * An overlay is going to draw this View instead of being drawn as part of this
+ * View's parent. mGhostView is the View in the Overlay that must be invalidated
+ * when this view is invalidated.
+ */
+ GhostView mGhostView;
+
+ /**
+ * Holds pairs of adjacent attribute data: attribute name followed by its value.
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "attributes", hasAdjacentMapping = true)
+ public String[] mAttributes;
+
+ /**
+ * Maps a Resource id to its name.
+ */
+ private static SparseArray<String> mAttributeMap;
+
+ /**
+ * Queue of pending runnables. Used to postpone calls to post() until this
+ * view is attached and has a handler.
+ */
+ private HandlerActionQueue mRunQueue;
+
+ /**
+ * The pointer icon when the mouse hovers on this view. The default is null.
+ */
+ private PointerIcon mPointerIcon;
+
+ /**
+ * @hide
+ */
+ String mStartActivityRequestWho;
+
+ @Nullable
+ private RoundScrollbarRenderer mRoundScrollbarRenderer;
+
+ /** Used to delay visibility updates sent to the autofill manager */
+ private Handler mVisibilityChangeForAutofillHandler;
+
+ /**
+ * Simple constructor to use when creating a view from code.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ */
+ public View(Context context) {
+ mContext = context;
+ mResources = context != null ? context.getResources() : null;
+ mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
+ // Set some flags defaults
+ mPrivateFlags2 =
+ (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
+ (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
+ (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
+ (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
+ (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
+ (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
+ mUserPaddingStart = UNDEFINED_PADDING;
+ mUserPaddingEnd = UNDEFINED_PADDING;
+ mRenderNode = RenderNode.create(getClass().getName(), this);
+
+ if (!sCompatibilityDone && context != null) {
+ final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+
+ // Older apps may need this compatibility hack for measurement.
+ sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+ // Older apps expect onMeasure() to always be called on a layout pass, regardless
+ // of whether a layout was requested on that View.
+ sIgnoreMeasureCache = targetSdkVersion < Build.VERSION_CODES.KITKAT;
+
+ Canvas.sCompatibilityRestore = targetSdkVersion < Build.VERSION_CODES.M;
+ Canvas.sCompatibilitySetBitmap = targetSdkVersion < Build.VERSION_CODES.O;
+
+ // In M and newer, our widgets can pass a "hint" value in the size
+ // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
+ // know what the expected parent size is going to be, so e.g. list items can size
+ // themselves at 1/3 the size of their container. It breaks older apps though,
+ // specifically apps that use some popular open source libraries.
+ sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;
+
+ // Old versions of the platform would give different results from
+ // LinearLayout measurement passes using EXACTLY and non-EXACTLY
+ // modes, so we always need to run an additional EXACTLY pass.
+ sAlwaysRemeasureExactly = targetSdkVersion <= Build.VERSION_CODES.M;
+
+ // Prior to N, layout params could change without requiring a
+ // subsequent call to setLayoutParams() and they would usually
+ // work. Partial layout breaks this assumption.
+ sLayoutParamsAlwaysChanged = targetSdkVersion <= Build.VERSION_CODES.M;
+
+ // Prior to N, TextureView would silently ignore calls to setBackground/setForeground.
+ // On N+, we throw, but that breaks compatibility with apps that use these methods.
+ sTextureViewIgnoresDrawableSetters = targetSdkVersion <= Build.VERSION_CODES.M;
+
+ // Prior to N, we would drop margins in LayoutParam conversions. The fix triggers bugs
+ // in apps so we target check it to avoid breaking existing apps.
+ sPreserveMarginParamsInLayoutParamConversion =
+ targetSdkVersion >= Build.VERSION_CODES.N;
+
+ sCascadedDragDrop = targetSdkVersion < Build.VERSION_CODES.N;
+
+ sHasFocusableExcludeAutoFocusable = targetSdkVersion < Build.VERSION_CODES.O;
+
+ sAutoFocusableOffUIThreadWontNotifyParents = targetSdkVersion < Build.VERSION_CODES.O;
+
+ sUseDefaultFocusHighlight = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_useDefaultFocusHighlight);
+
+ sCompatibilityDone = true;
+ }
+ }
+
+ /**
+ * Constructor that is called when inflating a view from XML. This is called
+ * when a view is being constructed from an XML file, supplying attributes
+ * that were specified in the XML file. This version uses a default style of
+ * 0, so the only attribute values applied are those in the Context's Theme
+ * and the given AttributeSet.
+ *
+ * <p>
+ * The method onFinishInflate() will be called after all children have been
+ * added.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @see #View(Context, AttributeSet, int)
+ */
+ public View(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Perform inflation from XML and apply a class-specific base style from a
+ * theme attribute. This constructor of View allows subclasses to use their
+ * own base style when they are inflating. For example, a Button class's
+ * constructor would call this version of the super class constructor and
+ * supply <code>R.attr.buttonStyle</code> for <var>defStyleAttr</var>; this
+ * allows the theme's button style to modify all of the base view attributes
+ * (in particular its background) as well as the Button class's attributes.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @see #View(Context, AttributeSet)
+ */
+ public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Perform inflation from XML and apply a class-specific base style from a
+ * theme attribute or style resource. This constructor of View allows
+ * subclasses to use their own base style when they are inflating.
+ * <p>
+ * When determining the final value of a particular attribute, there are
+ * four inputs that come into play:
+ * <ol>
+ * <li>Any attribute values in the given AttributeSet.
+ * <li>The style resource specified in the AttributeSet (named "style").
+ * <li>The default style specified by <var>defStyleAttr</var>.
+ * <li>The default style specified by <var>defStyleRes</var>.
+ * <li>The base values in this theme.
+ * </ol>
+ * <p>
+ * Each of these inputs is considered in-order, with the first listed taking
+ * precedence over the following ones. In other words, if in the
+ * AttributeSet you have supplied <code>&lt;Button * textColor="#ff000000"&gt;</code>
+ * , then the button's text will <em>always</em> be black, regardless of
+ * what is specified in any of the styles.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ * @see #View(Context, AttributeSet, int)
+ */
+ public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ this(context);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
+
+ if (mDebugViewAttributes) {
+ saveAttributeData(attrs, a);
+ }
+
+ Drawable background = null;
+
+ int leftPadding = -1;
+ int topPadding = -1;
+ int rightPadding = -1;
+ int bottomPadding = -1;
+ int startPadding = UNDEFINED_PADDING;
+ int endPadding = UNDEFINED_PADDING;
+
+ int padding = -1;
+ int paddingHorizontal = -1;
+ int paddingVertical = -1;
+
+ int viewFlagValues = 0;
+ int viewFlagMasks = 0;
+
+ boolean setScrollContainer = false;
+
+ int x = 0;
+ int y = 0;
+
+ float tx = 0;
+ float ty = 0;
+ float tz = 0;
+ float elevation = 0;
+ float rotation = 0;
+ float rotationX = 0;
+ float rotationY = 0;
+ float sx = 1f;
+ float sy = 1f;
+ boolean transformSet = false;
+
+ int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
+ int overScrollMode = mOverScrollMode;
+ boolean initializeScrollbars = false;
+ boolean initializeScrollIndicators = false;
+
+ boolean startPaddingDefined = false;
+ boolean endPaddingDefined = false;
+ boolean leftPaddingDefined = false;
+ boolean rightPaddingDefined = false;
+
+ final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+
+ // Set default values.
+ viewFlagValues |= FOCUSABLE_AUTO;
+ viewFlagMasks |= FOCUSABLE_AUTO;
+
+ final int N = a.getIndexCount();
+ for (int i = 0; i < N; i++) {
+ int attr = a.getIndex(i);
+ switch (attr) {
+ case com.android.internal.R.styleable.View_background:
+ background = a.getDrawable(attr);
+ break;
+ case com.android.internal.R.styleable.View_padding:
+ padding = a.getDimensionPixelSize(attr, -1);
+ mUserPaddingLeftInitial = padding;
+ mUserPaddingRightInitial = padding;
+ leftPaddingDefined = true;
+ rightPaddingDefined = true;
+ break;
+ case com.android.internal.R.styleable.View_paddingHorizontal:
+ paddingHorizontal = a.getDimensionPixelSize(attr, -1);
+ mUserPaddingLeftInitial = paddingHorizontal;
+ mUserPaddingRightInitial = paddingHorizontal;
+ leftPaddingDefined = true;
+ rightPaddingDefined = true;
+ break;
+ case com.android.internal.R.styleable.View_paddingVertical:
+ paddingVertical = a.getDimensionPixelSize(attr, -1);
+ break;
+ case com.android.internal.R.styleable.View_paddingLeft:
+ leftPadding = a.getDimensionPixelSize(attr, -1);
+ mUserPaddingLeftInitial = leftPadding;
+ leftPaddingDefined = true;
+ break;
+ case com.android.internal.R.styleable.View_paddingTop:
+ topPadding = a.getDimensionPixelSize(attr, -1);
+ break;
+ case com.android.internal.R.styleable.View_paddingRight:
+ rightPadding = a.getDimensionPixelSize(attr, -1);
+ mUserPaddingRightInitial = rightPadding;
+ rightPaddingDefined = true;
+ break;
+ case com.android.internal.R.styleable.View_paddingBottom:
+ bottomPadding = a.getDimensionPixelSize(attr, -1);
+ break;
+ case com.android.internal.R.styleable.View_paddingStart:
+ startPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
+ startPaddingDefined = (startPadding != UNDEFINED_PADDING);
+ break;
+ case com.android.internal.R.styleable.View_paddingEnd:
+ endPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
+ endPaddingDefined = (endPadding != UNDEFINED_PADDING);
+ break;
+ case com.android.internal.R.styleable.View_scrollX:
+ x = a.getDimensionPixelOffset(attr, 0);
+ break;
+ case com.android.internal.R.styleable.View_scrollY:
+ y = a.getDimensionPixelOffset(attr, 0);
+ break;
+ case com.android.internal.R.styleable.View_alpha:
+ setAlpha(a.getFloat(attr, 1f));
+ break;
+ case com.android.internal.R.styleable.View_transformPivotX:
+ setPivotX(a.getDimension(attr, 0));
+ break;
+ case com.android.internal.R.styleable.View_transformPivotY:
+ setPivotY(a.getDimension(attr, 0));
+ break;
+ case com.android.internal.R.styleable.View_translationX:
+ tx = a.getDimension(attr, 0);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_translationY:
+ ty = a.getDimension(attr, 0);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_translationZ:
+ tz = a.getDimension(attr, 0);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_elevation:
+ elevation = a.getDimension(attr, 0);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_rotation:
+ rotation = a.getFloat(attr, 0);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_rotationX:
+ rotationX = a.getFloat(attr, 0);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_rotationY:
+ rotationY = a.getFloat(attr, 0);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_scaleX:
+ sx = a.getFloat(attr, 1f);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_scaleY:
+ sy = a.getFloat(attr, 1f);
+ transformSet = true;
+ break;
+ case com.android.internal.R.styleable.View_id:
+ mID = a.getResourceId(attr, NO_ID);
+ break;
+ case com.android.internal.R.styleable.View_tag:
+ mTag = a.getText(attr);
+ break;
+ case com.android.internal.R.styleable.View_fitsSystemWindows:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= FITS_SYSTEM_WINDOWS;
+ viewFlagMasks |= FITS_SYSTEM_WINDOWS;
+ }
+ break;
+ case com.android.internal.R.styleable.View_focusable:
+ viewFlagValues = (viewFlagValues & ~FOCUSABLE_MASK) | getFocusableAttribute(a);
+ if ((viewFlagValues & FOCUSABLE_AUTO) == 0) {
+ viewFlagMasks |= FOCUSABLE_MASK;
+ }
+ break;
+ case com.android.internal.R.styleable.View_focusableInTouchMode:
+ if (a.getBoolean(attr, false)) {
+ // unset auto focus since focusableInTouchMode implies explicit focusable
+ viewFlagValues &= ~FOCUSABLE_AUTO;
+ viewFlagValues |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE;
+ viewFlagMasks |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE_MASK;
+ }
+ break;
+ case com.android.internal.R.styleable.View_clickable:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= CLICKABLE;
+ viewFlagMasks |= CLICKABLE;
+ }
+ break;
+ case com.android.internal.R.styleable.View_longClickable:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= LONG_CLICKABLE;
+ viewFlagMasks |= LONG_CLICKABLE;
+ }
+ break;
+ case com.android.internal.R.styleable.View_contextClickable:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= CONTEXT_CLICKABLE;
+ viewFlagMasks |= CONTEXT_CLICKABLE;
+ }
+ break;
+ case com.android.internal.R.styleable.View_saveEnabled:
+ if (!a.getBoolean(attr, true)) {
+ viewFlagValues |= SAVE_DISABLED;
+ viewFlagMasks |= SAVE_DISABLED_MASK;
+ }
+ break;
+ case com.android.internal.R.styleable.View_duplicateParentState:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= DUPLICATE_PARENT_STATE;
+ viewFlagMasks |= DUPLICATE_PARENT_STATE;
+ }
+ break;
+ case com.android.internal.R.styleable.View_visibility:
+ final int visibility = a.getInt(attr, 0);
+ if (visibility != 0) {
+ viewFlagValues |= VISIBILITY_FLAGS[visibility];
+ viewFlagMasks |= VISIBILITY_MASK;
+ }
+ break;
+ case com.android.internal.R.styleable.View_layoutDirection:
+ // Clear any layout direction flags (included resolved bits) already set
+ mPrivateFlags2 &=
+ ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
+ // Set the layout direction flags depending on the value of the attribute
+ final int layoutDirection = a.getInt(attr, -1);
+ final int value = (layoutDirection != -1) ?
+ LAYOUT_DIRECTION_FLAGS[layoutDirection] : LAYOUT_DIRECTION_DEFAULT;
+ mPrivateFlags2 |= (value << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT);
+ break;
+ case com.android.internal.R.styleable.View_drawingCacheQuality:
+ final int cacheQuality = a.getInt(attr, 0);
+ if (cacheQuality != 0) {
+ viewFlagValues |= DRAWING_CACHE_QUALITY_FLAGS[cacheQuality];
+ viewFlagMasks |= DRAWING_CACHE_QUALITY_MASK;
+ }
+ break;
+ case com.android.internal.R.styleable.View_contentDescription:
+ setContentDescription(a.getString(attr));
+ break;
+ case com.android.internal.R.styleable.View_accessibilityTraversalBefore:
+ setAccessibilityTraversalBefore(a.getResourceId(attr, NO_ID));
+ break;
+ case com.android.internal.R.styleable.View_accessibilityTraversalAfter:
+ setAccessibilityTraversalAfter(a.getResourceId(attr, NO_ID));
+ break;
+ case com.android.internal.R.styleable.View_labelFor:
+ setLabelFor(a.getResourceId(attr, NO_ID));
+ break;
+ case com.android.internal.R.styleable.View_soundEffectsEnabled:
+ if (!a.getBoolean(attr, true)) {
+ viewFlagValues &= ~SOUND_EFFECTS_ENABLED;
+ viewFlagMasks |= SOUND_EFFECTS_ENABLED;
+ }
+ break;
+ case com.android.internal.R.styleable.View_hapticFeedbackEnabled:
+ if (!a.getBoolean(attr, true)) {
+ viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED;
+ viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED;
+ }
+ break;
+ case R.styleable.View_scrollbars:
+ final int scrollbars = a.getInt(attr, SCROLLBARS_NONE);
+ if (scrollbars != SCROLLBARS_NONE) {
+ viewFlagValues |= scrollbars;
+ viewFlagMasks |= SCROLLBARS_MASK;
+ initializeScrollbars = true;
+ }
+ break;
+ //noinspection deprecation
+ case R.styleable.View_fadingEdge:
+ if (targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ // Ignore the attribute starting with ICS
+ break;
+ }
+ // With builds < ICS, fall through and apply fading edges
+ case R.styleable.View_requiresFadingEdge:
+ final int fadingEdge = a.getInt(attr, FADING_EDGE_NONE);
+ if (fadingEdge != FADING_EDGE_NONE) {
+ viewFlagValues |= fadingEdge;
+ viewFlagMasks |= FADING_EDGE_MASK;
+ initializeFadingEdgeInternal(a);
+ }
+ break;
+ case R.styleable.View_scrollbarStyle:
+ scrollbarStyle = a.getInt(attr, SCROLLBARS_INSIDE_OVERLAY);
+ if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
+ viewFlagValues |= scrollbarStyle & SCROLLBARS_STYLE_MASK;
+ viewFlagMasks |= SCROLLBARS_STYLE_MASK;
+ }
+ break;
+ case R.styleable.View_isScrollContainer:
+ setScrollContainer = true;
+ if (a.getBoolean(attr, false)) {
+ setScrollContainer(true);
+ }
+ break;
+ case com.android.internal.R.styleable.View_keepScreenOn:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= KEEP_SCREEN_ON;
+ viewFlagMasks |= KEEP_SCREEN_ON;
+ }
+ break;
+ case R.styleable.View_filterTouchesWhenObscured:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= FILTER_TOUCHES_WHEN_OBSCURED;
+ viewFlagMasks |= FILTER_TOUCHES_WHEN_OBSCURED;
+ }
+ break;
+ case R.styleable.View_nextFocusLeft:
+ mNextFocusLeftId = a.getResourceId(attr, View.NO_ID);
+ break;
+ case R.styleable.View_nextFocusRight:
+ mNextFocusRightId = a.getResourceId(attr, View.NO_ID);
+ break;
+ case R.styleable.View_nextFocusUp:
+ mNextFocusUpId = a.getResourceId(attr, View.NO_ID);
+ break;
+ case R.styleable.View_nextFocusDown:
+ mNextFocusDownId = a.getResourceId(attr, View.NO_ID);
+ break;
+ case R.styleable.View_nextFocusForward:
+ mNextFocusForwardId = a.getResourceId(attr, View.NO_ID);
+ break;
+ case R.styleable.View_nextClusterForward:
+ mNextClusterForwardId = a.getResourceId(attr, View.NO_ID);
+ break;
+ case R.styleable.View_minWidth:
+ mMinWidth = a.getDimensionPixelSize(attr, 0);
+ break;
+ case R.styleable.View_minHeight:
+ mMinHeight = a.getDimensionPixelSize(attr, 0);
+ break;
+ case R.styleable.View_onClick:
+ if (context.isRestricted()) {
+ throw new IllegalStateException("The android:onClick attribute cannot "
+ + "be used within a restricted context");
+ }
+
+ final String handlerName = a.getString(attr);
+ if (handlerName != null) {
+ setOnClickListener(new DeclaredOnClickListener(this, handlerName));
+ }
+ break;
+ case R.styleable.View_overScrollMode:
+ overScrollMode = a.getInt(attr, OVER_SCROLL_IF_CONTENT_SCROLLS);
+ break;
+ case R.styleable.View_verticalScrollbarPosition:
+ mVerticalScrollbarPosition = a.getInt(attr, SCROLLBAR_POSITION_DEFAULT);
+ break;
+ case R.styleable.View_layerType:
+ setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null);
+ break;
+ case R.styleable.View_textDirection:
+ // Clear any text direction flag already set
+ mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
+ // Set the text direction flags depending on the value of the attribute
+ final int textDirection = a.getInt(attr, -1);
+ if (textDirection != -1) {
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_FLAGS[textDirection];
+ }
+ break;
+ case R.styleable.View_textAlignment:
+ // Clear any text alignment flag already set
+ mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
+ // Set the text alignment flag depending on the value of the attribute
+ final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT);
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_FLAGS[textAlignment];
+ break;
+ case R.styleable.View_importantForAccessibility:
+ setImportantForAccessibility(a.getInt(attr,
+ IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
+ break;
+ case R.styleable.View_accessibilityLiveRegion:
+ setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
+ break;
+ case R.styleable.View_transitionName:
+ setTransitionName(a.getString(attr));
+ break;
+ case R.styleable.View_nestedScrollingEnabled:
+ setNestedScrollingEnabled(a.getBoolean(attr, false));
+ break;
+ case R.styleable.View_stateListAnimator:
+ setStateListAnimator(AnimatorInflater.loadStateListAnimator(context,
+ a.getResourceId(attr, 0)));
+ break;
+ case R.styleable.View_backgroundTint:
+ // This will get applied later during setBackground().
+ if (mBackgroundTint == null) {
+ mBackgroundTint = new TintInfo();
+ }
+ mBackgroundTint.mTintList = a.getColorStateList(
+ R.styleable.View_backgroundTint);
+ mBackgroundTint.mHasTintList = true;
+ break;
+ case R.styleable.View_backgroundTintMode:
+ // This will get applied later during setBackground().
+ if (mBackgroundTint == null) {
+ mBackgroundTint = new TintInfo();
+ }
+ mBackgroundTint.mTintMode = Drawable.parseTintMode(a.getInt(
+ R.styleable.View_backgroundTintMode, -1), null);
+ mBackgroundTint.mHasTintMode = true;
+ break;
+ case R.styleable.View_outlineProvider:
+ setOutlineProviderFromAttribute(a.getInt(R.styleable.View_outlineProvider,
+ PROVIDER_BACKGROUND));
+ break;
+ case R.styleable.View_foreground:
+ if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+ setForeground(a.getDrawable(attr));
+ }
+ break;
+ case R.styleable.View_foregroundGravity:
+ if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+ setForegroundGravity(a.getInt(attr, Gravity.NO_GRAVITY));
+ }
+ break;
+ case R.styleable.View_foregroundTintMode:
+ if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+ setForegroundTintMode(Drawable.parseTintMode(a.getInt(attr, -1), null));
+ }
+ break;
+ case R.styleable.View_foregroundTint:
+ if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+ setForegroundTintList(a.getColorStateList(attr));
+ }
+ break;
+ case R.styleable.View_foregroundInsidePadding:
+ if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
+ if (mForegroundInfo == null) {
+ mForegroundInfo = new ForegroundInfo();
+ }
+ mForegroundInfo.mInsidePadding = a.getBoolean(attr,
+ mForegroundInfo.mInsidePadding);
+ }
+ break;
+ case R.styleable.View_scrollIndicators:
+ final int scrollIndicators =
+ (a.getInt(attr, 0) << SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT)
+ & SCROLL_INDICATORS_PFLAG3_MASK;
+ if (scrollIndicators != 0) {
+ mPrivateFlags3 |= scrollIndicators;
+ initializeScrollIndicators = true;
+ }
+ break;
+ case R.styleable.View_pointerIcon:
+ final int resourceId = a.getResourceId(attr, 0);
+ if (resourceId != 0) {
+ setPointerIcon(PointerIcon.load(
+ context.getResources(), resourceId));
+ } else {
+ final int pointerType = a.getInt(attr, PointerIcon.TYPE_NOT_SPECIFIED);
+ if (pointerType != PointerIcon.TYPE_NOT_SPECIFIED) {
+ setPointerIcon(PointerIcon.getSystemIcon(context, pointerType));
+ }
+ }
+ break;
+ case R.styleable.View_forceHasOverlappingRendering:
+ if (a.peekValue(attr) != null) {
+ forceHasOverlappingRendering(a.getBoolean(attr, true));
+ }
+ break;
+ case R.styleable.View_tooltipText:
+ setTooltipText(a.getText(attr));
+ break;
+ case R.styleable.View_keyboardNavigationCluster:
+ if (a.peekValue(attr) != null) {
+ setKeyboardNavigationCluster(a.getBoolean(attr, true));
+ }
+ break;
+ case R.styleable.View_focusedByDefault:
+ if (a.peekValue(attr) != null) {
+ setFocusedByDefault(a.getBoolean(attr, true));
+ }
+ break;
+ case R.styleable.View_autofillHints:
+ if (a.peekValue(attr) != null) {
+ CharSequence[] rawHints = null;
+ String rawString = null;
+
+ if (a.getType(attr) == TypedValue.TYPE_REFERENCE) {
+ int resId = a.getResourceId(attr, 0);
+
+ try {
+ rawHints = a.getTextArray(attr);
+ } catch (Resources.NotFoundException e) {
+ rawString = getResources().getString(resId);
+ }
+ } else {
+ rawString = a.getString(attr);
+ }
+
+ if (rawHints == null) {
+ if (rawString == null) {
+ throw new IllegalArgumentException(
+ "Could not resolve autofillHints");
+ } else {
+ rawHints = rawString.split(",");
+ }
+ }
+
+ String[] hints = new String[rawHints.length];
+
+ int numHints = rawHints.length;
+ for (int rawHintNum = 0; rawHintNum < numHints; rawHintNum++) {
+ hints[rawHintNum] = rawHints[rawHintNum].toString().trim();
+ }
+ setAutofillHints(hints);
+ }
+ break;
+ case R.styleable.View_importantForAutofill:
+ if (a.peekValue(attr) != null) {
+ setImportantForAutofill(a.getInt(attr, IMPORTANT_FOR_AUTOFILL_AUTO));
+ }
+ break;
+ case R.styleable.View_defaultFocusHighlightEnabled:
+ if (a.peekValue(attr) != null) {
+ setDefaultFocusHighlightEnabled(a.getBoolean(attr, true));
+ }
+ break;
+ }
+ }
+
+ setOverScrollMode(overScrollMode);
+
+ // Cache start/end user padding as we cannot fully resolve padding here (we dont have yet
+ // the resolved layout direction). Those cached values will be used later during padding
+ // resolution.
+ mUserPaddingStart = startPadding;
+ mUserPaddingEnd = endPadding;
+
+ if (background != null) {
+ setBackground(background);
+ }
+
+ // setBackground above will record that padding is currently provided by the background.
+ // If we have padding specified via xml, record that here instead and use it.
+ mLeftPaddingDefined = leftPaddingDefined;
+ mRightPaddingDefined = rightPaddingDefined;
+
+ if (padding >= 0) {
+ leftPadding = padding;
+ topPadding = padding;
+ rightPadding = padding;
+ bottomPadding = padding;
+ mUserPaddingLeftInitial = padding;
+ mUserPaddingRightInitial = padding;
+ } else {
+ if (paddingHorizontal >= 0) {
+ leftPadding = paddingHorizontal;
+ rightPadding = paddingHorizontal;
+ mUserPaddingLeftInitial = paddingHorizontal;
+ mUserPaddingRightInitial = paddingHorizontal;
+ }
+ if (paddingVertical >= 0) {
+ topPadding = paddingVertical;
+ bottomPadding = paddingVertical;
+ }
+ }
+
+ if (isRtlCompatibilityMode()) {
+ // RTL compatibility mode: pre Jelly Bean MR1 case OR no RTL support case.
+ // left / right padding are used if defined (meaning here nothing to do). If they are not
+ // defined and start / end padding are defined (e.g. in Frameworks resources), then we use
+ // start / end and resolve them as left / right (layout direction is not taken into account).
+ // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial
+ // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if
+ // defined.
+ if (!mLeftPaddingDefined && startPaddingDefined) {
+ leftPadding = startPadding;
+ }
+ mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial;
+ if (!mRightPaddingDefined && endPaddingDefined) {
+ rightPadding = endPadding;
+ }
+ mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial;
+ } else {
+ // Jelly Bean MR1 and after case: if start/end defined, they will override any left/right
+ // values defined. Otherwise, left /right values are used.
+ // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial
+ // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if
+ // defined.
+ final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined;
+
+ if (mLeftPaddingDefined && !hasRelativePadding) {
+ mUserPaddingLeftInitial = leftPadding;
+ }
+ if (mRightPaddingDefined && !hasRelativePadding) {
+ mUserPaddingRightInitial = rightPadding;
+ }
+ }
+
+ internalSetPadding(
+ mUserPaddingLeftInitial,
+ topPadding >= 0 ? topPadding : mPaddingTop,
+ mUserPaddingRightInitial,
+ bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
+
+ if (viewFlagMasks != 0) {
+ setFlags(viewFlagValues, viewFlagMasks);
+ }
+
+ if (initializeScrollbars) {
+ initializeScrollbarsInternal(a);
+ }
+
+ if (initializeScrollIndicators) {
+ initializeScrollIndicatorsInternal();
+ }
+
+ a.recycle();
+
+ // Needs to be called after mViewFlags is set
+ if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
+ recomputePadding();
+ }
+
+ if (x != 0 || y != 0) {
+ scrollTo(x, y);
+ }
+
+ if (transformSet) {
+ setTranslationX(tx);
+ setTranslationY(ty);
+ setTranslationZ(tz);
+ setElevation(elevation);
+ setRotation(rotation);
+ setRotationX(rotationX);
+ setRotationY(rotationY);
+ setScaleX(sx);
+ setScaleY(sy);
+ }
+
+ if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
+ setScrollContainer(true);
+ }
+
+ computeOpaqueFlags();
+ }
+
+ /**
+ * An implementation of OnClickListener that attempts to lazily load a
+ * named click handling method from a parent or ancestor context.
+ */
+ private static class DeclaredOnClickListener implements OnClickListener {
+ private final View mHostView;
+ private final String mMethodName;
+
+ private Method mResolvedMethod;
+ private Context mResolvedContext;
+
+ public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {
+ mHostView = hostView;
+ mMethodName = methodName;
+ }
+
+ @Override
+ public void onClick(@NonNull View v) {
+ if (mResolvedMethod == null) {
+ resolveMethod(mHostView.getContext(), mMethodName);
+ }
+
+ try {
+ mResolvedMethod.invoke(mResolvedContext, v);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ "Could not execute non-public method for android:onClick", e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(
+ "Could not execute method for android:onClick", e);
+ }
+ }
+
+ @NonNull
+ private void resolveMethod(@Nullable Context context, @NonNull String name) {
+ while (context != null) {
+ try {
+ if (!context.isRestricted()) {
+ final Method method = context.getClass().getMethod(mMethodName, View.class);
+ if (method != null) {
+ mResolvedMethod = method;
+ mResolvedContext = context;
+ return;
+ }
+ }
+ } catch (NoSuchMethodException e) {
+ // Failed to find method, keep searching up the hierarchy.
+ }
+
+ if (context instanceof ContextWrapper) {
+ context = ((ContextWrapper) context).getBaseContext();
+ } else {
+ // Can't search up the hierarchy, null out and fail.
+ context = null;
+ }
+ }
+
+ final int id = mHostView.getId();
+ final String idText = id == NO_ID ? "" : " with id '"
+ + mHostView.getContext().getResources().getResourceEntryName(id) + "'";
+ throw new IllegalStateException("Could not find method " + mMethodName
+ + "(View) in a parent or ancestor Context for android:onClick "
+ + "attribute defined on view " + mHostView.getClass() + idText);
+ }
+ }
+
+ /**
+ * Non-public constructor for use in testing
+ */
+ View() {
+ mResources = null;
+ mRenderNode = RenderNode.create(getClass().getName(), this);
+ }
+
+ final boolean debugDraw() {
+ return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
+ }
+
+ private static SparseArray<String> getAttributeMap() {
+ if (mAttributeMap == null) {
+ mAttributeMap = new SparseArray<>();
+ }
+ return mAttributeMap;
+ }
+
+ private void saveAttributeData(@Nullable AttributeSet attrs, @NonNull TypedArray t) {
+ final int attrsCount = attrs == null ? 0 : attrs.getAttributeCount();
+ final int indexCount = t.getIndexCount();
+ final String[] attributes = new String[(attrsCount + indexCount) * 2];
+
+ int i = 0;
+
+ // Store raw XML attributes.
+ for (int j = 0; j < attrsCount; ++j) {
+ attributes[i] = attrs.getAttributeName(j);
+ attributes[i + 1] = attrs.getAttributeValue(j);
+ i += 2;
+ }
+
+ // Store resolved styleable attributes.
+ final Resources res = t.getResources();
+ final SparseArray<String> attributeMap = getAttributeMap();
+ for (int j = 0; j < indexCount; ++j) {
+ final int index = t.getIndex(j);
+ if (!t.hasValueOrEmpty(index)) {
+ // Value is undefined. Skip it.
+ continue;
+ }
+
+ final int resourceId = t.getResourceId(index, 0);
+ if (resourceId == 0) {
+ // Value is not a reference. Skip it.
+ continue;
+ }
+
+ String resourceName = attributeMap.get(resourceId);
+ if (resourceName == null) {
+ try {
+ resourceName = res.getResourceName(resourceId);
+ } catch (Resources.NotFoundException e) {
+ resourceName = "0x" + Integer.toHexString(resourceId);
+ }
+ attributeMap.put(resourceId, resourceName);
+ }
+
+ attributes[i] = resourceName;
+ attributes[i + 1] = t.getString(index);
+ i += 2;
+ }
+
+ // Trim to fit contents.
+ final String[] trimmed = new String[i];
+ System.arraycopy(attributes, 0, trimmed, 0, i);
+ mAttributes = trimmed;
+ }
+
+ public String toString() {
+ StringBuilder out = new StringBuilder(128);
+ out.append(getClass().getName());
+ out.append('{');
+ out.append(Integer.toHexString(System.identityHashCode(this)));
+ out.append(' ');
+ switch (mViewFlags&VISIBILITY_MASK) {
+ case VISIBLE: out.append('V'); break;
+ case INVISIBLE: out.append('I'); break;
+ case GONE: out.append('G'); break;
+ default: out.append('.'); break;
+ }
+ out.append((mViewFlags & FOCUSABLE) == FOCUSABLE ? 'F' : '.');
+ out.append((mViewFlags&ENABLED_MASK) == ENABLED ? 'E' : '.');
+ out.append((mViewFlags&DRAW_MASK) == WILL_NOT_DRAW ? '.' : 'D');
+ out.append((mViewFlags&SCROLLBARS_HORIZONTAL) != 0 ? 'H' : '.');
+ out.append((mViewFlags&SCROLLBARS_VERTICAL) != 0 ? 'V' : '.');
+ out.append((mViewFlags&CLICKABLE) != 0 ? 'C' : '.');
+ out.append((mViewFlags&LONG_CLICKABLE) != 0 ? 'L' : '.');
+ out.append((mViewFlags&CONTEXT_CLICKABLE) != 0 ? 'X' : '.');
+ out.append(' ');
+ out.append((mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0 ? 'R' : '.');
+ out.append((mPrivateFlags&PFLAG_FOCUSED) != 0 ? 'F' : '.');
+ out.append((mPrivateFlags&PFLAG_SELECTED) != 0 ? 'S' : '.');
+ if ((mPrivateFlags&PFLAG_PREPRESSED) != 0) {
+ out.append('p');
+ } else {
+ out.append((mPrivateFlags&PFLAG_PRESSED) != 0 ? 'P' : '.');
+ }
+ out.append((mPrivateFlags&PFLAG_HOVERED) != 0 ? 'H' : '.');
+ out.append((mPrivateFlags&PFLAG_ACTIVATED) != 0 ? 'A' : '.');
+ out.append((mPrivateFlags&PFLAG_INVALIDATED) != 0 ? 'I' : '.');
+ out.append((mPrivateFlags&PFLAG_DIRTY_MASK) != 0 ? 'D' : '.');
+ out.append(' ');
+ out.append(mLeft);
+ out.append(',');
+ out.append(mTop);
+ out.append('-');
+ out.append(mRight);
+ out.append(',');
+ out.append(mBottom);
+ final int id = getId();
+ if (id != NO_ID) {
+ out.append(" #");
+ out.append(Integer.toHexString(id));
+ final Resources r = mResources;
+ if (id > 0 && Resources.resourceHasPackage(id) && r != null) {
+ try {
+ String pkgname;
+ switch (id&0xff000000) {
+ case 0x7f000000:
+ pkgname="app";
+ break;
+ case 0x01000000:
+ pkgname="android";
+ break;
+ default:
+ pkgname = r.getResourcePackageName(id);
+ break;
+ }
+ String typename = r.getResourceTypeName(id);
+ String entryname = r.getResourceEntryName(id);
+ out.append(" ");
+ out.append(pkgname);
+ out.append(":");
+ out.append(typename);
+ out.append("/");
+ out.append(entryname);
+ } catch (Resources.NotFoundException e) {
+ }
+ }
+ }
+ out.append("}");
+ return out.toString();
+ }
+
+ /**
+ * <p>
+ * Initializes the fading edges from a given set of styled attributes. This
+ * method should be called by subclasses that need fading edges and when an
+ * instance of these subclasses is created programmatically rather than
+ * being inflated from XML. This method is automatically called when the XML
+ * is inflated.
+ * </p>
+ *
+ * @param a the styled attributes set to initialize the fading edges from
+ *
+ * @removed
+ */
+ protected void initializeFadingEdge(TypedArray a) {
+ // This method probably shouldn't have been included in the SDK to begin with.
+ // It relies on 'a' having been initialized using an attribute filter array that is
+ // not publicly available to the SDK. The old method has been renamed
+ // to initializeFadingEdgeInternal and hidden for framework use only;
+ // this one initializes using defaults to make it safe to call for apps.
+
+ TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View);
+
+ initializeFadingEdgeInternal(arr);
+
+ arr.recycle();
+ }
+
+ /**
+ * <p>
+ * Initializes the fading edges from a given set of styled attributes. This
+ * method should be called by subclasses that need fading edges and when an
+ * instance of these subclasses is created programmatically rather than
+ * being inflated from XML. This method is automatically called when the XML
+ * is inflated.
+ * </p>
+ *
+ * @param a the styled attributes set to initialize the fading edges from
+ * @hide This is the real method; the public one is shimmed to be safe to call from apps.
+ */
+ protected void initializeFadingEdgeInternal(TypedArray a) {
+ initScrollCache();
+
+ mScrollCache.fadingEdgeLength = a.getDimensionPixelSize(
+ R.styleable.View_fadingEdgeLength,
+ ViewConfiguration.get(mContext).getScaledFadingEdgeLength());
+ }
+
+ /**
+ * Returns the size of the vertical faded edges used to indicate that more
+ * content in this view is visible.
+ *
+ * @return The size in pixels of the vertical faded edge or 0 if vertical
+ * faded edges are not enabled for this view.
+ * @attr ref android.R.styleable#View_fadingEdgeLength
+ */
+ public int getVerticalFadingEdgeLength() {
+ if (isVerticalFadingEdgeEnabled()) {
+ ScrollabilityCache cache = mScrollCache;
+ if (cache != null) {
+ return cache.fadingEdgeLength;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Set the size of the faded edge used to indicate that more content in this
+ * view is available. Will not change whether the fading edge is enabled; use
+ * {@link #setVerticalFadingEdgeEnabled(boolean)} or
+ * {@link #setHorizontalFadingEdgeEnabled(boolean)} to enable the fading edge
+ * for the vertical or horizontal fading edges.
+ *
+ * @param length The size in pixels of the faded edge used to indicate that more
+ * content in this view is visible.
+ */
+ public void setFadingEdgeLength(int length) {
+ initScrollCache();
+ mScrollCache.fadingEdgeLength = length;
+ }
+
+ /**
+ * Returns the size of the horizontal faded edges used to indicate that more
+ * content in this view is visible.
+ *
+ * @return The size in pixels of the horizontal faded edge or 0 if horizontal
+ * faded edges are not enabled for this view.
+ * @attr ref android.R.styleable#View_fadingEdgeLength
+ */
+ public int getHorizontalFadingEdgeLength() {
+ if (isHorizontalFadingEdgeEnabled()) {
+ ScrollabilityCache cache = mScrollCache;
+ if (cache != null) {
+ return cache.fadingEdgeLength;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the width of the vertical scrollbar.
+ *
+ * @return The width in pixels of the vertical scrollbar or 0 if there
+ * is no vertical scrollbar.
+ */
+ public int getVerticalScrollbarWidth() {
+ ScrollabilityCache cache = mScrollCache;
+ if (cache != null) {
+ ScrollBarDrawable scrollBar = cache.scrollBar;
+ if (scrollBar != null) {
+ int size = scrollBar.getSize(true);
+ if (size <= 0) {
+ size = cache.scrollBarSize;
+ }
+ return size;
+ }
+ return 0;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the height of the horizontal scrollbar.
+ *
+ * @return The height in pixels of the horizontal scrollbar or 0 if
+ * there is no horizontal scrollbar.
+ */
+ protected int getHorizontalScrollbarHeight() {
+ ScrollabilityCache cache = mScrollCache;
+ if (cache != null) {
+ ScrollBarDrawable scrollBar = cache.scrollBar;
+ if (scrollBar != null) {
+ int size = scrollBar.getSize(false);
+ if (size <= 0) {
+ size = cache.scrollBarSize;
+ }
+ return size;
+ }
+ return 0;
+ }
+ return 0;
+ }
+
+ /**
+ * <p>
+ * Initializes the scrollbars from a given set of styled attributes. This
+ * method should be called by subclasses that need scrollbars and when an
+ * instance of these subclasses is created programmatically rather than
+ * being inflated from XML. This method is automatically called when the XML
+ * is inflated.
+ * </p>
+ *
+ * @param a the styled attributes set to initialize the scrollbars from
+ *
+ * @removed
+ */
+ protected void initializeScrollbars(TypedArray a) {
+ // It's not safe to use this method from apps. The parameter 'a' must have been obtained
+ // using the View filter array which is not available to the SDK. As such, internal
+ // framework usage now uses initializeScrollbarsInternal and we grab a default
+ // TypedArray with the right filter instead here.
+ TypedArray arr = mContext.obtainStyledAttributes(com.android.internal.R.styleable.View);
+
+ initializeScrollbarsInternal(arr);
+
+ // We ignored the method parameter. Recycle the one we actually did use.
+ arr.recycle();
+ }
+
+ /**
+ * <p>
+ * Initializes the scrollbars from a given set of styled attributes. This
+ * method should be called by subclasses that need scrollbars and when an
+ * instance of these subclasses is created programmatically rather than
+ * being inflated from XML. This method is automatically called when the XML
+ * is inflated.
+ * </p>
+ *
+ * @param a the styled attributes set to initialize the scrollbars from
+ * @hide
+ */
+ protected void initializeScrollbarsInternal(TypedArray a) {
+ initScrollCache();
+
+ final ScrollabilityCache scrollabilityCache = mScrollCache;
+
+ if (scrollabilityCache.scrollBar == null) {
+ scrollabilityCache.scrollBar = new ScrollBarDrawable();
+ scrollabilityCache.scrollBar.setState(getDrawableState());
+ scrollabilityCache.scrollBar.setCallback(this);
+ }
+
+ final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, true);
+
+ if (!fadeScrollbars) {
+ scrollabilityCache.state = ScrollabilityCache.ON;
+ }
+ scrollabilityCache.fadeScrollBars = fadeScrollbars;
+
+
+ scrollabilityCache.scrollBarFadeDuration = a.getInt(
+ R.styleable.View_scrollbarFadeDuration, ViewConfiguration
+ .getScrollBarFadeDuration());
+ scrollabilityCache.scrollBarDefaultDelayBeforeFade = a.getInt(
+ R.styleable.View_scrollbarDefaultDelayBeforeFade,
+ ViewConfiguration.getScrollDefaultDelay());
+
+
+ scrollabilityCache.scrollBarSize = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.View_scrollbarSize,
+ ViewConfiguration.get(mContext).getScaledScrollBarSize());
+
+ Drawable track = a.getDrawable(R.styleable.View_scrollbarTrackHorizontal);
+ scrollabilityCache.scrollBar.setHorizontalTrackDrawable(track);
+
+ Drawable thumb = a.getDrawable(R.styleable.View_scrollbarThumbHorizontal);
+ if (thumb != null) {
+ scrollabilityCache.scrollBar.setHorizontalThumbDrawable(thumb);
+ }
+
+ boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack,
+ false);
+ if (alwaysDraw) {
+ scrollabilityCache.scrollBar.setAlwaysDrawHorizontalTrack(true);
+ }
+
+ track = a.getDrawable(R.styleable.View_scrollbarTrackVertical);
+ scrollabilityCache.scrollBar.setVerticalTrackDrawable(track);
+
+ thumb = a.getDrawable(R.styleable.View_scrollbarThumbVertical);
+ if (thumb != null) {
+ scrollabilityCache.scrollBar.setVerticalThumbDrawable(thumb);
+ }
+
+ alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack,
+ false);
+ if (alwaysDraw) {
+ scrollabilityCache.scrollBar.setAlwaysDrawVerticalTrack(true);
+ }
+
+ // Apply layout direction to the new Drawables if needed
+ final int layoutDirection = getLayoutDirection();
+ if (track != null) {
+ track.setLayoutDirection(layoutDirection);
+ }
+ if (thumb != null) {
+ thumb.setLayoutDirection(layoutDirection);
+ }
+
+ // Re-apply user/background padding so that scrollbar(s) get added
+ resolvePadding();
+ }
+
+ private void initializeScrollIndicatorsInternal() {
+ // Some day maybe we'll break this into top/left/start/etc. and let the
+ // client control it. Until then, you can have any scroll indicator you
+ // want as long as it's a 1dp foreground-colored rectangle.
+ if (mScrollIndicatorDrawable == null) {
+ mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
+ }
+ }
+
+ /**
+ * <p>
+ * Initalizes the scrollability cache if necessary.
+ * </p>
+ */
+ private void initScrollCache() {
+ if (mScrollCache == null) {
+ mScrollCache = new ScrollabilityCache(ViewConfiguration.get(mContext), this);
+ }
+ }
+
+ private ScrollabilityCache getScrollCache() {
+ initScrollCache();
+ return mScrollCache;
+ }
+
+ /**
+ * Set the position of the vertical scroll bar. Should be one of
+ * {@link #SCROLLBAR_POSITION_DEFAULT}, {@link #SCROLLBAR_POSITION_LEFT} or
+ * {@link #SCROLLBAR_POSITION_RIGHT}.
+ *
+ * @param position Where the vertical scroll bar should be positioned.
+ */
+ public void setVerticalScrollbarPosition(int position) {
+ if (mVerticalScrollbarPosition != position) {
+ mVerticalScrollbarPosition = position;
+ computeOpaqueFlags();
+ resolvePadding();
+ }
+ }
+
+ /**
+ * @return The position where the vertical scroll bar will show, if applicable.
+ * @see #setVerticalScrollbarPosition(int)
+ */
+ public int getVerticalScrollbarPosition() {
+ return mVerticalScrollbarPosition;
+ }
+
+ boolean isOnScrollbar(float x, float y) {
+ if (mScrollCache == null) {
+ return false;
+ }
+ x += getScrollX();
+ y += getScrollY();
+ if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) {
+ final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+ getVerticalScrollBarBounds(null, touchBounds);
+ if (touchBounds.contains((int) x, (int) y)) {
+ return true;
+ }
+ }
+ if (isHorizontalScrollBarEnabled()) {
+ final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+ getHorizontalScrollBarBounds(null, touchBounds);
+ if (touchBounds.contains((int) x, (int) y)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean isOnScrollbarThumb(float x, float y) {
+ return isOnVerticalScrollbarThumb(x, y) || isOnHorizontalScrollbarThumb(x, y);
+ }
+
+ private boolean isOnVerticalScrollbarThumb(float x, float y) {
+ if (mScrollCache == null) {
+ return false;
+ }
+ if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) {
+ x += getScrollX();
+ y += getScrollY();
+ final Rect bounds = mScrollCache.mScrollBarBounds;
+ final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+ getVerticalScrollBarBounds(bounds, touchBounds);
+ final int range = computeVerticalScrollRange();
+ final int offset = computeVerticalScrollOffset();
+ final int extent = computeVerticalScrollExtent();
+ final int thumbLength = ScrollBarUtils.getThumbLength(bounds.height(), bounds.width(),
+ extent, range);
+ final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.height(), thumbLength,
+ extent, range, offset);
+ final int thumbTop = bounds.top + thumbOffset;
+ final int adjust = Math.max(mScrollCache.scrollBarMinTouchTarget - thumbLength, 0) / 2;
+ if (x >= touchBounds.left && x <= touchBounds.right
+ && y >= thumbTop - adjust && y <= thumbTop + thumbLength + adjust) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isOnHorizontalScrollbarThumb(float x, float y) {
+ if (mScrollCache == null) {
+ return false;
+ }
+ if (isHorizontalScrollBarEnabled()) {
+ x += getScrollX();
+ y += getScrollY();
+ final Rect bounds = mScrollCache.mScrollBarBounds;
+ final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+ getHorizontalScrollBarBounds(bounds, touchBounds);
+ final int range = computeHorizontalScrollRange();
+ final int offset = computeHorizontalScrollOffset();
+ final int extent = computeHorizontalScrollExtent();
+ final int thumbLength = ScrollBarUtils.getThumbLength(bounds.width(), bounds.height(),
+ extent, range);
+ final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.width(), thumbLength,
+ extent, range, offset);
+ final int thumbLeft = bounds.left + thumbOffset;
+ final int adjust = Math.max(mScrollCache.scrollBarMinTouchTarget - thumbLength, 0) / 2;
+ if (x >= thumbLeft - adjust && x <= thumbLeft + thumbLength + adjust
+ && y >= touchBounds.top && y <= touchBounds.bottom) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean isDraggingScrollBar() {
+ return mScrollCache != null
+ && mScrollCache.mScrollBarDraggingState != ScrollabilityCache.NOT_DRAGGING;
+ }
+
+ /**
+ * Sets the state of all scroll indicators.
+ * <p>
+ * See {@link #setScrollIndicators(int, int)} for usage information.
+ *
+ * @param indicators a bitmask of indicators that should be enabled, or
+ * {@code 0} to disable all indicators
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ * @attr ref android.R.styleable#View_scrollIndicators
+ */
+ public void setScrollIndicators(@ScrollIndicators int indicators) {
+ setScrollIndicators(indicators,
+ SCROLL_INDICATORS_PFLAG3_MASK >>> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT);
+ }
+
+ /**
+ * Sets the state of the scroll indicators specified by the mask. To change
+ * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
+ * <p>
+ * When a scroll indicator is enabled, it will be displayed if the view
+ * can scroll in the direction of the indicator.
+ * <p>
+ * Multiple indicator types may be enabled or disabled by passing the
+ * logical OR of the desired types. If multiple types are specified, they
+ * will all be set to the same enabled state.
+ * <p>
+ * For example, to enable the top scroll indicatorExample: {@code setScrollIndicators
+ *
+ * @param indicators the indicator direction, or the logical OR of multiple
+ * indicator directions. One or more of:
+ * <ul>
+ * <li>{@link #SCROLL_INDICATOR_TOP}</li>
+ * <li>{@link #SCROLL_INDICATOR_BOTTOM}</li>
+ * <li>{@link #SCROLL_INDICATOR_LEFT}</li>
+ * <li>{@link #SCROLL_INDICATOR_RIGHT}</li>
+ * <li>{@link #SCROLL_INDICATOR_START}</li>
+ * <li>{@link #SCROLL_INDICATOR_END}</li>
+ * </ul>
+ * @see #setScrollIndicators(int)
+ * @see #getScrollIndicators()
+ * @attr ref android.R.styleable#View_scrollIndicators
+ */
+ public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask) {
+ // Shift and sanitize mask.
+ mask <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+ mask &= SCROLL_INDICATORS_PFLAG3_MASK;
+
+ // Shift and mask indicators.
+ indicators <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+ indicators &= mask;
+
+ // Merge with non-masked flags.
+ final int updatedFlags = indicators | (mPrivateFlags3 & ~mask);
+
+ if (mPrivateFlags3 != updatedFlags) {
+ mPrivateFlags3 = updatedFlags;
+
+ if (indicators != 0) {
+ initializeScrollIndicatorsInternal();
+ }
+ invalidate();
+ }
+ }
+
+ /**
+ * Returns a bitmask representing the enabled scroll indicators.
+ * <p>
+ * For example, if the top and left scroll indicators are enabled and all
+ * other indicators are disabled, the return value will be
+ * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
+ * <p>
+ * To check whether the bottom scroll indicator is enabled, use the value
+ * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
+ *
+ * @return a bitmask representing the enabled scroll indicators
+ */
+ @ScrollIndicators
+ public int getScrollIndicators() {
+ return (mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK)
+ >>> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT;
+ }
+
+ ListenerInfo getListenerInfo() {
+ if (mListenerInfo != null) {
+ return mListenerInfo;
+ }
+ mListenerInfo = new ListenerInfo();
+ return mListenerInfo;
+ }
+
+ /**
+ * Register a callback to be invoked when the scroll X or Y positions of
+ * this view change.
+ * <p>
+ * <b>Note:</b> Some views handle scrolling independently from View and may
+ * have their own separate listeners for scroll-type events. For example,
+ * {@link android.widget.ListView ListView} allows clients to register an
+ * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+ * to listen for changes in list scroll position.
+ *
+ * @param l The listener to notify when the scroll X or Y position changes.
+ * @see android.view.View#getScrollX()
+ * @see android.view.View#getScrollY()
+ */
+ public void setOnScrollChangeListener(OnScrollChangeListener l) {
+ getListenerInfo().mOnScrollChangeListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when focus of this view changed.
+ *
+ * @param l The callback that will run.
+ */
+ public void setOnFocusChangeListener(OnFocusChangeListener l) {
+ getListenerInfo().mOnFocusChangeListener = l;
+ }
+
+ /**
+ * Add a listener that will be called when the bounds of the view change due to
+ * layout processing.
+ *
+ * @param listener The listener that will be called when layout bounds change.
+ */
+ public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
+ ListenerInfo li = getListenerInfo();
+ if (li.mOnLayoutChangeListeners == null) {
+ li.mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>();
+ }
+ if (!li.mOnLayoutChangeListeners.contains(listener)) {
+ li.mOnLayoutChangeListeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove a listener for layout changes.
+ *
+ * @param listener The listener for layout bounds change.
+ */
+ public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
+ ListenerInfo li = mListenerInfo;
+ if (li == null || li.mOnLayoutChangeListeners == null) {
+ return;
+ }
+ li.mOnLayoutChangeListeners.remove(listener);
+ }
+
+ /**
+ * Add a listener for attach state changes.
+ *
+ * This listener will be called whenever this view is attached or detached
+ * from a window. Remove the listener using
+ * {@link #removeOnAttachStateChangeListener(OnAttachStateChangeListener)}.
+ *
+ * @param listener Listener to attach
+ * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener)
+ */
+ public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
+ ListenerInfo li = getListenerInfo();
+ if (li.mOnAttachStateChangeListeners == null) {
+ li.mOnAttachStateChangeListeners
+ = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
+ }
+ li.mOnAttachStateChangeListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener for attach state changes. The listener will receive no further
+ * notification of window attach/detach events.
+ *
+ * @param listener Listener to remove
+ * @see #addOnAttachStateChangeListener(OnAttachStateChangeListener)
+ */
+ public void removeOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
+ ListenerInfo li = mListenerInfo;
+ if (li == null || li.mOnAttachStateChangeListeners == null) {
+ return;
+ }
+ li.mOnAttachStateChangeListeners.remove(listener);
+ }
+
+ /**
+ * Returns the focus-change callback registered for this view.
+ *
+ * @return The callback, or null if one is not registered.
+ */
+ public OnFocusChangeListener getOnFocusChangeListener() {
+ ListenerInfo li = mListenerInfo;
+ return li != null ? li.mOnFocusChangeListener : null;
+ }
+
+ /**
+ * Register a callback to be invoked when this view is clicked. If this view is not
+ * clickable, it becomes clickable.
+ *
+ * @param l The callback that will run
+ *
+ * @see #setClickable(boolean)
+ */
+ public void setOnClickListener(@Nullable OnClickListener l) {
+ if (!isClickable()) {
+ setClickable(true);
+ }
+ getListenerInfo().mOnClickListener = l;
+ }
+
+ /**
+ * Return whether this view has an attached OnClickListener. Returns
+ * true if there is a listener, false if there is none.
+ */
+ public boolean hasOnClickListeners() {
+ ListenerInfo li = mListenerInfo;
+ return (li != null && li.mOnClickListener != null);
+ }
+
+ /**
+ * Register a callback to be invoked when this view is clicked and held. If this view is not
+ * long clickable, it becomes long clickable.
+ *
+ * @param l The callback that will run
+ *
+ * @see #setLongClickable(boolean)
+ */
+ public void setOnLongClickListener(@Nullable OnLongClickListener l) {
+ if (!isLongClickable()) {
+ setLongClickable(true);
+ }
+ getListenerInfo().mOnLongClickListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when this view is context clicked. If the view is not
+ * context clickable, it becomes context clickable.
+ *
+ * @param l The callback that will run
+ * @see #setContextClickable(boolean)
+ */
+ public void setOnContextClickListener(@Nullable OnContextClickListener l) {
+ if (!isContextClickable()) {
+ setContextClickable(true);
+ }
+ getListenerInfo().mOnContextClickListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when the context menu for this view is
+ * being built. If this view is not long clickable, it becomes long clickable.
+ *
+ * @param l The callback that will run
+ *
+ */
+ public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
+ if (!isLongClickable()) {
+ setLongClickable(true);
+ }
+ getListenerInfo().mOnCreateContextMenuListener = l;
+ }
+
+ /**
+ * Set an observer to collect stats for each frame rendered for this view.
+ *
+ * @hide
+ */
+ public void addFrameMetricsListener(Window window,
+ Window.OnFrameMetricsAvailableListener listener,
+ Handler handler) {
+ if (mAttachInfo != null) {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ if (mFrameMetricsObservers == null) {
+ mFrameMetricsObservers = new ArrayList<>();
+ }
+
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window,
+ handler.getLooper(), listener);
+ mFrameMetricsObservers.add(fmo);
+ mAttachInfo.mThreadedRenderer.addFrameMetricsObserver(fmo);
+ } else {
+ Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
+ }
+ } else {
+ if (mFrameMetricsObservers == null) {
+ mFrameMetricsObservers = new ArrayList<>();
+ }
+
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window,
+ handler.getLooper(), listener);
+ mFrameMetricsObservers.add(fmo);
+ }
+ }
+
+ /**
+ * Remove observer configured to collect frame stats for this view.
+ *
+ * @hide
+ */
+ public void removeFrameMetricsListener(
+ Window.OnFrameMetricsAvailableListener listener) {
+ ThreadedRenderer renderer = getThreadedRenderer();
+ FrameMetricsObserver fmo = findFrameMetricsObserver(listener);
+ if (fmo == null) {
+ throw new IllegalArgumentException(
+ "attempt to remove OnFrameMetricsAvailableListener that was never added");
+ }
+
+ if (mFrameMetricsObservers != null) {
+ mFrameMetricsObservers.remove(fmo);
+ if (renderer != null) {
+ renderer.removeFrameMetricsObserver(fmo);
+ }
+ }
+ }
+
+ private void registerPendingFrameMetricsObservers() {
+ if (mFrameMetricsObservers != null) {
+ ThreadedRenderer renderer = getThreadedRenderer();
+ if (renderer != null) {
+ for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
+ renderer.addFrameMetricsObserver(fmo);
+ }
+ } else {
+ Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
+ }
+ }
+ }
+
+ private FrameMetricsObserver findFrameMetricsObserver(
+ Window.OnFrameMetricsAvailableListener listener) {
+ for (int i = 0; i < mFrameMetricsObservers.size(); i++) {
+ FrameMetricsObserver observer = mFrameMetricsObservers.get(i);
+ if (observer.mListener == listener) {
+ return observer;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Call this view's OnClickListener, if it is defined. Performs all normal
+ * actions associated with clicking: reporting accessibility event, playing
+ * a sound, etc.
+ *
+ * @return True there was an assigned OnClickListener that was called, false
+ * otherwise is returned.
+ */
+ public boolean performClick() {
+ final boolean result;
+ final ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnClickListener != null) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ li.mOnClickListener.onClick(this);
+ result = true;
+ } else {
+ result = false;
+ }
+
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+
+ notifyEnterOrExitForAutoFillIfNeeded(true);
+
+ return result;
+ }
+
+ /**
+ * Directly call any attached OnClickListener. Unlike {@link #performClick()},
+ * this only calls the listener, and does not do any associated clicking
+ * actions like reporting an accessibility event.
+ *
+ * @return True there was an assigned OnClickListener that was called, false
+ * otherwise is returned.
+ */
+ public boolean callOnClick() {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnClickListener != null) {
+ li.mOnClickListener.onClick(this);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Calls this view's OnLongClickListener, if it is defined. Invokes the
+ * context menu if the OnLongClickListener did not consume the event.
+ *
+ * @return {@code true} if one of the above receivers consumed the event,
+ * {@code false} otherwise
+ */
+ public boolean performLongClick() {
+ return performLongClickInternal(mLongClickX, mLongClickY);
+ }
+
+ /**
+ * Calls this view's OnLongClickListener, if it is defined. Invokes the
+ * context menu if the OnLongClickListener did not consume the event,
+ * anchoring it to an (x,y) coordinate.
+ *
+ * @param x x coordinate of the anchoring touch event, or {@link Float#NaN}
+ * to disable anchoring
+ * @param y y coordinate of the anchoring touch event, or {@link Float#NaN}
+ * to disable anchoring
+ * @return {@code true} if one of the above receivers consumed the event,
+ * {@code false} otherwise
+ */
+ public boolean performLongClick(float x, float y) {
+ mLongClickX = x;
+ mLongClickY = y;
+ final boolean handled = performLongClick();
+ mLongClickX = Float.NaN;
+ mLongClickY = Float.NaN;
+ return handled;
+ }
+
+ /**
+ * Calls this view's OnLongClickListener, if it is defined. Invokes the
+ * context menu if the OnLongClickListener did not consume the event,
+ * optionally anchoring it to an (x,y) coordinate.
+ *
+ * @param x x coordinate of the anchoring touch event, or {@link Float#NaN}
+ * to disable anchoring
+ * @param y y coordinate of the anchoring touch event, or {@link Float#NaN}
+ * to disable anchoring
+ * @return {@code true} if one of the above receivers consumed the event,
+ * {@code false} otherwise
+ */
+ private boolean performLongClickInternal(float x, float y) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+
+ boolean handled = false;
+ final ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnLongClickListener != null) {
+ handled = li.mOnLongClickListener.onLongClick(View.this);
+ }
+ if (!handled) {
+ final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
+ handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
+ }
+ if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+ if (!handled) {
+ handled = showLongClickTooltip((int) x, (int) y);
+ }
+ }
+ if (handled) {
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
+ return handled;
+ }
+
+ /**
+ * Call this view's OnContextClickListener, if it is defined.
+ *
+ * @param x the x coordinate of the context click
+ * @param y the y coordinate of the context click
+ * @return True if there was an assigned OnContextClickListener that consumed the event, false
+ * otherwise.
+ */
+ public boolean performContextClick(float x, float y) {
+ return performContextClick();
+ }
+
+ /**
+ * Call this view's OnContextClickListener, if it is defined.
+ *
+ * @return True if there was an assigned OnContextClickListener that consumed the event, false
+ * otherwise.
+ */
+ public boolean performContextClick() {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED);
+
+ boolean handled = false;
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnContextClickListener != null) {
+ handled = li.mOnContextClickListener.onContextClick(View.this);
+ }
+ if (handled) {
+ performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
+ }
+ return handled;
+ }
+
+ /**
+ * Performs button-related actions during a touch down event.
+ *
+ * @param event The event.
+ * @return True if the down was consumed.
+ *
+ * @hide
+ */
+ protected boolean performButtonActionOnTouchDown(MotionEvent event) {
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
+ (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
+ showContextMenu(event.getX(), event.getY());
+ mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Shows the context menu for this view.
+ *
+ * @return {@code true} if the context menu was shown, {@code false}
+ * otherwise
+ * @see #showContextMenu(float, float)
+ */
+ public boolean showContextMenu() {
+ return getParent().showContextMenuForChild(this);
+ }
+
+ /**
+ * Shows the context menu for this view anchored to the specified
+ * view-relative coordinate.
+ *
+ * @param x the X coordinate in pixels relative to the view to which the
+ * menu should be anchored, or {@link Float#NaN} to disable anchoring
+ * @param y the Y coordinate in pixels relative to the view to which the
+ * menu should be anchored, or {@link Float#NaN} to disable anchoring
+ * @return {@code true} if the context menu was shown, {@code false}
+ * otherwise
+ */
+ public boolean showContextMenu(float x, float y) {
+ return getParent().showContextMenuForChild(this, x, y);
+ }
+
+ /**
+ * Start an action mode with the default type {@link ActionMode#TYPE_PRIMARY}.
+ *
+ * @param callback Callback that will control the lifecycle of the action mode
+ * @return The new action mode if it is started, null otherwise
+ *
+ * @see ActionMode
+ * @see #startActionMode(android.view.ActionMode.Callback, int)
+ */
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ return startActionMode(callback, ActionMode.TYPE_PRIMARY);
+ }
+
+ /**
+ * Start an action mode with the given type.
+ *
+ * @param callback Callback that will control the lifecycle of the action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The new action mode if it is started, null otherwise
+ *
+ * @see ActionMode
+ */
+ public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+ ViewParent parent = getParent();
+ if (parent == null) return null;
+ try {
+ return parent.startActionModeForChild(this, callback, type);
+ } catch (AbstractMethodError ame) {
+ // Older implementations of custom views might not implement this.
+ return parent.startActionModeForChild(this, callback);
+ }
+ }
+
+ /**
+ * Call {@link Context#startActivityForResult(String, Intent, int, Bundle)} for the View's
+ * Context, creating a unique View identifier to retrieve the result.
+ *
+ * @param intent The Intent to be started.
+ * @param requestCode The request code to use.
+ * @hide
+ */
+ public void startActivityForResult(Intent intent, int requestCode) {
+ mStartActivityRequestWho = "@android:view:" + System.identityHashCode(this);
+ getContext().startActivityForResult(mStartActivityRequestWho, intent, requestCode, null);
+ }
+
+ /**
+ * If this View corresponds to the calling who, dispatches the activity result.
+ * @param who The identifier for the targeted View to receive the result.
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ * @return {@code true} if the activity result was dispatched.
+ * @hide
+ */
+ public boolean dispatchActivityResult(
+ String who, int requestCode, int resultCode, Intent data) {
+ if (mStartActivityRequestWho != null && mStartActivityRequestWho.equals(who)) {
+ onActivityResult(requestCode, resultCode, data);
+ mStartActivityRequestWho = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
+ *
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ * @hide
+ */
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Do nothing.
+ }
+
+ /**
+ * Register a callback to be invoked when a hardware key is pressed in this view.
+ * Key presses in software input methods will generally not trigger the methods of
+ * this listener.
+ * @param l the key listener to attach to this view
+ */
+ public void setOnKeyListener(OnKeyListener l) {
+ getListenerInfo().mOnKeyListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when a touch event is sent to this view.
+ * @param l the touch listener to attach to this view
+ */
+ public void setOnTouchListener(OnTouchListener l) {
+ getListenerInfo().mOnTouchListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when a generic motion event is sent to this view.
+ * @param l the generic motion listener to attach to this view
+ */
+ public void setOnGenericMotionListener(OnGenericMotionListener l) {
+ getListenerInfo().mOnGenericMotionListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when a hover event is sent to this view.
+ * @param l the hover listener to attach to this view
+ */
+ public void setOnHoverListener(OnHoverListener l) {
+ getListenerInfo().mOnHoverListener = l;
+ }
+
+ /**
+ * Register a drag event listener callback object for this View. The parameter is
+ * an implementation of {@link android.view.View.OnDragListener}. To send a drag event to a
+ * View, the system calls the
+ * {@link android.view.View.OnDragListener#onDrag(View,DragEvent)} method.
+ * @param l An implementation of {@link android.view.View.OnDragListener}.
+ */
+ public void setOnDragListener(OnDragListener l) {
+ getListenerInfo().mOnDragListener = l;
+ }
+
+ /**
+ * Give this view focus. This will cause
+ * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
+ *
+ * Note: this does not check whether this {@link View} should get focus, it just
+ * gives it focus no matter what. It should only be called internally by framework
+ * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}.
+ *
+ * @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which
+ * focus moved when requestFocus() is called. It may not always
+ * apply, in which case use the default View.FOCUS_DOWN.
+ * @param previouslyFocusedRect The rectangle of the view that had focus
+ * prior in this View's coordinate system.
+ */
+ void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
+ if (DBG) {
+ System.out.println(this + " requestFocus()");
+ }
+
+ if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
+ mPrivateFlags |= PFLAG_FOCUSED;
+
+ View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
+
+ if (mParent != null) {
+ mParent.requestChildFocus(this, this);
+ updateFocusedInCluster(oldFocus, direction);
+ }
+
+ if (mAttachInfo != null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
+ }
+
+ onFocusChanged(true, direction, previouslyFocusedRect);
+ refreshDrawableState();
+ }
+ }
+
+ /**
+ * Sets this view's preference for reveal behavior when it gains focus.
+ *
+ * <p>When set to true, this is a signal to ancestor views in the hierarchy that
+ * this view would prefer to be brought fully into view when it gains focus.
+ * For example, a text field that a user is meant to type into. Other views such
+ * as scrolling containers may prefer to opt-out of this behavior.</p>
+ *
+ * <p>The default value for views is true, though subclasses may change this
+ * based on their preferred behavior.</p>
+ *
+ * @param revealOnFocus true to request reveal on focus in ancestors, false otherwise
+ *
+ * @see #getRevealOnFocusHint()
+ */
+ public final void setRevealOnFocusHint(boolean revealOnFocus) {
+ if (revealOnFocus) {
+ mPrivateFlags3 &= ~PFLAG3_NO_REVEAL_ON_FOCUS;
+ } else {
+ mPrivateFlags3 |= PFLAG3_NO_REVEAL_ON_FOCUS;
+ }
+ }
+
+ /**
+ * Returns this view's preference for reveal behavior when it gains focus.
+ *
+ * <p>When this method returns true for a child view requesting focus, ancestor
+ * views responding to a focus change in {@link ViewParent#requestChildFocus(View, View)}
+ * should make a best effort to make the newly focused child fully visible to the user.
+ * When it returns false, ancestor views should preferably not disrupt scroll positioning or
+ * other properties affecting visibility to the user as part of the focus change.</p>
+ *
+ * @return true if this view would prefer to become fully visible when it gains focus,
+ * false if it would prefer not to disrupt scroll positioning
+ *
+ * @see #setRevealOnFocusHint(boolean)
+ */
+ public final boolean getRevealOnFocusHint() {
+ return (mPrivateFlags3 & PFLAG3_NO_REVEAL_ON_FOCUS) == 0;
+ }
+
+ /**
+ * Populates <code>outRect</code> with the hotspot bounds. By default,
+ * the hotspot bounds are identical to the screen bounds.
+ *
+ * @param outRect rect to populate with hotspot bounds
+ * @hide Only for internal use by views and widgets.
+ */
+ public void getHotspotBounds(Rect outRect) {
+ final Drawable background = getBackground();
+ if (background != null) {
+ background.getHotspotBounds(outRect);
+ } else {
+ getBoundsOnScreen(outRect);
+ }
+ }
+
+ /**
+ * Request that a rectangle of this view be visible on the screen,
+ * scrolling if necessary just enough.
+ *
+ * <p>A View should call this if it maintains some notion of which part
+ * of its content is interesting. For example, a text editing view
+ * should call this when its cursor moves.
+ * <p>The Rectangle passed into this method should be in the View's content coordinate space.
+ * It should not be affected by which part of the View is currently visible or its scroll
+ * position.
+ *
+ * @param rectangle The rectangle in the View's content coordinate space
+ * @return Whether any parent scrolled.
+ */
+ public boolean requestRectangleOnScreen(Rect rectangle) {
+ return requestRectangleOnScreen(rectangle, false);
+ }
+
+ /**
+ * Request that a rectangle of this view be visible on the screen,
+ * scrolling if necessary just enough.
+ *
+ * <p>A View should call this if it maintains some notion of which part
+ * of its content is interesting. For example, a text editing view
+ * should call this when its cursor moves.
+ * <p>The Rectangle passed into this method should be in the View's content coordinate space.
+ * It should not be affected by which part of the View is currently visible or its scroll
+ * position.
+ * <p>When <code>immediate</code> is set to true, scrolling will not be
+ * animated.
+ *
+ * @param rectangle The rectangle in the View's content coordinate space
+ * @param immediate True to forbid animated scrolling, false otherwise
+ * @return Whether any parent scrolled.
+ */
+ public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
+ if (mParent == null) {
+ return false;
+ }
+
+ View child = this;
+
+ RectF position = (mAttachInfo != null) ? mAttachInfo.mTmpTransformRect : new RectF();
+ position.set(rectangle);
+
+ ViewParent parent = mParent;
+ boolean scrolled = false;
+ while (parent != null) {
+ rectangle.set((int) position.left, (int) position.top,
+ (int) position.right, (int) position.bottom);
+
+ scrolled |= parent.requestChildRectangleOnScreen(child, rectangle, immediate);
+
+ if (!(parent instanceof View)) {
+ break;
+ }
+
+ // move it from child's content coordinate space to parent's content coordinate space
+ position.offset(child.mLeft - child.getScrollX(), child.mTop -child.getScrollY());
+
+ child = (View) parent;
+ parent = child.getParent();
+ }
+
+ return scrolled;
+ }
+
+ /**
+ * Called when this view wants to give up focus. If focus is cleared
+ * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called.
+ * <p>
+ * <strong>Note:</strong> When a View clears focus the framework is trying
+ * to give focus to the first focusable View from the top. Hence, if this
+ * View is the first from the top that can take focus, then all callbacks
+ * related to clearing focus will be invoked after which the framework will
+ * give focus to this view.
+ * </p>
+ */
+ public void clearFocus() {
+ if (DBG) {
+ System.out.println(this + " clearFocus()");
+ }
+
+ clearFocusInternal(null, true, true);
+ }
+
+ /**
+ * Clears focus from the view, optionally propagating the change up through
+ * the parent hierarchy and requesting that the root view place new focus.
+ *
+ * @param propagate whether to propagate the change up through the parent
+ * hierarchy
+ * @param refocus when propagate is true, specifies whether to request the
+ * root view place new focus
+ */
+ void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
+ if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+ mPrivateFlags &= ~PFLAG_FOCUSED;
+
+ if (propagate && mParent != null) {
+ mParent.clearChildFocus(this);
+ }
+
+ onFocusChanged(false, 0, null);
+ refreshDrawableState();
+
+ if (propagate && (!refocus || !rootViewRequestFocus())) {
+ notifyGlobalFocusCleared(this);
+ }
+ }
+ }
+
+ void notifyGlobalFocusCleared(View oldFocus) {
+ if (oldFocus != null && mAttachInfo != null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+ }
+ }
+
+ boolean rootViewRequestFocus() {
+ final View root = getRootView();
+ return root != null && root.requestFocus();
+ }
+
+ /**
+ * Called internally by the view system when a new view is getting focus.
+ * This is what clears the old focus.
+ * <p>
+ * <b>NOTE:</b> The parent view's focused child must be updated manually
+ * after calling this method. Otherwise, the view hierarchy may be left in
+ * an inconstent state.
+ */
+ void unFocus(View focused) {
+ if (DBG) {
+ System.out.println(this + " unFocus()");
+ }
+
+ clearFocusInternal(focused, false, false);
+ }
+
+ /**
+ * Returns true if this view has focus itself, or is the ancestor of the
+ * view that has focus.
+ *
+ * @return True if this view has or contains focus, false otherwise.
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public boolean hasFocus() {
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0;
+ }
+
+ /**
+ * Returns true if this view is focusable or if it contains a reachable View
+ * for which {@link #hasFocusable()} returns {@code true}. A "reachable hasFocusable()"
+ * is a view whose parents do not block descendants focus.
+ * Only {@link #VISIBLE} views are considered focusable.
+ *
+ * <p>As of {@link Build.VERSION_CODES#O} views that are determined to be focusable
+ * through {@link #FOCUSABLE_AUTO} will also cause this method to return {@code true}.
+ * Apps that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} of
+ * earlier than {@link Build.VERSION_CODES#O} will continue to see this method return
+ * {@code false} for views not explicitly marked as focusable.
+ * Use {@link #hasExplicitFocusable()} if you require the pre-{@link Build.VERSION_CODES#O}
+ * behavior.</p>
+ *
+ * @return {@code true} if the view is focusable or if the view contains a focusable
+ * view, {@code false} otherwise
+ *
+ * @see ViewGroup#FOCUS_BLOCK_DESCENDANTS
+ * @see ViewGroup#getTouchscreenBlocksFocus()
+ * @see #hasExplicitFocusable()
+ */
+ public boolean hasFocusable() {
+ return hasFocusable(!sHasFocusableExcludeAutoFocusable, false);
+ }
+
+ /**
+ * Returns true if this view is focusable or if it contains a reachable View
+ * for which {@link #hasExplicitFocusable()} returns {@code true}.
+ * A "reachable hasExplicitFocusable()" is a view whose parents do not block descendants focus.
+ * Only {@link #VISIBLE} views for which {@link #getFocusable()} would return
+ * {@link #FOCUSABLE} are considered focusable.
+ *
+ * <p>This method preserves the pre-{@link Build.VERSION_CODES#O} behavior of
+ * {@link #hasFocusable()} in that only views explicitly set focusable will cause
+ * this method to return true. A view set to {@link #FOCUSABLE_AUTO} that resolves
+ * to focusable will not.</p>
+ *
+ * @return {@code true} if the view is focusable or if the view contains a focusable
+ * view, {@code false} otherwise
+ *
+ * @see #hasFocusable()
+ */
+ public boolean hasExplicitFocusable() {
+ return hasFocusable(false, true);
+ }
+
+ boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {
+ if (!isFocusableInTouchMode()) {
+ for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) {
+ final ViewGroup g = (ViewGroup) p;
+ if (g.shouldBlockFocusForTouchscreen()) {
+ return false;
+ }
+ }
+ }
+
+ // Invisible, gone, or disabled views are never focusable.
+ if ((mViewFlags & VISIBILITY_MASK) != VISIBLE
+ || (mViewFlags & ENABLED_MASK) != ENABLED) {
+ return false;
+ }
+
+ // Only use effective focusable value when allowed.
+ if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Called by the view system when the focus state of this view changes.
+ * When the focus change event is caused by directional navigation, direction
+ * and previouslyFocusedRect provide insight into where the focus is coming from.
+ * When overriding, be sure to call up through to the super class so that
+ * the standard focus handling will occur.
+ *
+ * @param gainFocus True if the View has focus; false otherwise.
+ * @param direction The direction focus has moved when requestFocus()
+ * is called to give this view focus. Values are
+ * {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT},
+ * {@link #FOCUS_RIGHT}, {@link #FOCUS_FORWARD}, or {@link #FOCUS_BACKWARD}.
+ * It may not always apply, in which case use the default.
+ * @param previouslyFocusedRect The rectangle, in this view's coordinate
+ * system, of the previously focused view. If applicable, this will be
+ * passed in as finer grained information about where the focus is coming
+ * from (in addition to direction). Will be <code>null</code> otherwise.
+ */
+ @CallSuper
+ protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
+ @Nullable Rect previouslyFocusedRect) {
+ if (gainFocus) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ } else {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+
+ // Here we check whether we still need the default focus highlight, and switch it on/off.
+ switchDefaultFocusHighlight();
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (!gainFocus) {
+ if (isPressed()) {
+ setPressed(false);
+ }
+ if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
+ imm.focusOut(this);
+ }
+ onFocusLost();
+ } else if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
+ imm.focusIn(this);
+ }
+
+ invalidate(true);
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnFocusChangeListener != null) {
+ li.mOnFocusChangeListener.onFocusChange(this, gainFocus);
+ }
+
+ if (mAttachInfo != null) {
+ mAttachInfo.mKeyDispatchState.reset(this);
+ }
+
+ notifyEnterOrExitForAutoFillIfNeeded(gainFocus);
+ }
+
+ private void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) {
+ if (isAutofillable() && isAttachedToWindow()) {
+ AutofillManager afm = getAutofillManager();
+ if (afm != null) {
+ if (enter && hasWindowFocus() && isFocused()) {
+ // We have not been laid out yet, hence cannot evaluate
+ // whether this view is visible to the user, we will do
+ // the evaluation once layout is complete.
+ if (!isLaidOut()) {
+ mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
+ } else if (isVisibleToUser()) {
+ afm.notifyViewEntered(this);
+ }
+ } else if (!hasWindowFocus() || !isFocused()) {
+ afm.notifyViewExited(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sends an accessibility event of the given type. If accessibility is
+ * not enabled this method has no effect. The default implementation calls
+ * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first
+ * to populate information about the event source (this View), then calls
+ * {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)} to
+ * populate the text content of the event source including its descendants,
+ * and last calls
+ * {@link ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+ * on its parent to request sending of the event to interested parties.
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#sendAccessibilityEvent(View, int)} is
+ * responsible for handling this call.
+ * </p>
+ *
+ * @param eventType The type of the event to send, as defined by several types from
+ * {@link android.view.accessibility.AccessibilityEvent}, such as
+ * {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED} or
+ * {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}.
+ *
+ * @see #onInitializeAccessibilityEvent(AccessibilityEvent)
+ * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ * @see ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)
+ * @see AccessibilityDelegate
+ */
+ public void sendAccessibilityEvent(int eventType) {
+ if (mAccessibilityDelegate != null) {
+ mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
+ } else {
+ sendAccessibilityEventInternal(eventType);
+ }
+ }
+
+ /**
+ * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
+ * {@link AccessibilityEvent} to make an announcement which is related to some
+ * sort of a context change for which none of the events representing UI transitions
+ * is a good fit. For example, announcing a new page in a book. If accessibility
+ * is not enabled this method does nothing.
+ *
+ * @param text The announcement text.
+ */
+ public void announceForAccessibility(CharSequence text) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ onInitializeAccessibilityEvent(event);
+ event.getText().add(text);
+ event.setContentDescription(null);
+ mParent.requestSendAccessibilityEvent(this, event);
+ }
+ }
+
+ /**
+ * @see #sendAccessibilityEvent(int)
+ *
+ * Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public void sendAccessibilityEventInternal(int eventType) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
+ }
+ }
+
+ /**
+ * This method behaves exactly as {@link #sendAccessibilityEvent(int)} but
+ * takes as an argument an empty {@link AccessibilityEvent} and does not
+ * perform a check whether accessibility is enabled.
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#sendAccessibilityEventUnchecked(View, AccessibilityEvent)}
+ * is responsible for handling this call.
+ * </p>
+ *
+ * @param event The event to send.
+ *
+ * @see #sendAccessibilityEvent(int)
+ */
+ public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+ if (mAccessibilityDelegate != null) {
+ mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
+ } else {
+ sendAccessibilityEventUncheckedInternal(event);
+ }
+ }
+
+ /**
+ * @see #sendAccessibilityEventUnchecked(AccessibilityEvent)
+ *
+ * Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
+ if (!isShown()) {
+ return;
+ }
+ onInitializeAccessibilityEvent(event);
+ // Only a subset of accessibility events populates text content.
+ if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
+ dispatchPopulateAccessibilityEvent(event);
+ }
+ // In the beginning we called #isShown(), so we know that getParent() is not null.
+ ViewParent parent = getParent();
+ if (parent != null) {
+ getParent().requestSendAccessibilityEvent(this, event);
+ }
+ }
+
+ /**
+ * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then
+ * to its children for adding their text content to the event. Note that the
+ * event text is populated in a separate dispatch path since we add to the
+ * event not only the text of the source but also the text of all its descendants.
+ * A typical implementation will call
+ * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on the this view
+ * and then call the {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+ * on each child. Override this method if custom population of the event text
+ * content is required.
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#dispatchPopulateAccessibilityEvent(View, AccessibilityEvent)}
+ * is responsible for handling this call.
+ * </p>
+ * <p>
+ * <em>Note:</em> Accessibility events of certain types are not dispatched for
+ * populating the event text via this method. For details refer to {@link AccessibilityEvent}.
+ * </p>
+ *
+ * @param event The event.
+ *
+ * @return True if the event population was completed.
+ */
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (mAccessibilityDelegate != null) {
+ return mAccessibilityDelegate.dispatchPopulateAccessibilityEvent(this, event);
+ } else {
+ return dispatchPopulateAccessibilityEventInternal(event);
+ }
+ }
+
+ /**
+ * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ *
+ * Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return false;
+ }
+
+ /**
+ * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+ * giving a chance to this View to populate the accessibility event with its
+ * text content. While this method is free to modify event
+ * attributes other than text content, doing so should normally be performed in
+ * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}.
+ * <p>
+ * Example: Adding formatted date string to an accessibility event in addition
+ * to the text added by the super implementation:
+ * <pre> public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ * super.onPopulateAccessibilityEvent(event);
+ * final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
+ * String selectedDateUtterance = DateUtils.formatDateTime(mContext,
+ * mCurrentDate.getTimeInMillis(), flags);
+ * event.getText().add(selectedDateUtterance);
+ * }</pre>
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#onPopulateAccessibilityEvent(View, AccessibilityEvent)}
+ * is responsible for handling this call.
+ * </p>
+ * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
+ * information to the event, in case the default implementation has basic information to add.
+ * </p>
+ *
+ * @param event The accessibility event which to populate.
+ *
+ * @see #sendAccessibilityEvent(int)
+ * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ */
+ @CallSuper
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (mAccessibilityDelegate != null) {
+ mAccessibilityDelegate.onPopulateAccessibilityEvent(this, event);
+ } else {
+ onPopulateAccessibilityEventInternal(event);
+ }
+ }
+
+ /**
+ * @see #onPopulateAccessibilityEvent(AccessibilityEvent)
+ *
+ * Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ }
+
+ /**
+ * Initializes an {@link AccessibilityEvent} with information about
+ * this View which is the event source. In other words, the source of
+ * an accessibility event is the view whose state change triggered firing
+ * the event.
+ * <p>
+ * Example: Setting the password property of an event in addition
+ * to properties set by the super implementation:
+ * <pre> public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ * super.onInitializeAccessibilityEvent(event);
+ * event.setPassword(true);
+ * }</pre>
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#onInitializeAccessibilityEvent(View, AccessibilityEvent)}
+ * is responsible for handling this call.
+ * </p>
+ * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
+ * information to the event, in case the default implementation has basic information to add.
+ * </p>
+ * @param event The event to initialize.
+ *
+ * @see #sendAccessibilityEvent(int)
+ * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ */
+ @CallSuper
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ if (mAccessibilityDelegate != null) {
+ mAccessibilityDelegate.onInitializeAccessibilityEvent(this, event);
+ } else {
+ onInitializeAccessibilityEventInternal(event);
+ }
+ }
+
+ /**
+ * @see #onInitializeAccessibilityEvent(AccessibilityEvent)
+ *
+ * Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ event.setSource(this);
+ event.setClassName(getAccessibilityClassName());
+ event.setPackageName(getContext().getPackageName());
+ event.setEnabled(isEnabled());
+ event.setContentDescription(mContentDescription);
+
+ switch (event.getEventType()) {
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+ ArrayList<View> focusablesTempList = (mAttachInfo != null)
+ ? mAttachInfo.mTempArrayList : new ArrayList<View>();
+ getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL);
+ event.setItemCount(focusablesTempList.size());
+ event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+ if (mAttachInfo != null) {
+ focusablesTempList.clear();
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ event.setFromIndex(getAccessibilitySelectionStart());
+ event.setToIndex(getAccessibilitySelectionEnd());
+ event.setItemCount(text.length());
+ }
+ } break;
+ }
+ }
+
+ /**
+ * Returns an {@link AccessibilityNodeInfo} representing this view from the
+ * point of view of an {@link android.accessibilityservice.AccessibilityService}.
+ * This method is responsible for obtaining an accessibility node info from a
+ * pool of reusable instances and calling
+ * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on this view to
+ * initialize the former.
+ * <p>
+ * Note: The client is responsible for recycling the obtained instance by calling
+ * {@link AccessibilityNodeInfo#recycle()} to minimize object creation.
+ * </p>
+ *
+ * @return A populated {@link AccessibilityNodeInfo}.
+ *
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+ if (mAccessibilityDelegate != null) {
+ return mAccessibilityDelegate.createAccessibilityNodeInfo(this);
+ } else {
+ return createAccessibilityNodeInfoInternal();
+ }
+ }
+
+ /**
+ * @see #createAccessibilityNodeInfo()
+ *
+ * @hide
+ */
+ public AccessibilityNodeInfo createAccessibilityNodeInfoInternal() {
+ AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+ if (provider != null) {
+ return provider.createAccessibilityNodeInfo(AccessibilityNodeProvider.HOST_VIEW_ID);
+ } else {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(this);
+ onInitializeAccessibilityNodeInfo(info);
+ return info;
+ }
+ }
+
+ /**
+ * Initializes an {@link AccessibilityNodeInfo} with information about this view.
+ * The base implementation sets:
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#setParent(View)},</li>
+ * <li>{@link AccessibilityNodeInfo#setBoundsInParent(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setClassName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfo#setEnabled(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setFocusable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setFocused(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setLongClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setSelected(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfo#setContextClickable(boolean)}</li>
+ * </ul>
+ * <p>
+ * Subclasses should override this method, call the super implementation,
+ * and set additional attributes.
+ * </p>
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)}
+ * is responsible for handling this call.
+ * </p>
+ *
+ * @param info The instance to initialize.
+ */
+ @CallSuper
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ if (mAccessibilityDelegate != null) {
+ mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(this, info);
+ } else {
+ onInitializeAccessibilityNodeInfoInternal(info);
+ }
+ }
+
+ /**
+ * Gets the location of this view in screen coordinates.
+ *
+ * @param outRect The output location
+ * @hide
+ */
+ public void getBoundsOnScreen(Rect outRect) {
+ getBoundsOnScreen(outRect, false);
+ }
+
+ /**
+ * Gets the location of this view in screen coordinates.
+ *
+ * @param outRect The output location
+ * @param clipToParent Whether to clip child bounds to the parent ones.
+ * @hide
+ */
+ public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ if (mAttachInfo == null) {
+ return;
+ }
+
+ RectF position = mAttachInfo.mTmpTransformRect;
+ position.set(0, 0, mRight - mLeft, mBottom - mTop);
+ mapRectFromViewToScreenCoords(position, clipToParent);
+ outRect.set(Math.round(position.left), Math.round(position.top),
+ Math.round(position.right), Math.round(position.bottom));
+ }
+
+ /**
+ * Map a rectangle from view-relative coordinates to screen-relative coordinates
+ *
+ * @param rect The rectangle to be mapped
+ * @param clipToParent Whether to clip child bounds to the parent ones.
+ * @hide
+ */
+ public void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent) {
+ if (!hasIdentityMatrix()) {
+ getMatrix().mapRect(rect);
+ }
+
+ rect.offset(mLeft, mTop);
+
+ ViewParent parent = mParent;
+ while (parent instanceof View) {
+ View parentView = (View) parent;
+
+ rect.offset(-parentView.mScrollX, -parentView.mScrollY);
+
+ if (clipToParent) {
+ rect.left = Math.max(rect.left, 0);
+ rect.top = Math.max(rect.top, 0);
+ rect.right = Math.min(rect.right, parentView.getWidth());
+ rect.bottom = Math.min(rect.bottom, parentView.getHeight());
+ }
+
+ if (!parentView.hasIdentityMatrix()) {
+ parentView.getMatrix().mapRect(rect);
+ }
+
+ rect.offset(parentView.mLeft, parentView.mTop);
+
+ parent = parentView.mParent;
+ }
+
+ if (parent instanceof ViewRootImpl) {
+ ViewRootImpl viewRootImpl = (ViewRootImpl) parent;
+ rect.offset(0, -viewRootImpl.mCurScrollY);
+ }
+
+ rect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+ }
+
+ /**
+ * Return the class name of this object to be used for accessibility purposes.
+ * Subclasses should only override this if they are implementing something that
+ * should be seen as a completely new class of view when used by accessibility,
+ * unrelated to the class it is deriving from. This is used to fill in
+ * {@link AccessibilityNodeInfo#setClassName AccessibilityNodeInfo.setClassName}.
+ */
+ public CharSequence getAccessibilityClassName() {
+ return View.class.getName();
+ }
+
+ /**
+ * Called when assist structure is being retrieved from a view as part of
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
+ * @param structure Fill in with structured view data. The default implementation
+ * fills in all data that can be inferred from the view itself.
+ */
+ public void onProvideStructure(ViewStructure structure) {
+ onProvideStructureForAssistOrAutofill(structure, false, 0);
+ }
+
+ /**
+ * Populates a {@link ViewStructure} to fullfil an autofill request.
+ *
+ * <p>The structure should contain at least the following properties:
+ * <ul>
+ * <li>Autofill id ({@link ViewStructure#setAutofillId(AutofillId, int)}).
+ * <li>Autofill type ({@link ViewStructure#setAutofillType(int)}).
+ * <li>Autofill value ({@link ViewStructure#setAutofillValue(AutofillValue)}).
+ * <li>Whether the data is sensitive ({@link ViewStructure#setDataIsSensitive(boolean)}).
+ * </ul>
+ *
+ * <p>It's also recommended to set the following properties - the more properties the structure
+ * has, the higher the changes of an {@link android.service.autofill.AutofillService} properly
+ * using the structure:
+ *
+ * <ul>
+ * <li>Autofill hints ({@link ViewStructure#setAutofillHints(String[])}).
+ * <li>Autofill options ({@link ViewStructure#setAutofillOptions(CharSequence[])}) when the
+ * view can only be filled with predefined values (typically used when the autofill type
+ * is {@link #AUTOFILL_TYPE_LIST}).
+ * <li>Resource id ({@link ViewStructure#setId(int, String, String, String)}).
+ * <li>Class name ({@link ViewStructure#setClassName(String)}).
+ * <li>Content description ({@link ViewStructure#setContentDescription(CharSequence)}).
+ * <li>Visual properties such as visibility ({@link ViewStructure#setVisibility(int)}),
+ * dimensions ({@link ViewStructure#setDimens(int, int, int, int, int, int)}), and
+ * opacity ({@link ViewStructure#setOpaque(boolean)}).
+ * <li>For views representing text fields, text properties such as the text itself
+ * ({@link ViewStructure#setText(CharSequence)}), text hints
+ * ({@link ViewStructure#setHint(CharSequence)}, input type
+ * ({@link ViewStructure#setInputType(int)}),
+ * <li>For views representing HTML nodes, its web domain
+ * ({@link ViewStructure#setWebDomain(String)}) and HTML properties
+ * (({@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}).
+ * </ul>
+ *
+ * <p>The default implementation of this method already sets most of these properties based on
+ * related {@link View} methods (for example, the autofill id is set using
+ * {@link #getAutofillId()}, the autofill type set using {@link #getAutofillType()}, etc.),
+ * and views in the standard Android widgets library also override it to set their
+ * relevant properties (for example, {@link android.widget.TextView} already sets the text
+ * properties), so it's recommended to only override this method
+ * (and call {@code super.onProvideAutofillStructure()}) when:
+ *
+ * <ul>
+ * <li>The view contents does not include PII (Personally Identifiable Information), so it
+ * can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}.
+ * <li>The view can only be autofilled with predefined options, so it can call
+ * {@link ViewStructure#setAutofillOptions(CharSequence[])}.
+ * </ul>
+ *
+ * <p><b>Note:</b> The {@code left} and {@code top} values set in
+ * {@link ViewStructure#setDimens(int, int, int, int, int, int)} must be relative to the next
+ * {@link ViewGroup#isImportantForAutofill()} predecessor view included in the structure.
+ *
+ * <p>Views support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the view means and how it can be autofilled.
+ * <li>Notifying the Android System when the view value changed by calling
+ * {@link AutofillManager#notifyValueChanged(View)}.
+ * <li>Implementing the methods that autofill the view.
+ * </ul>
+ * <p>This method is responsible for the former; {@link #autofill(AutofillValue)} is responsible
+ * for the latter.
+ *
+ * @param structure fill in with structured view data for autofill purposes.
+ * @param flags optional flags.
+ *
+ * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ */
+ public void onProvideAutofillStructure(ViewStructure structure, @AutofillFlags int flags) {
+ onProvideStructureForAssistOrAutofill(structure, true, flags);
+ }
+
+ private void onProvideStructureForAssistOrAutofill(ViewStructure structure,
+ boolean forAutofill, @AutofillFlags int flags) {
+ final int id = mID;
+ if (id != NO_ID && !isViewIdGenerated(id)) {
+ String pkg, type, entry;
+ try {
+ final Resources res = getResources();
+ entry = res.getResourceEntryName(id);
+ type = res.getResourceTypeName(id);
+ pkg = res.getResourcePackageName(id);
+ } catch (Resources.NotFoundException e) {
+ entry = type = pkg = null;
+ }
+ structure.setId(id, pkg, type, entry);
+ } else {
+ structure.setId(id, null, null, null);
+ }
+
+ if (forAutofill) {
+ final @AutofillType int autofillType = getAutofillType();
+ // Don't need to fill autofill info if view does not support it.
+ // For example, only TextViews that are editable support autofill
+ if (autofillType != AUTOFILL_TYPE_NONE) {
+ structure.setAutofillType(autofillType);
+ structure.setAutofillHints(getAutofillHints());
+ structure.setAutofillValue(getAutofillValue());
+ }
+ }
+
+ int ignoredParentLeft = 0;
+ int ignoredParentTop = 0;
+ if (forAutofill && (flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+ View parentGroup = null;
+
+ ViewParent viewParent = getParent();
+ if (viewParent instanceof View) {
+ parentGroup = (View) viewParent;
+ }
+
+ while (parentGroup != null && !parentGroup.isImportantForAutofill()) {
+ ignoredParentLeft += parentGroup.mLeft;
+ ignoredParentTop += parentGroup.mTop;
+
+ viewParent = parentGroup.getParent();
+ if (viewParent instanceof View) {
+ parentGroup = (View) viewParent;
+ } else {
+ break;
+ }
+ }
+ }
+
+ structure.setDimens(ignoredParentLeft + mLeft, ignoredParentTop + mTop, mScrollX, mScrollY,
+ mRight - mLeft, mBottom - mTop);
+ if (!forAutofill) {
+ if (!hasIdentityMatrix()) {
+ structure.setTransformation(getMatrix());
+ }
+ structure.setElevation(getZ());
+ }
+ structure.setVisibility(getVisibility());
+ structure.setEnabled(isEnabled());
+ if (isClickable()) {
+ structure.setClickable(true);
+ }
+ if (isFocusable()) {
+ structure.setFocusable(true);
+ }
+ if (isFocused()) {
+ structure.setFocused(true);
+ }
+ if (isAccessibilityFocused()) {
+ structure.setAccessibilityFocused(true);
+ }
+ if (isSelected()) {
+ structure.setSelected(true);
+ }
+ if (isActivated()) {
+ structure.setActivated(true);
+ }
+ if (isLongClickable()) {
+ structure.setLongClickable(true);
+ }
+ if (this instanceof Checkable) {
+ structure.setCheckable(true);
+ if (((Checkable)this).isChecked()) {
+ structure.setChecked(true);
+ }
+ }
+ if (isOpaque()) {
+ structure.setOpaque(true);
+ }
+ if (isContextClickable()) {
+ structure.setContextClickable(true);
+ }
+ structure.setClassName(getAccessibilityClassName().toString());
+ structure.setContentDescription(getContentDescription());
+ }
+
+ /**
+ * Called when assist structure is being retrieved from a view as part of
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} to
+ * generate additional virtual structure under this view. The defaullt implementation
+ * uses {@link #getAccessibilityNodeProvider()} to try to generate this from the
+ * view's virtual accessibility nodes, if any. You can override this for a more
+ * optimal implementation providing this data.
+ */
+ public void onProvideVirtualStructure(ViewStructure structure) {
+ AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+ if (provider != null) {
+ AccessibilityNodeInfo info = createAccessibilityNodeInfo();
+ structure.setChildCount(1);
+ ViewStructure root = structure.newChild(0);
+ populateVirtualStructure(root, provider, info);
+ info.recycle();
+ }
+ }
+
+ /**
+ * Populates a {@link ViewStructure} containing virtual children to fullfil an autofill
+ * request.
+ *
+ * <p>This method should be used when the view manages a virtual structure under this view. For
+ * example, a view that draws input fields using {@link #draw(Canvas)}.
+ *
+ * <p>When implementing this method, subclasses must follow the rules below:
+ *
+ * <ul>
+ * <li>Add virtual children by calling the {@link ViewStructure#newChild(int)} or
+ * {@link ViewStructure#asyncNewChild(int)} methods, where the {@code id} is an unique id
+ * identifying the children in the virtual structure.
+ * <li>The children hierarchy can have multiple levels if necessary, but ideally it should
+ * exclude intermediate levels that are irrelevant for autofill; that would improve the
+ * autofill performance.
+ * <li>Also implement {@link #autofill(SparseArray)} to autofill the virtual
+ * children.
+ * <li>Set the autofill properties of the child structure as defined by
+ * {@link #onProvideAutofillStructure(ViewStructure, int)}, using
+ * {@link ViewStructure#setAutofillId(AutofillId, int)} to set its autofill id.
+ * <li>Call {@link android.view.autofill.AutofillManager#notifyViewEntered(View, int, Rect)}
+ * and/or {@link android.view.autofill.AutofillManager#notifyViewExited(View, int)}
+ * when the focused virtual child changed.
+ * <li>Call
+ * {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)}
+ * when the value of a virtual child changed.
+ * <li>Call {@link
+ * android.view.autofill.AutofillManager#notifyViewVisibilityChanged(View, int, boolean)}
+ * when the visibility of a virtual child changed.
+ * <li>Call {@link AutofillManager#commit()} when the autofill context of the view structure
+ * changed and the current context should be committed (for example, when the user tapped
+ * a {@code SUBMIT} button in an HTML page).
+ * <li>Call {@link AutofillManager#cancel()} when the autofill context of the view structure
+ * changed and the current context should be canceled (for example, when the user tapped
+ * a {@code CANCEL} button in an HTML page).
+ * <li>Provide ways for users to manually request autofill by calling
+ * {@link AutofillManager#requestAutofill(View, int, Rect)}.
+ * <li>The {@code left} and {@code top} values set in
+ * {@link ViewStructure#setDimens(int, int, int, int, int, int)} must be relative to the
+ * next {@link ViewGroup#isImportantForAutofill()} predecessor view included in the
+ * structure.
+ * </ul>
+ *
+ * <p>Views with virtual children support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the virtual children mean and how they can be
+ * autofilled.
+ * <li>Implementing the methods that autofill the virtual children.
+ * </ul>
+ * <p>This method is responsible for the former; {@link #autofill(SparseArray)} is responsible
+ * for the latter.
+ *
+ * @param structure fill in with virtual children data for autofill purposes.
+ * @param flags optional flags.
+ *
+ * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ */
+ public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+ }
+
+ /**
+ * Automatically fills the content of this view with the {@code value}.
+ *
+ * <p>Views support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the view means and how it can be autofilled.
+ * <li>Implementing the methods that autofill the view.
+ * </ul>
+ * <p>{@link #onProvideAutofillStructure(ViewStructure, int)} is responsible for the former,
+ * this method is responsible for latter.
+ *
+ * <p>This method does nothing by default, but when overridden it typically:
+ * <ol>
+ * <li>Checks if the provided value matches the expected type (which is defined by
+ * {@link #getAutofillType()}).
+ * <li>Checks if the view is editable - if it isn't, it should return right away.
+ * <li>Call the proper getter method on {@link AutofillValue} to fetch the actual value.
+ * <li>Pass the actual value to the equivalent setter in the view.
+ * </ol>
+ *
+ * <p>For example, a text-field view could implement the method this way:
+ *
+ * <pre class="prettyprint">
+ * &#64;Override
+ * public void autofill(AutofillValue value) {
+ * if (!value.isText() || !this.isEditable()) {
+ * return;
+ * }
+ * CharSequence text = value.getTextValue();
+ * if (text != null) {
+ * this.setText(text);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>If the value is updated asynchronously, the next call to
+ * {@link AutofillManager#notifyValueChanged(View)} must happen <b>after</b> the value was
+ * changed to the autofilled value. If not, the view will not be considered autofilled.
+ *
+ * <p><b>Note:</b> After this method is called, the value returned by
+ * {@link #getAutofillValue()} must be equal to the {@code value} passed to it, otherwise the
+ * view will not be highlighted as autofilled.
+ *
+ * @param value value to be autofilled.
+ */
+ public void autofill(@SuppressWarnings("unused") AutofillValue value) {
+ }
+
+ /**
+ * Automatically fills the content of the virtual children within this view.
+ *
+ * <p>Views with virtual children support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the virtual children mean and how they can be
+ * autofilled.
+ * <li>Implementing the methods that autofill the virtual children.
+ * </ul>
+ * <p>{@link #onProvideAutofillVirtualStructure(ViewStructure, int)} is responsible for the
+ * former, this method is responsible for the latter - see {@link #autofill(AutofillValue)} and
+ * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info about autofill.
+ *
+ * <p>If a child value is updated asynchronously, the next call to
+ * {@link AutofillManager#notifyValueChanged(View, int, AutofillValue)} must happen
+ * <b>after</b> the value was changed to the autofilled value. If not, the child will not be
+ * considered autofilled.
+ *
+ * <p><b>Note:</b> To indicate that a virtual view was autofilled,
+ * <code>?android:attr/autofilledHighlight</code> should be drawn over it until the data
+ * changes.
+ *
+ * @param values map of values to be autofilled, keyed by virtual child id.
+ *
+ * @attr ref android.R.styleable#Theme_autofilledHighlight
+ */
+ public void autofill(@NonNull @SuppressWarnings("unused") SparseArray<AutofillValue> values) {
+ }
+
+ /**
+ * Gets the unique identifier of this view in the screen, for autofill purposes.
+ *
+ * @return The View's autofill id.
+ */
+ public final AutofillId getAutofillId() {
+ if (mAutofillId == null) {
+ // The autofill id needs to be unique, but its value doesn't matter,
+ // so it's better to reuse the accessibility id to save space.
+ mAutofillId = new AutofillId(getAutofillViewId());
+ }
+ return mAutofillId;
+ }
+
+ /**
+ * Describes the autofill type of this view, so an
+ * {@link android.service.autofill.AutofillService} can create the proper {@link AutofillValue}
+ * when autofilling the view.
+ *
+ * <p>By default returns {@link #AUTOFILL_TYPE_NONE}, but views should override it to properly
+ * support the Autofill Framework.
+ *
+ * @return either {@link #AUTOFILL_TYPE_NONE}, {@link #AUTOFILL_TYPE_TEXT},
+ * {@link #AUTOFILL_TYPE_LIST}, {@link #AUTOFILL_TYPE_DATE}, or {@link #AUTOFILL_TYPE_TOGGLE}.
+ *
+ * @see #onProvideAutofillStructure(ViewStructure, int)
+ * @see #autofill(AutofillValue)
+ */
+ public @AutofillType int getAutofillType() {
+ return AUTOFILL_TYPE_NONE;
+ }
+
+ /**
+ * Gets the hints that help an {@link android.service.autofill.AutofillService} determine how
+ * to autofill the view with the user's data.
+ *
+ * <p>See {@link #setAutofillHints(String...)} for more info about these hints.
+ *
+ * @return The hints set via the attribute or {@link #setAutofillHints(String...)}, or
+ * {@code null} if no hints were set.
+ *
+ * @attr ref android.R.styleable#View_autofillHints
+ */
+ @ViewDebug.ExportedProperty()
+ @Nullable public String[] getAutofillHints() {
+ return mAutofillHints;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isAutofilled() {
+ return (mPrivateFlags3 & PFLAG3_IS_AUTOFILLED) != 0;
+ }
+
+ /**
+ * Gets the {@link View}'s current autofill value.
+ *
+ * <p>By default returns {@code null}, but subclasses should override it and return an
+ * appropriate value to properly support the Autofill Framework.
+ *
+ * @see #onProvideAutofillStructure(ViewStructure, int)
+ * @see #autofill(AutofillValue)
+ */
+ @Nullable
+ public AutofillValue getAutofillValue() {
+ return null;
+ }
+
+ /**
+ * Gets the mode for determining whether this view is important for autofill.
+ *
+ * <p>See {@link #setImportantForAutofill(int)} and {@link #isImportantForAutofill()} for more
+ * info about this mode.
+ *
+ * @return {@link #IMPORTANT_FOR_AUTOFILL_AUTO} by default, or value passed to
+ * {@link #setImportantForAutofill(int)}.
+ *
+ * @attr ref android.R.styleable#View_importantForAutofill
+ */
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_AUTO, to = "auto"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_YES, to = "yes"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_NO, to = "no"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS,
+ to = "yesExcludeDescendants"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS,
+ to = "noExcludeDescendants")})
+ public @AutofillImportance int getImportantForAutofill() {
+ return (mPrivateFlags3
+ & PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK) >> PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT;
+ }
+
+ /**
+ * Sets the mode for determining whether this view is considered important for autofill.
+ *
+ * <p>The platform determines the importance for autofill automatically but you
+ * can use this method to customize the behavior. For example:
+ *
+ * <ol>
+ * <li>When the view contents is irrelevant for autofill (for example, a text field used in a
+ * "Captcha" challenge), it should be {@link #IMPORTANT_FOR_AUTOFILL_NO}.
+ * <li>When both the view and its children are irrelevant for autofill (for example, the root
+ * view of an activity containing a spreadhseet editor), it should be
+ * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}.
+ * <li>When the view content is relevant for autofill but its children aren't (for example,
+ * a credit card expiration date represented by a custom view that overrides the proper
+ * autofill methods and has 2 children representing the month and year), it should
+ * be {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}.
+ * </ol>
+ *
+ * <p><b>Note:</b> Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or
+ * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its
+ * children) will be always be considered not important; for example, when the user explicitly
+ * makes an autofill request, all views are considered important. See
+ * {@link #isImportantForAutofill()} for more details about how the View's importance for
+ * autofill is used.
+ *
+ * @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES},
+ * {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS},
+ * or {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}.
+ *
+ * @attr ref android.R.styleable#View_importantForAutofill
+ */
+ public void setImportantForAutofill(@AutofillImportance int mode) {
+ mPrivateFlags3 &= ~PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK;
+ mPrivateFlags3 |= (mode << PFLAG3_IMPORTANT_FOR_AUTOFILL_SHIFT)
+ & PFLAG3_IMPORTANT_FOR_AUTOFILL_MASK;
+ }
+
+ /**
+ * Hints the Android System whether the {@link android.app.assist.AssistStructure.ViewNode}
+ * associated with this view is considered important for autofill purposes.
+ *
+ * <p>Generally speaking, a view is important for autofill if:
+ * <ol>
+ * <li>The view can be autofilled by an {@link android.service.autofill.AutofillService}.
+ * <li>The view contents can help an {@link android.service.autofill.AutofillService}
+ * determine how other views can be autofilled.
+ * <ol>
+ *
+ * <p>For example, view containers should typically return {@code false} for performance reasons
+ * (since the important info is provided by their children), but if its properties have relevant
+ * information (for example, a resource id called {@code credentials}, it should return
+ * {@code true}. On the other hand, views representing labels or editable fields should
+ * typically return {@code true}, but in some cases they could return {@code false}
+ * (for example, if they're part of a "Captcha" mechanism).
+ *
+ * <p>The value returned by this method depends on the value returned by
+ * {@link #getImportantForAutofill()}:
+ *
+ * <ol>
+ * <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_YES} or
+ * {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, then it returns {@code true}
+ * <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_NO} or
+ * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}, then it returns {@code false}
+ * <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, then it uses some simple heuristics
+ * that can return {@code true} in some cases (like a container with a resource id),
+ * but {@code false} in most.
+ * <li>otherwise, it returns {@code false}.
+ * </ol>
+ *
+ * <p>When a view is considered important for autofill:
+ * <ul>
+ * <li>The view might automatically trigger an autofill request when focused on.
+ * <li>The contents of the view are included in the {@link ViewStructure} used in an autofill
+ * request.
+ * </ul>
+ *
+ * <p>On the other hand, when a view is considered not important for autofill:
+ * <ul>
+ * <li>The view never automatically triggers autofill requests, but it can trigger a manual
+ * request through {@link AutofillManager#requestAutofill(View)}.
+ * <li>The contents of the view are not included in the {@link ViewStructure} used in an
+ * autofill request, unless the request has the
+ * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag.
+ * </ul>
+ *
+ * @return whether the view is considered important for autofill.
+ *
+ * @see #setImportantForAutofill(int)
+ * @see #IMPORTANT_FOR_AUTOFILL_AUTO
+ * @see #IMPORTANT_FOR_AUTOFILL_YES
+ * @see #IMPORTANT_FOR_AUTOFILL_NO
+ * @see #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
+ * @see #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+ * @see AutofillManager#requestAutofill(View)
+ */
+ public final boolean isImportantForAutofill() {
+ // Check parent mode to ensure we're not hidden.
+ ViewParent parent = mParent;
+ while (parent instanceof View) {
+ final int parentImportance = ((View) parent).getImportantForAutofill();
+ if (parentImportance == IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+ || parentImportance == IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS) {
+ return false;
+ }
+ parent = parent.getParent();
+ }
+
+ final int importance = getImportantForAutofill();
+
+ // First, check the explicit states.
+ if (importance == IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
+ || importance == IMPORTANT_FOR_AUTOFILL_YES) {
+ return true;
+ }
+ if (importance == IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+ || importance == IMPORTANT_FOR_AUTOFILL_NO) {
+ return false;
+ }
+
+ // Then use some heuristics to handle AUTO.
+
+ // Always include views that have an explicit resource id.
+ final int id = mID;
+ if (id != NO_ID && !isViewIdGenerated(id)) {
+ final Resources res = getResources();
+ String entry = null;
+ String pkg = null;
+ try {
+ entry = res.getResourceEntryName(id);
+ pkg = res.getResourcePackageName(id);
+ } catch (Resources.NotFoundException e) {
+ // ignore
+ }
+ if (entry != null && pkg != null && pkg.equals(mContext.getPackageName())) {
+ return true;
+ }
+ }
+
+ // Otherwise, assume it's not important...
+ return false;
+ }
+
+ @Nullable
+ private AutofillManager getAutofillManager() {
+ return mContext.getSystemService(AutofillManager.class);
+ }
+
+ private boolean isAutofillable() {
+ return getAutofillType() != AUTOFILL_TYPE_NONE && isImportantForAutofill()
+ && getAutofillViewId() > LAST_APP_AUTOFILL_ID;
+ }
+
+ private void populateVirtualStructure(ViewStructure structure,
+ AccessibilityNodeProvider provider, AccessibilityNodeInfo info) {
+ structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
+ null, null, null);
+ Rect rect = structure.getTempRect();
+ info.getBoundsInParent(rect);
+ structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
+ structure.setVisibility(VISIBLE);
+ structure.setEnabled(info.isEnabled());
+ if (info.isClickable()) {
+ structure.setClickable(true);
+ }
+ if (info.isFocusable()) {
+ structure.setFocusable(true);
+ }
+ if (info.isFocused()) {
+ structure.setFocused(true);
+ }
+ if (info.isAccessibilityFocused()) {
+ structure.setAccessibilityFocused(true);
+ }
+ if (info.isSelected()) {
+ structure.setSelected(true);
+ }
+ if (info.isLongClickable()) {
+ structure.setLongClickable(true);
+ }
+ if (info.isCheckable()) {
+ structure.setCheckable(true);
+ if (info.isChecked()) {
+ structure.setChecked(true);
+ }
+ }
+ if (info.isContextClickable()) {
+ structure.setContextClickable(true);
+ }
+ CharSequence cname = info.getClassName();
+ structure.setClassName(cname != null ? cname.toString() : null);
+ structure.setContentDescription(info.getContentDescription());
+ if ((info.getText() != null || info.getError() != null)) {
+ structure.setText(info.getText(), info.getTextSelectionStart(),
+ info.getTextSelectionEnd());
+ }
+ final int NCHILDREN = info.getChildCount();
+ if (NCHILDREN > 0) {
+ structure.setChildCount(NCHILDREN);
+ for (int i=0; i<NCHILDREN; i++) {
+ AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
+ ViewStructure child = structure.newChild(i);
+ populateVirtualStructure(child, provider, cinfo);
+ cinfo.recycle();
+ }
+ }
+ }
+
+ /**
+ * Dispatch creation of {@link ViewStructure} down the hierarchy. The default
+ * implementation calls {@link #onProvideStructure} and
+ * {@link #onProvideVirtualStructure}.
+ */
+ public void dispatchProvideStructure(ViewStructure structure) {
+ dispatchProvideStructureForAssistOrAutofill(structure, false, 0);
+ }
+
+ /**
+ * Dispatches creation of a {@link ViewStructure}s for autofill purposes down the hierarchy,
+ * when an Assist structure is being created as part of an autofill request.
+ *
+ * <p>The default implementation does the following:
+ * <ul>
+ * <li>Sets the {@link AutofillId} in the structure.
+ * <li>Calls {@link #onProvideAutofillStructure(ViewStructure, int)}.
+ * <li>Calls {@link #onProvideAutofillVirtualStructure(ViewStructure, int)}.
+ * </ul>
+ *
+ * <p>Typically, this method should only be overridden by subclasses that provide a view
+ * hierarchy (such as {@link ViewGroup}) - other classes should override
+ * {@link #onProvideAutofillStructure(ViewStructure, int)} or
+ * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} instead.
+ *
+ * <p>When overridden, it must:
+ *
+ * <ul>
+ * <li>Either call
+ * {@code super.dispatchProvideAutofillStructure(structure, flags)} or explicitly
+ * set the {@link AutofillId} in the structure (for example, by calling
+ * {@code structure.setAutofillId(getAutofillId())}).
+ * <li>Decide how to handle the {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag - when
+ * set, all views in the structure should be considered important for autofill,
+ * regardless of what {@link #isImportantForAutofill()} returns. We encourage you to
+ * respect this flag to provide a better user experience - this flag is typically used
+ * when an user explicitly requested autofill. If the flag is not set,
+ * then only views marked as important for autofill should be included in the
+ * structure - skipping non-important views optimizes the overall autofill performance.
+ * </ul>
+ *
+ * @param structure fill in with structured view data for autofill purposes.
+ * @param flags optional flags.
+ *
+ * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ */
+ public void dispatchProvideAutofillStructure(@NonNull ViewStructure structure,
+ @AutofillFlags int flags) {
+ dispatchProvideStructureForAssistOrAutofill(structure, true, flags);
+ }
+
+ private void dispatchProvideStructureForAssistOrAutofill(ViewStructure structure,
+ boolean forAutofill, @AutofillFlags int flags) {
+ if (forAutofill) {
+ structure.setAutofillId(getAutofillId());
+ onProvideAutofillStructure(structure, flags);
+ onProvideAutofillVirtualStructure(structure, flags);
+ } else if (!isAssistBlocked()) {
+ onProvideStructure(structure);
+ onProvideVirtualStructure(structure);
+ } else {
+ structure.setClassName(getAccessibilityClassName().toString());
+ structure.setAssistBlocked(true);
+ }
+ }
+
+ /**
+ * @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ *
+ * Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ if (mAttachInfo == null) {
+ return;
+ }
+
+ Rect bounds = mAttachInfo.mTmpInvalRect;
+
+ getDrawingRect(bounds);
+ info.setBoundsInParent(bounds);
+
+ getBoundsOnScreen(bounds, true);
+ info.setBoundsInScreen(bounds);
+
+ ViewParent parent = getParentForAccessibility();
+ if (parent instanceof View) {
+ info.setParent((View) parent);
+ }
+
+ if (mID != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+
+ View label = rootView.findLabelForView(this, mID);
+ if (label != null) {
+ info.setLabeledBy(label);
+ }
+
+ if ((mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
+ && Resources.resourceHasPackage(mID)) {
+ try {
+ String viewId = getResources().getResourceName(mID);
+ info.setViewIdResourceName(viewId);
+ } catch (Resources.NotFoundException nfe) {
+ /* ignore */
+ }
+ }
+ }
+
+ if (mLabelForId != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+ View labeled = rootView.findViewInsideOutShouldExist(this, mLabelForId);
+ if (labeled != null) {
+ info.setLabelFor(labeled);
+ }
+ }
+
+ if (mAccessibilityTraversalBeforeId != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+ View next = rootView.findViewInsideOutShouldExist(this,
+ mAccessibilityTraversalBeforeId);
+ if (next != null && next.includeForAccessibility()) {
+ info.setTraversalBefore(next);
+ }
+ }
+
+ if (mAccessibilityTraversalAfterId != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+ View next = rootView.findViewInsideOutShouldExist(this,
+ mAccessibilityTraversalAfterId);
+ if (next != null && next.includeForAccessibility()) {
+ info.setTraversalAfter(next);
+ }
+ }
+
+ info.setVisibleToUser(isVisibleToUser());
+
+ info.setImportantForAccessibility(isImportantForAccessibility());
+ info.setPackageName(mContext.getPackageName());
+ info.setClassName(getAccessibilityClassName());
+ info.setContentDescription(getContentDescription());
+
+ info.setEnabled(isEnabled());
+ info.setClickable(isClickable());
+ info.setFocusable(isFocusable());
+ info.setFocused(isFocused());
+ info.setAccessibilityFocused(isAccessibilityFocused());
+ info.setSelected(isSelected());
+ info.setLongClickable(isLongClickable());
+ info.setContextClickable(isContextClickable());
+ info.setLiveRegion(getAccessibilityLiveRegion());
+
+ // TODO: These make sense only if we are in an AdapterView but all
+ // views can be selected. Maybe from accessibility perspective
+ // we should report as selectable view in an AdapterView.
+ info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+
+ if (isFocusable()) {
+ if (isFocused()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+ } else {
+ info.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ }
+ }
+
+ if (!isAccessibilityFocused()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ } else {
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+ }
+
+ if (isClickable() && isEnabled()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+
+ if (isLongClickable() && isEnabled()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+ }
+
+ if (isContextClickable() && isEnabled()) {
+ info.addAction(AccessibilityAction.ACTION_CONTEXT_CLICK);
+ }
+
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ info.setTextSelection(getAccessibilitySelectionStart(), getAccessibilitySelectionEnd());
+
+ info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+ info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+ info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+ info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+ }
+
+ info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN);
+ populateAccessibilityNodeInfoDrawingOrderInParent(info);
+ }
+
+ /**
+ * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+ * additional data.
+ * <p>
+ * This method only needs overloading if the node is marked as having extra data available.
+ * </p>
+ *
+ * @param info The info to which to add the extra data. Never {@code null}.
+ * @param extraDataKey A key specifying the type of extra data to add to the info. The
+ * extra data should be added to the {@link Bundle} returned by
+ * the info's {@link AccessibilityNodeInfo#getExtras} method. Never
+ * {@code null}.
+ * @param arguments A {@link Bundle} holding any arguments relevant for this request. May be
+ * {@code null} if the service provided no arguments.
+ *
+ * @see AccessibilityNodeInfo#setAvailableExtraData(List)
+ */
+ public void addExtraDataToAccessibilityNodeInfo(
+ @NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
+ @Nullable Bundle arguments) {
+ }
+
+ /**
+ * Determine the order in which this view will be drawn relative to its siblings for a11y
+ *
+ * @param info The info whose drawing order should be populated
+ */
+ private void populateAccessibilityNodeInfoDrawingOrderInParent(AccessibilityNodeInfo info) {
+ /*
+ * If the view's bounds haven't been set yet, layout has not completed. In that situation,
+ * drawing order may not be well-defined, and some Views with custom drawing order may
+ * not be initialized sufficiently to respond properly getChildDrawingOrder.
+ */
+ if ((mPrivateFlags & PFLAG_HAS_BOUNDS) == 0) {
+ info.setDrawingOrder(0);
+ return;
+ }
+ int drawingOrderInParent = 1;
+ // Iterate up the hierarchy if parents are not important for a11y
+ View viewAtDrawingLevel = this;
+ final ViewParent parent = getParentForAccessibility();
+ while (viewAtDrawingLevel != parent) {
+ final ViewParent currentParent = viewAtDrawingLevel.getParent();
+ if (!(currentParent instanceof ViewGroup)) {
+ // Should only happen for the Decor
+ drawingOrderInParent = 0;
+ break;
+ } else {
+ final ViewGroup parentGroup = (ViewGroup) currentParent;
+ final int childCount = parentGroup.getChildCount();
+ if (childCount > 1) {
+ List<View> preorderedList = parentGroup.buildOrderedChildList();
+ if (preorderedList != null) {
+ final int childDrawIndex = preorderedList.indexOf(viewAtDrawingLevel);
+ for (int i = 0; i < childDrawIndex; i++) {
+ drawingOrderInParent += numViewsForAccessibility(preorderedList.get(i));
+ }
+ } else {
+ final int childIndex = parentGroup.indexOfChild(viewAtDrawingLevel);
+ final boolean customOrder = parentGroup.isChildrenDrawingOrderEnabled();
+ final int childDrawIndex = ((childIndex >= 0) && customOrder) ? parentGroup
+ .getChildDrawingOrder(childCount, childIndex) : childIndex;
+ final int numChildrenToIterate = customOrder ? childCount : childDrawIndex;
+ if (childDrawIndex != 0) {
+ for (int i = 0; i < numChildrenToIterate; i++) {
+ final int otherDrawIndex = (customOrder ?
+ parentGroup.getChildDrawingOrder(childCount, i) : i);
+ if (otherDrawIndex < childDrawIndex) {
+ drawingOrderInParent +=
+ numViewsForAccessibility(parentGroup.getChildAt(i));
+ }
+ }
+ }
+ }
+ }
+ }
+ viewAtDrawingLevel = (View) currentParent;
+ }
+ info.setDrawingOrder(drawingOrderInParent);
+ }
+
+ private static int numViewsForAccessibility(View view) {
+ if (view != null) {
+ if (view.includeForAccessibility()) {
+ return 1;
+ } else if (view instanceof ViewGroup) {
+ return ((ViewGroup) view).getNumChildrenForAccessibility();
+ }
+ }
+ return 0;
+ }
+
+ private View findLabelForView(View view, int labeledId) {
+ if (mMatchLabelForPredicate == null) {
+ mMatchLabelForPredicate = new MatchLabelForPredicate();
+ }
+ mMatchLabelForPredicate.mLabeledId = labeledId;
+ return findViewByPredicateInsideOut(view, mMatchLabelForPredicate);
+ }
+
+ /**
+ * Computes whether this view is visible to the user. Such a view is
+ * attached, visible, all its predecessors are visible, it is not clipped
+ * entirely by its predecessors, and has an alpha greater than zero.
+ *
+ * @return Whether the view is visible on the screen.
+ *
+ * @hide
+ */
+ protected boolean isVisibleToUser() {
+ return isVisibleToUser(null);
+ }
+
+ /**
+ * Computes whether the given portion of this view is visible to the user.
+ * Such a view is attached, visible, all its predecessors are visible,
+ * has an alpha greater than zero, and the specified portion is not
+ * clipped entirely by its predecessors.
+ *
+ * @param boundInView the portion of the view to test; coordinates should be relative; may be
+ * <code>null</code>, and the entire view will be tested in this case.
+ * When <code>true</code> is returned by the function, the actual visible
+ * region will be stored in this parameter; that is, if boundInView is fully
+ * contained within the view, no modification will be made, otherwise regions
+ * outside of the visible area of the view will be clipped.
+ *
+ * @return Whether the specified portion of the view is visible on the screen.
+ *
+ * @hide
+ */
+ protected boolean isVisibleToUser(Rect boundInView) {
+ if (mAttachInfo != null) {
+ // Attached to invisible window means this view is not visible.
+ if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
+ return false;
+ }
+ // An invisible predecessor or one with alpha zero means
+ // that this view is not visible to the user.
+ Object current = this;
+ while (current instanceof View) {
+ View view = (View) current;
+ // We have attach info so this view is attached and there is no
+ // need to check whether we reach to ViewRootImpl on the way up.
+ if (view.getAlpha() <= 0 || view.getTransitionAlpha() <= 0 ||
+ view.getVisibility() != VISIBLE) {
+ return false;
+ }
+ current = view.mParent;
+ }
+ // Check if the view is entirely covered by its predecessors.
+ Rect visibleRect = mAttachInfo.mTmpInvalRect;
+ Point offset = mAttachInfo.mPoint;
+ if (!getGlobalVisibleRect(visibleRect, offset)) {
+ return false;
+ }
+ // Check if the visible portion intersects the rectangle of interest.
+ if (boundInView != null) {
+ visibleRect.offset(-offset.x, -offset.y);
+ return boundInView.intersect(visibleRect);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the delegate for implementing accessibility support via
+ * composition. For more details see {@link AccessibilityDelegate}.
+ *
+ * @return The delegate, or null if none set.
+ *
+ * @hide
+ */
+ public AccessibilityDelegate getAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ /**
+ * Sets a delegate for implementing accessibility support via composition
+ * (as opposed to inheritance). For more details, see
+ * {@link AccessibilityDelegate}.
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
+ *
+ * @param delegate the object to which accessibility method calls should be
+ * delegated
+ * @see AccessibilityDelegate
+ */
+ public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate) {
+ mAccessibilityDelegate = delegate;
+ }
+
+ /**
+ * Gets the provider for managing a virtual view hierarchy rooted at this View
+ * and reported to {@link android.accessibilityservice.AccessibilityService}s
+ * that explore the window content.
+ * <p>
+ * If this method returns an instance, this instance is responsible for managing
+ * {@link AccessibilityNodeInfo}s describing the virtual sub-tree rooted at this
+ * View including the one representing the View itself. Similarly the returned
+ * instance is responsible for performing accessibility actions on any virtual
+ * view or the root view itself.
+ * </p>
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#getAccessibilityNodeProvider(View)}
+ * is responsible for handling this call.
+ * </p>
+ *
+ * @return The provider.
+ *
+ * @see AccessibilityNodeProvider
+ */
+ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+ if (mAccessibilityDelegate != null) {
+ return mAccessibilityDelegate.getAccessibilityNodeProvider(this);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the unique identifier of this view on the screen for accessibility purposes.
+ *
+ * @return The view accessibility id.
+ *
+ * @hide
+ */
+ public int getAccessibilityViewId() {
+ if (mAccessibilityViewId == NO_ID) {
+ mAccessibilityViewId = sNextAccessibilityViewId++;
+ }
+ return mAccessibilityViewId;
+ }
+
+ /**
+ * Gets the unique identifier of this view on the screen for autofill purposes.
+ *
+ * @return The view autofill id.
+ *
+ * @hide
+ */
+ public int getAutofillViewId() {
+ if (mAutofillViewId == NO_ID) {
+ mAutofillViewId = mContext.getNextAutofillId();
+ }
+ return mAutofillViewId;
+ }
+
+ /**
+ * Gets the unique identifier of the window in which this View reseides.
+ *
+ * @return The window accessibility id.
+ *
+ * @hide
+ */
+ public int getAccessibilityWindowId() {
+ return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId
+ : AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+
+ /**
+ * Returns the {@link View}'s content description.
+ * <p>
+ * <strong>Note:</strong> Do not override this method, as it will have no
+ * effect on the content description presented to accessibility services.
+ * You must call {@link #setContentDescription(CharSequence)} to modify the
+ * content description.
+ *
+ * @return the content description
+ * @see #setContentDescription(CharSequence)
+ * @attr ref android.R.styleable#View_contentDescription
+ */
+ @ViewDebug.ExportedProperty(category = "accessibility")
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the {@link View}'s content description.
+ * <p>
+ * A content description briefly describes the view and is primarily used
+ * for accessibility support to determine how a view should be presented to
+ * the user. In the case of a view with no textual representation, such as
+ * {@link android.widget.ImageButton}, a useful content description
+ * explains what the view does. For example, an image button with a phone
+ * icon that is used to place a call may use "Call" as its content
+ * description. An image of a floppy disk that is used to save a file may
+ * use "Save".
+ *
+ * @param contentDescription The content description.
+ * @see #getContentDescription()
+ * @attr ref android.R.styleable#View_contentDescription
+ */
+ @RemotableViewMethod
+ public void setContentDescription(CharSequence contentDescription) {
+ if (mContentDescription == null) {
+ if (contentDescription == null) {
+ return;
+ }
+ } else if (mContentDescription.equals(contentDescription)) {
+ return;
+ }
+ mContentDescription = contentDescription;
+ final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0;
+ if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ } else {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
+ }
+ }
+
+ /**
+ * Sets the id of a view before which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of this view before the content of the one
+ * it precedes. For example, if view B is set to be before view A, then a screen-reader
+ * will traverse the entire content of B before traversing the entire content of A,
+ * regardles of what traversal strategy it is using.
+ * <p>
+ * Views that do not have specified before/after relationships are traversed in order
+ * determined by the screen-reader.
+ * </p>
+ * <p>
+ * Setting that this view is before a view that is not important for accessibility
+ * or if this view is not important for accessibility will have no effect as the
+ * screen-reader is not aware of unimportant views.
+ * </p>
+ *
+ * @param beforeId The id of a view this one precedes in accessibility traversal.
+ *
+ * @attr ref android.R.styleable#View_accessibilityTraversalBefore
+ *
+ * @see #setImportantForAccessibility(int)
+ */
+ @RemotableViewMethod
+ public void setAccessibilityTraversalBefore(int beforeId) {
+ if (mAccessibilityTraversalBeforeId == beforeId) {
+ return;
+ }
+ mAccessibilityTraversalBeforeId = beforeId;
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Gets the id of a view before which this one is visited in accessibility traversal.
+ *
+ * @return The id of a view this one precedes in accessibility traversal if
+ * specified, otherwise {@link #NO_ID}.
+ *
+ * @see #setAccessibilityTraversalBefore(int)
+ */
+ public int getAccessibilityTraversalBefore() {
+ return mAccessibilityTraversalBeforeId;
+ }
+
+ /**
+ * Sets the id of a view after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other view before the content of this
+ * one. For example, if view B is set to be after view A, then a screen-reader
+ * will traverse the entire content of A before traversing the entire content of B,
+ * regardles of what traversal strategy it is using.
+ * <p>
+ * Views that do not have specified before/after relationships are traversed in order
+ * determined by the screen-reader.
+ * </p>
+ * <p>
+ * Setting that this view is after a view that is not important for accessibility
+ * or if this view is not important for accessibility will have no effect as the
+ * screen-reader is not aware of unimportant views.
+ * </p>
+ *
+ * @param afterId The id of a view this one succedees in accessibility traversal.
+ *
+ * @attr ref android.R.styleable#View_accessibilityTraversalAfter
+ *
+ * @see #setImportantForAccessibility(int)
+ */
+ @RemotableViewMethod
+ public void setAccessibilityTraversalAfter(int afterId) {
+ if (mAccessibilityTraversalAfterId == afterId) {
+ return;
+ }
+ mAccessibilityTraversalAfterId = afterId;
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Gets the id of a view after which this one is visited in accessibility traversal.
+ *
+ * @return The id of a view this one succeedes in accessibility traversal if
+ * specified, otherwise {@link #NO_ID}.
+ *
+ * @see #setAccessibilityTraversalAfter(int)
+ */
+ public int getAccessibilityTraversalAfter() {
+ return mAccessibilityTraversalAfterId;
+ }
+
+ /**
+ * Gets the id of a view for which this view serves as a label for
+ * accessibility purposes.
+ *
+ * @return The labeled view id.
+ */
+ @ViewDebug.ExportedProperty(category = "accessibility")
+ public int getLabelFor() {
+ return mLabelForId;
+ }
+
+ /**
+ * Sets the id of a view for which this view serves as a label for
+ * accessibility purposes.
+ *
+ * @param id The labeled view id.
+ */
+ @RemotableViewMethod
+ public void setLabelFor(@IdRes int id) {
+ if (mLabelForId == id) {
+ return;
+ }
+ mLabelForId = id;
+ if (mLabelForId != View.NO_ID
+ && mID == View.NO_ID) {
+ mID = generateViewId();
+ }
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Invoked whenever this view loses focus, either by losing window focus or by losing
+ * focus within its window. This method can be used to clear any state tied to the
+ * focus. For instance, if a button is held pressed with the trackball and the window
+ * loses focus, this method can be used to cancel the press.
+ *
+ * Subclasses of View overriding this method should always call super.onFocusLost().
+ *
+ * @see #onFocusChanged(boolean, int, android.graphics.Rect)
+ * @see #onWindowFocusChanged(boolean)
+ *
+ * @hide pending API council approval
+ */
+ @CallSuper
+ protected void onFocusLost() {
+ resetPressedState();
+ }
+
+ private void resetPressedState() {
+ if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+ return;
+ }
+
+ if (isPressed()) {
+ setPressed(false);
+
+ if (!mHasPerformedLongPress) {
+ removeLongPressCallback();
+ }
+ }
+ }
+
+ /**
+ * Returns true if this view has focus
+ *
+ * @return True if this view has focus, false otherwise.
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public boolean isFocused() {
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0;
+ }
+
+ /**
+ * Find the view in the hierarchy rooted at this view that currently has
+ * focus.
+ *
+ * @return The view that currently has focus, or null if no focused view can
+ * be found.
+ */
+ public View findFocus() {
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
+ }
+
+ /**
+ * Indicates whether this view is one of the set of scrollable containers in
+ * its window.
+ *
+ * @return whether this view is one of the set of scrollable containers in
+ * its window
+ *
+ * @attr ref android.R.styleable#View_isScrollContainer
+ */
+ public boolean isScrollContainer() {
+ return (mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0;
+ }
+
+ /**
+ * Change whether this view is one of the set of scrollable containers in
+ * its window. This will be used to determine whether the window can
+ * resize or must pan when a soft input area is open -- scrollable
+ * containers allow the window to use resize mode since the container
+ * will appropriately shrink.
+ *
+ * @attr ref android.R.styleable#View_isScrollContainer
+ */
+ public void setScrollContainer(boolean isScrollContainer) {
+ if (isScrollContainer) {
+ if (mAttachInfo != null && (mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) == 0) {
+ mAttachInfo.mScrollContainers.add(this);
+ mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
+ }
+ mPrivateFlags |= PFLAG_SCROLL_CONTAINER;
+ } else {
+ if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
+ mAttachInfo.mScrollContainers.remove(this);
+ }
+ mPrivateFlags &= ~(PFLAG_SCROLL_CONTAINER|PFLAG_SCROLL_CONTAINER_ADDED);
+ }
+ }
+
+ /**
+ * Returns the quality of the drawing cache.
+ *
+ * @return One of {@link #DRAWING_CACHE_QUALITY_AUTO},
+ * {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH}
+ *
+ * @see #setDrawingCacheQuality(int)
+ * @see #setDrawingCacheEnabled(boolean)
+ * @see #isDrawingCacheEnabled()
+ *
+ * @attr ref android.R.styleable#View_drawingCacheQuality
+ */
+ @DrawingCacheQuality
+ public int getDrawingCacheQuality() {
+ return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
+ }
+
+ /**
+ * Set the drawing cache quality of this view. This value is used only when the
+ * drawing cache is enabled
+ *
+ * @param quality One of {@link #DRAWING_CACHE_QUALITY_AUTO},
+ * {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH}
+ *
+ * @see #getDrawingCacheQuality()
+ * @see #setDrawingCacheEnabled(boolean)
+ * @see #isDrawingCacheEnabled()
+ *
+ * @attr ref android.R.styleable#View_drawingCacheQuality
+ */
+ public void setDrawingCacheQuality(@DrawingCacheQuality int quality) {
+ setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
+ }
+
+ /**
+ * Returns whether the screen should remain on, corresponding to the current
+ * value of {@link #KEEP_SCREEN_ON}.
+ *
+ * @return Returns true if {@link #KEEP_SCREEN_ON} is set.
+ *
+ * @see #setKeepScreenOn(boolean)
+ *
+ * @attr ref android.R.styleable#View_keepScreenOn
+ */
+ public boolean getKeepScreenOn() {
+ return (mViewFlags & KEEP_SCREEN_ON) != 0;
+ }
+
+ /**
+ * Controls whether the screen should remain on, modifying the
+ * value of {@link #KEEP_SCREEN_ON}.
+ *
+ * @param keepScreenOn Supply true to set {@link #KEEP_SCREEN_ON}.
+ *
+ * @see #getKeepScreenOn()
+ *
+ * @attr ref android.R.styleable#View_keepScreenOn
+ */
+ public void setKeepScreenOn(boolean keepScreenOn) {
+ setFlags(keepScreenOn ? KEEP_SCREEN_ON : 0, KEEP_SCREEN_ON);
+ }
+
+ /**
+ * Gets the id of the view to use when the next focus is {@link #FOCUS_LEFT}.
+ * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusLeft
+ */
+ public int getNextFocusLeftId() {
+ return mNextFocusLeftId;
+ }
+
+ /**
+ * Sets the id of the view to use when the next focus is {@link #FOCUS_LEFT}.
+ * @param nextFocusLeftId The next focus ID, or {@link #NO_ID} if the framework should
+ * decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusLeft
+ */
+ public void setNextFocusLeftId(int nextFocusLeftId) {
+ mNextFocusLeftId = nextFocusLeftId;
+ }
+
+ /**
+ * Gets the id of the view to use when the next focus is {@link #FOCUS_RIGHT}.
+ * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusRight
+ */
+ public int getNextFocusRightId() {
+ return mNextFocusRightId;
+ }
+
+ /**
+ * Sets the id of the view to use when the next focus is {@link #FOCUS_RIGHT}.
+ * @param nextFocusRightId The next focus ID, or {@link #NO_ID} if the framework should
+ * decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusRight
+ */
+ public void setNextFocusRightId(int nextFocusRightId) {
+ mNextFocusRightId = nextFocusRightId;
+ }
+
+ /**
+ * Gets the id of the view to use when the next focus is {@link #FOCUS_UP}.
+ * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusUp
+ */
+ public int getNextFocusUpId() {
+ return mNextFocusUpId;
+ }
+
+ /**
+ * Sets the id of the view to use when the next focus is {@link #FOCUS_UP}.
+ * @param nextFocusUpId The next focus ID, or {@link #NO_ID} if the framework should
+ * decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusUp
+ */
+ public void setNextFocusUpId(int nextFocusUpId) {
+ mNextFocusUpId = nextFocusUpId;
+ }
+
+ /**
+ * Gets the id of the view to use when the next focus is {@link #FOCUS_DOWN}.
+ * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusDown
+ */
+ public int getNextFocusDownId() {
+ return mNextFocusDownId;
+ }
+
+ /**
+ * Sets the id of the view to use when the next focus is {@link #FOCUS_DOWN}.
+ * @param nextFocusDownId The next focus ID, or {@link #NO_ID} if the framework should
+ * decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusDown
+ */
+ public void setNextFocusDownId(int nextFocusDownId) {
+ mNextFocusDownId = nextFocusDownId;
+ }
+
+ /**
+ * Gets the id of the view to use when the next focus is {@link #FOCUS_FORWARD}.
+ * @return The next focus ID, or {@link #NO_ID} if the framework should decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusForward
+ */
+ public int getNextFocusForwardId() {
+ return mNextFocusForwardId;
+ }
+
+ /**
+ * Sets the id of the view to use when the next focus is {@link #FOCUS_FORWARD}.
+ * @param nextFocusForwardId The next focus ID, or {@link #NO_ID} if the framework should
+ * decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextFocusForward
+ */
+ public void setNextFocusForwardId(int nextFocusForwardId) {
+ mNextFocusForwardId = nextFocusForwardId;
+ }
+
+ /**
+ * Gets the id of the root of the next keyboard navigation cluster.
+ * @return The next keyboard navigation cluster ID, or {@link #NO_ID} if the framework should
+ * decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextClusterForward
+ */
+ public int getNextClusterForwardId() {
+ return mNextClusterForwardId;
+ }
+
+ /**
+ * Sets the id of the view to use as the root of the next keyboard navigation cluster.
+ * @param nextClusterForwardId The next cluster ID, or {@link #NO_ID} if the framework should
+ * decide automatically.
+ *
+ * @attr ref android.R.styleable#View_nextClusterForward
+ */
+ public void setNextClusterForwardId(int nextClusterForwardId) {
+ mNextClusterForwardId = nextClusterForwardId;
+ }
+
+ /**
+ * Returns the visibility of this view and all of its ancestors
+ *
+ * @return True if this view and all of its ancestors are {@link #VISIBLE}
+ */
+ public boolean isShown() {
+ View current = this;
+ //noinspection ConstantConditions
+ do {
+ if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+ return false;
+ }
+ ViewParent parent = current.mParent;
+ if (parent == null) {
+ return false; // We are not attached to the view root
+ }
+ if (!(parent instanceof View)) {
+ return true;
+ }
+ current = (View) parent;
+ } while (current != null);
+
+ return false;
+ }
+
+ /**
+ * Called by the view hierarchy when the content insets for a window have
+ * changed, to allow it to adjust its content to fit within those windows.
+ * The content insets tell you the space that the status bar, input method,
+ * and other system windows infringe on the application's window.
+ *
+ * <p>You do not normally need to deal with this function, since the default
+ * window decoration given to applications takes care of applying it to the
+ * content of the window. If you use {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}
+ * or {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION} this will not be the case,
+ * and your content can be placed under those system elements. You can then
+ * use this method within your view hierarchy if you have parts of your UI
+ * which you would like to ensure are not being covered.
+ *
+ * <p>The default implementation of this method simply applies the content
+ * insets to the view's padding, consuming that content (modifying the
+ * insets to be 0), and returning true. This behavior is off by default, but can
+ * be enabled through {@link #setFitsSystemWindows(boolean)}.
+ *
+ * <p>This function's traversal down the hierarchy is depth-first. The same content
+ * insets object is propagated down the hierarchy, so any changes made to it will
+ * be seen by all following views (including potentially ones above in
+ * the hierarchy since this is a depth-first traversal). The first view
+ * that returns true will abort the entire traversal.
+ *
+ * <p>The default implementation works well for a situation where it is
+ * used with a container that covers the entire window, allowing it to
+ * apply the appropriate insets to its content on all edges. If you need
+ * a more complicated layout (such as two different views fitting system
+ * windows, one on the top of the window, and one on the bottom),
+ * you can override the method and handle the insets however you would like.
+ * Note that the insets provided by the framework are always relative to the
+ * far edges of the window, not accounting for the location of the called view
+ * within that window. (In fact when this method is called you do not yet know
+ * where the layout will place the view, as it is done before layout happens.)
+ *
+ * <p>Note: unlike many View methods, there is no dispatch phase to this
+ * call. If you are overriding it in a ViewGroup and want to allow the
+ * call to continue to your children, you must be sure to call the super
+ * implementation.
+ *
+ * <p>Here is a sample layout that makes use of fitting system windows
+ * to have controls for a video view placed inside of the window decorations
+ * that it hides and shows. This can be used with code like the second
+ * sample (video player) shown in {@link #setSystemUiVisibility(int)}.
+ *
+ * {@sample development/samples/ApiDemos/res/layout/video_player.xml complete}
+ *
+ * @param insets Current content insets of the window. Prior to
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} you must not modify
+ * the insets or else you and Android will be unhappy.
+ *
+ * @return {@code true} if this view applied the insets and it should not
+ * continue propagating further down the hierarchy, {@code false} otherwise.
+ * @see #getFitsSystemWindows()
+ * @see #setFitsSystemWindows(boolean)
+ * @see #setSystemUiVisibility(int)
+ *
+ * @deprecated As of API 20 use {@link #dispatchApplyWindowInsets(WindowInsets)} to apply
+ * insets to views. Views should override {@link #onApplyWindowInsets(WindowInsets)} or use
+ * {@link #setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener)}
+ * to implement handling their own insets.
+ */
+ @Deprecated
+ protected boolean fitSystemWindows(Rect insets) {
+ if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) {
+ if (insets == null) {
+ // Null insets by definition have already been consumed.
+ // This call cannot apply insets since there are none to apply,
+ // so return false.
+ return false;
+ }
+ // If we're not in the process of dispatching the newer apply insets call,
+ // that means we're not in the compatibility path. Dispatch into the newer
+ // apply insets path and take things from there.
+ try {
+ mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;
+ return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed();
+ } finally {
+ mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;
+ }
+ } else {
+ // We're being called from the newer apply insets path.
+ // Perform the standard fallback behavior.
+ return fitSystemWindowsInt(insets);
+ }
+ }
+
+ private boolean fitSystemWindowsInt(Rect insets) {
+ if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
+ mUserPaddingStart = UNDEFINED_PADDING;
+ mUserPaddingEnd = UNDEFINED_PADDING;
+ Rect localInsets = sThreadLocal.get();
+ if (localInsets == null) {
+ localInsets = new Rect();
+ sThreadLocal.set(localInsets);
+ }
+ boolean res = computeFitSystemWindows(insets, localInsets);
+ mUserPaddingLeftInitial = localInsets.left;
+ mUserPaddingRightInitial = localInsets.right;
+ internalSetPadding(localInsets.left, localInsets.top,
+ localInsets.right, localInsets.bottom);
+ return res;
+ }
+ return false;
+ }
+
+ /**
+ * Called when the view should apply {@link WindowInsets} according to its internal policy.
+ *
+ * <p>This method should be overridden by views that wish to apply a policy different from or
+ * in addition to the default behavior. Clients that wish to force a view subtree
+ * to apply insets should call {@link #dispatchApplyWindowInsets(WindowInsets)}.</p>
+ *
+ * <p>Clients may supply an {@link OnApplyWindowInsetsListener} to a view. If one is set
+ * it will be called during dispatch instead of this method. The listener may optionally
+ * call this method from its own implementation if it wishes to apply the view's default
+ * insets policy in addition to its own.</p>
+ *
+ * <p>Implementations of this method should either return the insets parameter unchanged
+ * or a new {@link WindowInsets} cloned from the supplied insets with any insets consumed
+ * that this view applied itself. This allows new inset types added in future platform
+ * versions to pass through existing implementations unchanged without being erroneously
+ * consumed.</p>
+ *
+ * <p>By default if a view's {@link #setFitsSystemWindows(boolean) fitsSystemWindows}
+ * property is set then the view will consume the system window insets and apply them
+ * as padding for the view.</p>
+ *
+ * @param insets Insets to apply
+ * @return The supplied insets with any applied insets consumed
+ */
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
+ // We weren't called from within a direct call to fitSystemWindows,
+ // call into it as a fallback in case we're in a class that overrides it
+ // and has logic to perform.
+ if (fitSystemWindows(insets.getSystemWindowInsets())) {
+ return insets.consumeSystemWindowInsets();
+ }
+ } else {
+ // We were called from within a direct call to fitSystemWindows.
+ if (fitSystemWindowsInt(insets.getSystemWindowInsets())) {
+ return insets.consumeSystemWindowInsets();
+ }
+ }
+ return insets;
+ }
+
+ /**
+ * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
+ * window insets to this view. The listener's
+ * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
+ * method will be called instead of the view's
+ * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
+ *
+ * @param listener Listener to set
+ *
+ * @see #onApplyWindowInsets(WindowInsets)
+ */
+ public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) {
+ getListenerInfo().mOnApplyWindowInsetsListener = listener;
+ }
+
+ /**
+ * Request to apply the given window insets to this view or another view in its subtree.
+ *
+ * <p>This method should be called by clients wishing to apply insets corresponding to areas
+ * obscured by window decorations or overlays. This can include the status and navigation bars,
+ * action bars, input methods and more. New inset categories may be added in the future.
+ * The method returns the insets provided minus any that were applied by this view or its
+ * children.</p>
+ *
+ * <p>Clients wishing to provide custom behavior should override the
+ * {@link #onApplyWindowInsets(WindowInsets)} method or alternatively provide a
+ * {@link OnApplyWindowInsetsListener} via the
+ * {@link #setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) setOnApplyWindowInsetsListener}
+ * method.</p>
+ *
+ * <p>This method replaces the older {@link #fitSystemWindows(Rect) fitSystemWindows} method.
+ * </p>
+ *
+ * @param insets Insets to apply
+ * @return The provided insets minus the insets that were consumed
+ */
+ public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+ try {
+ mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
+ if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
+ return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
+ } else {
+ return onApplyWindowInsets(insets);
+ }
+ } finally {
+ mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
+ }
+ }
+
+ /**
+ * Compute the view's coordinate within the surface.
+ *
+ * <p>Computes the coordinates of this view in its surface. The argument
+ * must be an array of two integers. After the method returns, the array
+ * contains the x and y location in that order.</p>
+ * @hide
+ * @param location an array of two integers in which to hold the coordinates
+ */
+ public void getLocationInSurface(@Size(2) int[] location) {
+ getLocationInWindow(location);
+ if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) {
+ location[0] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.left;
+ location[1] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.top;
+ }
+ }
+
+ /**
+ * Provide original WindowInsets that are dispatched to the view hierarchy. The insets are
+ * only available if the view is attached.
+ *
+ * @return WindowInsets from the top of the view hierarchy or null if View is detached
+ */
+ public WindowInsets getRootWindowInsets() {
+ if (mAttachInfo != null) {
+ return mAttachInfo.mViewRootImpl.getWindowInsets(false /* forceConstruct */);
+ }
+ return null;
+ }
+
+ /**
+ * @hide Compute the insets that should be consumed by this view and the ones
+ * that should propagate to those under it.
+ */
+ protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
+ if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
+ || mAttachInfo == null
+ || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
+ && !mAttachInfo.mOverscanRequested)) {
+ outLocalInsets.set(inoutInsets);
+ inoutInsets.set(0, 0, 0, 0);
+ return true;
+ } else {
+ // The application wants to take care of fitting system window for
+ // the content... however we still need to take care of any overscan here.
+ final Rect overscan = mAttachInfo.mOverscanInsets;
+ outLocalInsets.set(overscan);
+ inoutInsets.left -= overscan.left;
+ inoutInsets.top -= overscan.top;
+ inoutInsets.right -= overscan.right;
+ inoutInsets.bottom -= overscan.bottom;
+ return false;
+ }
+ }
+
+ /**
+ * Compute insets that should be consumed by this view and the ones that should propagate
+ * to those under it.
+ *
+ * @param in Insets currently being processed by this View, likely received as a parameter
+ * to {@link #onApplyWindowInsets(WindowInsets)}.
+ * @param outLocalInsets A Rect that will receive the insets that should be consumed
+ * by this view
+ * @return Insets that should be passed along to views under this one
+ */
+ public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
+ if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
+ || mAttachInfo == null
+ || (mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
+ outLocalInsets.set(in.getSystemWindowInsets());
+ return in.consumeSystemWindowInsets();
+ } else {
+ outLocalInsets.set(0, 0, 0, 0);
+ return in;
+ }
+ }
+
+ /**
+ * Sets whether or not this view should account for system screen decorations
+ * such as the status bar and inset its content; that is, controlling whether
+ * the default implementation of {@link #fitSystemWindows(Rect)} will be
+ * executed. See that method for more details.
+ *
+ * <p>Note that if you are providing your own implementation of
+ * {@link #fitSystemWindows(Rect)}, then there is no need to set this
+ * flag to true -- your implementation will be overriding the default
+ * implementation that checks this flag.
+ *
+ * @param fitSystemWindows If true, then the default implementation of
+ * {@link #fitSystemWindows(Rect)} will be executed.
+ *
+ * @attr ref android.R.styleable#View_fitsSystemWindows
+ * @see #getFitsSystemWindows()
+ * @see #fitSystemWindows(Rect)
+ * @see #setSystemUiVisibility(int)
+ */
+ public void setFitsSystemWindows(boolean fitSystemWindows) {
+ setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);
+ }
+
+ /**
+ * Check for state of {@link #setFitsSystemWindows(boolean)}. If this method
+ * returns {@code true}, the default implementation of {@link #fitSystemWindows(Rect)}
+ * will be executed.
+ *
+ * @return {@code true} if the default implementation of
+ * {@link #fitSystemWindows(Rect)} will be executed.
+ *
+ * @attr ref android.R.styleable#View_fitsSystemWindows
+ * @see #setFitsSystemWindows(boolean)
+ * @see #fitSystemWindows(Rect)
+ * @see #setSystemUiVisibility(int)
+ */
+ @ViewDebug.ExportedProperty
+ public boolean getFitsSystemWindows() {
+ return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS;
+ }
+
+ /** @hide */
+ public boolean fitsSystemWindows() {
+ return getFitsSystemWindows();
+ }
+
+ /**
+ * Ask that a new dispatch of {@link #fitSystemWindows(Rect)} be performed.
+ * @deprecated Use {@link #requestApplyInsets()} for newer platform versions.
+ */
+ @Deprecated
+ public void requestFitSystemWindows() {
+ if (mParent != null) {
+ mParent.requestFitSystemWindows();
+ }
+ }
+
+ /**
+ * Ask that a new dispatch of {@link #onApplyWindowInsets(WindowInsets)} be performed.
+ */
+ public void requestApplyInsets() {
+ requestFitSystemWindows();
+ }
+
+ /**
+ * For use by PhoneWindow to make its own system window fitting optional.
+ * @hide
+ */
+ public void makeOptionalFitsSystemWindows() {
+ setFlags(OPTIONAL_FITS_SYSTEM_WINDOWS, OPTIONAL_FITS_SYSTEM_WINDOWS);
+ }
+
+ /**
+ * Returns the outsets, which areas of the device that aren't a surface, but we would like to
+ * treat them as such.
+ * @hide
+ */
+ public void getOutsets(Rect outOutsetRect) {
+ if (mAttachInfo != null) {
+ outOutsetRect.set(mAttachInfo.mOutsets);
+ } else {
+ outOutsetRect.setEmpty();
+ }
+ }
+
+ /**
+ * Returns the visibility status for this view.
+ *
+ * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+ * @attr ref android.R.styleable#View_visibility
+ */
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = VISIBLE, to = "VISIBLE"),
+ @ViewDebug.IntToString(from = INVISIBLE, to = "INVISIBLE"),
+ @ViewDebug.IntToString(from = GONE, to = "GONE")
+ })
+ @Visibility
+ public int getVisibility() {
+ return mViewFlags & VISIBILITY_MASK;
+ }
+
+ /**
+ * Set the visibility state of this view.
+ *
+ * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+ * @attr ref android.R.styleable#View_visibility
+ */
+ @RemotableViewMethod
+ public void setVisibility(@Visibility int visibility) {
+ setFlags(visibility, VISIBILITY_MASK);
+ }
+
+ /**
+ * Returns the enabled status for this view. The interpretation of the
+ * enabled state varies by subclass.
+ *
+ * @return True if this view is enabled, false otherwise.
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isEnabled() {
+ return (mViewFlags & ENABLED_MASK) == ENABLED;
+ }
+
+ /**
+ * Set the enabled state of this view. The interpretation of the enabled
+ * state varies by subclass.
+ *
+ * @param enabled True if this view is enabled, false otherwise.
+ */
+ @RemotableViewMethod
+ public void setEnabled(boolean enabled) {
+ if (enabled == isEnabled()) return;
+
+ setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);
+
+ /*
+ * The View most likely has to change its appearance, so refresh
+ * the drawable state.
+ */
+ refreshDrawableState();
+
+ // 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();
+ }
+ }
+
+ /**
+ * Set whether this view can receive the focus.
+ * <p>
+ * Setting this to false will also ensure that this view is not focusable
+ * in touch mode.
+ *
+ * @param focusable If true, this view can receive the focus.
+ *
+ * @see #setFocusableInTouchMode(boolean)
+ * @see #setFocusable(int)
+ * @attr ref android.R.styleable#View_focusable
+ */
+ public void setFocusable(boolean focusable) {
+ setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE);
+ }
+
+ /**
+ * Sets whether this view can receive focus.
+ * <p>
+ * Setting this to {@link #FOCUSABLE_AUTO} tells the framework to determine focusability
+ * automatically based on the view's interactivity. This is the default.
+ * <p>
+ * Setting this to NOT_FOCUSABLE will ensure that this view is also not focusable
+ * in touch mode.
+ *
+ * @param focusable One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE},
+ * or {@link #FOCUSABLE_AUTO}.
+ * @see #setFocusableInTouchMode(boolean)
+ * @attr ref android.R.styleable#View_focusable
+ */
+ public void setFocusable(@Focusable int focusable) {
+ if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
+ setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
+ }
+ setFlags(focusable, FOCUSABLE_MASK);
+ }
+
+ /**
+ * Set whether this view can receive focus while in touch mode.
+ *
+ * Setting this to true will also ensure that this view is focusable.
+ *
+ * @param focusableInTouchMode If true, this view can receive the focus while
+ * in touch mode.
+ *
+ * @see #setFocusable(boolean)
+ * @attr ref android.R.styleable#View_focusableInTouchMode
+ */
+ public void setFocusableInTouchMode(boolean focusableInTouchMode) {
+ // Focusable in touch mode should always be set before the focusable flag
+ // otherwise, setting the focusable flag will trigger a focusableViewAvailable()
+ // which, in touch mode, will not successfully request focus on this view
+ // because the focusable in touch mode flag is not set
+ setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE);
+
+ // Clear FOCUSABLE_AUTO if set.
+ if (focusableInTouchMode) {
+ // Clears FOCUSABLE_AUTO if set.
+ setFlags(FOCUSABLE, FOCUSABLE_MASK);
+ }
+ }
+
+ /**
+ * Sets the hints that help an {@link android.service.autofill.AutofillService} determine how
+ * to autofill the view with the user's data.
+ *
+ * <p>Typically, there is only one way to autofill a view, but there could be more than one.
+ * For example, if the application accepts either an username or email address to identify
+ * an user.
+ *
+ * <p>These hints are not validated by the Android System, but passed "as is" to the service.
+ * Hence, they can have any value, but it's recommended to use the {@code AUTOFILL_HINT_}
+ * constants such as:
+ * {@link #AUTOFILL_HINT_USERNAME}, {@link #AUTOFILL_HINT_PASSWORD},
+ * {@link #AUTOFILL_HINT_EMAIL_ADDRESS},
+ * {@link #AUTOFILL_HINT_NAME},
+ * {@link #AUTOFILL_HINT_PHONE},
+ * {@link #AUTOFILL_HINT_POSTAL_ADDRESS}, {@link #AUTOFILL_HINT_POSTAL_CODE},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_NUMBER}, {@link #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY},
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH} or
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}.
+ *
+ * @param autofillHints The autofill hints to set. If the array is emtpy, {@code null} is set.
+ * @attr ref android.R.styleable#View_autofillHints
+ */
+ public void setAutofillHints(@Nullable String... autofillHints) {
+ if (autofillHints == null || autofillHints.length == 0) {
+ mAutofillHints = null;
+ } else {
+ mAutofillHints = autofillHints;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public void setAutofilled(boolean isAutofilled) {
+ boolean wasChanged = isAutofilled != isAutofilled();
+
+ if (wasChanged) {
+ if (isAutofilled) {
+ mPrivateFlags3 |= PFLAG3_IS_AUTOFILLED;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_IS_AUTOFILLED;
+ }
+
+ invalidate();
+ }
+ }
+
+ /**
+ * Set whether this view should have sound effects enabled for events such as
+ * clicking and touching.
+ *
+ * <p>You may wish to disable sound effects for a view if you already play sounds,
+ * for instance, a dial key that plays dtmf tones.
+ *
+ * @param soundEffectsEnabled whether sound effects are enabled for this view.
+ * @see #isSoundEffectsEnabled()
+ * @see #playSoundEffect(int)
+ * @attr ref android.R.styleable#View_soundEffectsEnabled
+ */
+ public void setSoundEffectsEnabled(boolean soundEffectsEnabled) {
+ setFlags(soundEffectsEnabled ? SOUND_EFFECTS_ENABLED: 0, SOUND_EFFECTS_ENABLED);
+ }
+
+ /**
+ * @return whether this view should have sound effects enabled for events such as
+ * clicking and touching.
+ *
+ * @see #setSoundEffectsEnabled(boolean)
+ * @see #playSoundEffect(int)
+ * @attr ref android.R.styleable#View_soundEffectsEnabled
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isSoundEffectsEnabled() {
+ return SOUND_EFFECTS_ENABLED == (mViewFlags & SOUND_EFFECTS_ENABLED);
+ }
+
+ /**
+ * Set whether this view should have haptic feedback for events such as
+ * long presses.
+ *
+ * <p>You may wish to disable haptic feedback if your view already controls
+ * its own haptic feedback.
+ *
+ * @param hapticFeedbackEnabled whether haptic feedback enabled for this view.
+ * @see #isHapticFeedbackEnabled()
+ * @see #performHapticFeedback(int)
+ * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+ */
+ public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) {
+ setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED);
+ }
+
+ /**
+ * @return whether this view should have haptic feedback enabled for events
+ * long presses.
+ *
+ * @see #setHapticFeedbackEnabled(boolean)
+ * @see #performHapticFeedback(int)
+ * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isHapticFeedbackEnabled() {
+ return HAPTIC_FEEDBACK_ENABLED == (mViewFlags & HAPTIC_FEEDBACK_ENABLED);
+ }
+
+ /**
+ * Returns the layout direction for this view.
+ *
+ * @return One of {@link #LAYOUT_DIRECTION_LTR},
+ * {@link #LAYOUT_DIRECTION_RTL},
+ * {@link #LAYOUT_DIRECTION_INHERIT} or
+ * {@link #LAYOUT_DIRECTION_LOCALE}.
+ *
+ * @attr ref android.R.styleable#View_layoutDirection
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "LTR"),
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RTL"),
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"),
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE")
+ })
+ @LayoutDir
+ public int getRawLayoutDirection() {
+ return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
+ }
+
+ /**
+ * Set the layout direction for this view. This will propagate a reset of layout direction
+ * resolution to the view's children and resolve layout direction for this view.
+ *
+ * @param layoutDirection the layout direction to set. Should be one of:
+ *
+ * {@link #LAYOUT_DIRECTION_LTR},
+ * {@link #LAYOUT_DIRECTION_RTL},
+ * {@link #LAYOUT_DIRECTION_INHERIT},
+ * {@link #LAYOUT_DIRECTION_LOCALE}.
+ *
+ * Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+ * will return the default {@link #LAYOUT_DIRECTION_LTR}.
+ *
+ * @attr ref android.R.styleable#View_layoutDirection
+ */
+ @RemotableViewMethod
+ public void setLayoutDirection(@LayoutDir int layoutDirection) {
+ if (getRawLayoutDirection() != layoutDirection) {
+ // Reset the current layout direction and the resolved one
+ mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_MASK;
+ resetRtlProperties();
+ // Set the new layout direction (filtered)
+ mPrivateFlags2 |=
+ ((layoutDirection << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) & PFLAG2_LAYOUT_DIRECTION_MASK);
+ // We need to resolve all RTL properties as they all depend on layout direction
+ resolveRtlPropertiesIfNeeded();
+ requestLayout();
+ invalidate(true);
+ }
+ }
+
+ /**
+ * Returns the resolved layout direction for this view.
+ *
+ * @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
+ * {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+ *
+ * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
+ * is lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}.
+ *
+ * @attr ref android.R.styleable#View_layoutDirection
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"),
+ @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL")
+ })
+ @ResolvedLayoutDir
+ public int getLayoutDirection() {
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
+ }
+ return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
+ PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
+ }
+
+ /**
+ * Indicates whether or not this view's layout is right-to-left. This is resolved from
+ * layout attribute and/or the inherited value from the parent
+ *
+ * @return true if the layout is right-to-left.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ }
+
+ /**
+ * Indicates whether the view is currently tracking transient state that the
+ * app should not need to concern itself with saving and restoring, but that
+ * the framework should take special note to preserve when possible.
+ *
+ * <p>A view with transient state cannot be trivially rebound from an external
+ * data source, such as an adapter binding item views in a list. This may be
+ * because the view is performing an animation, tracking user selection
+ * of content, or similar.</p>
+ *
+ * @return true if the view has transient state
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public boolean hasTransientState() {
+ return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE;
+ }
+
+ /**
+ * Set whether this view is currently tracking transient state that the
+ * framework should attempt to preserve when possible. This flag is reference counted,
+ * so every call to setHasTransientState(true) should be paired with a later call
+ * to setHasTransientState(false).
+ *
+ * <p>A view with transient state cannot be trivially rebound from an external
+ * data source, such as an adapter binding item views in a list. This may be
+ * because the view is performing an animation, tracking user selection
+ * of content, or similar.</p>
+ *
+ * @param hasTransientState true if this view has transient state
+ */
+ public void setHasTransientState(boolean hasTransientState) {
+ final boolean oldHasTransientState = hasTransientState();
+ mTransientStateCount = hasTransientState ? mTransientStateCount + 1 :
+ mTransientStateCount - 1;
+ if (mTransientStateCount < 0) {
+ mTransientStateCount = 0;
+ Log.e(VIEW_LOG_TAG, "hasTransientState decremented below 0: " +
+ "unmatched pair of setHasTransientState calls");
+ } else if ((hasTransientState && mTransientStateCount == 1) ||
+ (!hasTransientState && mTransientStateCount == 0)) {
+ // update flag if we've just incremented up from 0 or decremented down to 0
+ mPrivateFlags2 = (mPrivateFlags2 & ~PFLAG2_HAS_TRANSIENT_STATE) |
+ (hasTransientState ? PFLAG2_HAS_TRANSIENT_STATE : 0);
+ final boolean newHasTransientState = hasTransientState();
+ if (mParent != null && newHasTransientState != oldHasTransientState) {
+ try {
+ mParent.childHasTransientStateChanged(this, newHasTransientState);
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if this view is currently attached to a window.
+ */
+ public boolean isAttachedToWindow() {
+ return mAttachInfo != null;
+ }
+
+ /**
+ * Returns true if this view has been through at least one layout since it
+ * was last attached to or detached from a window.
+ */
+ public boolean isLaidOut() {
+ return (mPrivateFlags3 & PFLAG3_IS_LAID_OUT) == PFLAG3_IS_LAID_OUT;
+ }
+
+ /**
+ * If this view doesn't do any drawing on its own, set this flag to
+ * allow further optimizations. By default, this flag is not set on
+ * View, but could be set on some View subclasses such as ViewGroup.
+ *
+ * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
+ * you should clear this flag.
+ *
+ * @param willNotDraw whether or not this View draw on its own
+ */
+ public void setWillNotDraw(boolean willNotDraw) {
+ setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
+ }
+
+ /**
+ * Returns whether or not this View draws on its own.
+ *
+ * @return true if this view has nothing to draw, false otherwise
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean willNotDraw() {
+ return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
+ }
+
+ /**
+ * When a View's drawing cache is enabled, drawing is redirected to an
+ * offscreen bitmap. Some views, like an ImageView, must be able to
+ * bypass this mechanism if they already draw a single bitmap, to avoid
+ * unnecessary usage of the memory.
+ *
+ * @param willNotCacheDrawing true if this view does not cache its
+ * drawing, false otherwise
+ */
+ public void setWillNotCacheDrawing(boolean willNotCacheDrawing) {
+ setFlags(willNotCacheDrawing ? WILL_NOT_CACHE_DRAWING : 0, WILL_NOT_CACHE_DRAWING);
+ }
+
+ /**
+ * Returns whether or not this View can cache its drawing or not.
+ *
+ * @return true if this view does not cache its drawing, false otherwise
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean willNotCacheDrawing() {
+ return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING;
+ }
+
+ /**
+ * Indicates whether this view reacts to click events or not.
+ *
+ * @return true if the view is clickable, false otherwise
+ *
+ * @see #setClickable(boolean)
+ * @attr ref android.R.styleable#View_clickable
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isClickable() {
+ return (mViewFlags & CLICKABLE) == CLICKABLE;
+ }
+
+ /**
+ * Enables or disables click events for this view. When a view
+ * is clickable it will change its state to "pressed" on every click.
+ * Subclasses should set the view clickable to visually react to
+ * user's clicks.
+ *
+ * @param clickable true to make the view clickable, false otherwise
+ *
+ * @see #isClickable()
+ * @attr ref android.R.styleable#View_clickable
+ */
+ public void setClickable(boolean clickable) {
+ setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
+ }
+
+ /**
+ * Indicates whether this view reacts to long click events or not.
+ *
+ * @return true if the view is long clickable, false otherwise
+ *
+ * @see #setLongClickable(boolean)
+ * @attr ref android.R.styleable#View_longClickable
+ */
+ public boolean isLongClickable() {
+ return (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+ }
+
+ /**
+ * Enables or disables long click events for this view. When a view is long
+ * clickable it reacts to the user holding down the button for a longer
+ * duration than a tap. This event can either launch the listener or a
+ * context menu.
+ *
+ * @param longClickable true to make the view long clickable, false otherwise
+ * @see #isLongClickable()
+ * @attr ref android.R.styleable#View_longClickable
+ */
+ public void setLongClickable(boolean longClickable) {
+ setFlags(longClickable ? LONG_CLICKABLE : 0, LONG_CLICKABLE);
+ }
+
+ /**
+ * Indicates whether this view reacts to context clicks or not.
+ *
+ * @return true if the view is context clickable, false otherwise
+ * @see #setContextClickable(boolean)
+ * @attr ref android.R.styleable#View_contextClickable
+ */
+ public boolean isContextClickable() {
+ return (mViewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+ }
+
+ /**
+ * Enables or disables context clicking for this view. This event can launch the listener.
+ *
+ * @param contextClickable true to make the view react to a context click, false otherwise
+ * @see #isContextClickable()
+ * @attr ref android.R.styleable#View_contextClickable
+ */
+ public void setContextClickable(boolean contextClickable) {
+ setFlags(contextClickable ? CONTEXT_CLICKABLE : 0, CONTEXT_CLICKABLE);
+ }
+
+ /**
+ * Sets the pressed state for this view and provides a touch coordinate for
+ * animation hinting.
+ *
+ * @param pressed Pass true to set the View's internal state to "pressed",
+ * or false to reverts the View's internal state from a
+ * previously set "pressed" state.
+ * @param x The x coordinate of the touch that caused the press
+ * @param y The y coordinate of the touch that caused the press
+ */
+ private void setPressed(boolean pressed, float x, float y) {
+ if (pressed) {
+ drawableHotspotChanged(x, y);
+ }
+
+ setPressed(pressed);
+ }
+
+ /**
+ * Sets the pressed state for this view.
+ *
+ * @see #isClickable()
+ * @see #setClickable(boolean)
+ *
+ * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
+ * the View's internal state from a previously set "pressed" state.
+ */
+ public void setPressed(boolean pressed) {
+ final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
+
+ if (pressed) {
+ mPrivateFlags |= PFLAG_PRESSED;
+ } else {
+ mPrivateFlags &= ~PFLAG_PRESSED;
+ }
+
+ if (needsRefresh) {
+ refreshDrawableState();
+ }
+ dispatchSetPressed(pressed);
+ }
+
+ /**
+ * Dispatch setPressed to all of this View's children.
+ *
+ * @see #setPressed(boolean)
+ *
+ * @param pressed The new pressed state
+ */
+ protected void dispatchSetPressed(boolean pressed) {
+ }
+
+ /**
+ * Indicates whether the view is currently in pressed state. Unless
+ * {@link #setPressed(boolean)} is explicitly called, only clickable views can enter
+ * the pressed state.
+ *
+ * @see #setPressed(boolean)
+ * @see #isClickable()
+ * @see #setClickable(boolean)
+ *
+ * @return true if the view is currently pressed, false otherwise
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isPressed() {
+ return (mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED;
+ }
+
+ /**
+ * @hide
+ * Indicates whether this view will participate in data collection through
+ * {@link ViewStructure}. If true, it will not provide any data
+ * for itself or its children. If false, the normal data collection will be allowed.
+ *
+ * @return Returns false if assist data collection is not blocked, else true.
+ *
+ * @see #setAssistBlocked(boolean)
+ * @attr ref android.R.styleable#View_assistBlocked
+ */
+ public boolean isAssistBlocked() {
+ return (mPrivateFlags3 & PFLAG3_ASSIST_BLOCKED) != 0;
+ }
+
+ /**
+ * @hide
+ * Controls whether assist data collection from this view and its children is enabled
+ * (that is, whether {@link #onProvideStructure} and
+ * {@link #onProvideVirtualStructure} will be called). The default value is false,
+ * allowing normal assist collection. Setting this to false will disable assist collection.
+ *
+ * @param enabled Set to true to <em>disable</em> assist data collection, or false
+ * (the default) to allow it.
+ *
+ * @see #isAssistBlocked()
+ * @see #onProvideStructure
+ * @see #onProvideVirtualStructure
+ * @attr ref android.R.styleable#View_assistBlocked
+ */
+ public void setAssistBlocked(boolean enabled) {
+ if (enabled) {
+ mPrivateFlags3 |= PFLAG3_ASSIST_BLOCKED;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_ASSIST_BLOCKED;
+ }
+ }
+
+ /**
+ * Indicates whether this view will save its state (that is,
+ * whether its {@link #onSaveInstanceState} method will be called).
+ *
+ * @return Returns true if the view state saving is enabled, else false.
+ *
+ * @see #setSaveEnabled(boolean)
+ * @attr ref android.R.styleable#View_saveEnabled
+ */
+ public boolean isSaveEnabled() {
+ return (mViewFlags & SAVE_DISABLED_MASK) != SAVE_DISABLED;
+ }
+
+ /**
+ * Controls whether the saving of this view's state is
+ * enabled (that is, whether its {@link #onSaveInstanceState} method
+ * will be called). Note that even if freezing is enabled, the
+ * view still must have an id assigned to it (via {@link #setId(int)})
+ * for its state to be saved. This flag can only disable the
+ * saving of this view; any child views may still have their state saved.
+ *
+ * @param enabled Set to false to <em>disable</em> state saving, or true
+ * (the default) to allow it.
+ *
+ * @see #isSaveEnabled()
+ * @see #setId(int)
+ * @see #onSaveInstanceState()
+ * @attr ref android.R.styleable#View_saveEnabled
+ */
+ public void setSaveEnabled(boolean enabled) {
+ setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK);
+ }
+
+ /**
+ * Gets whether the framework should discard touches when the view's
+ * window is obscured by another visible window.
+ * Refer to the {@link View} security documentation for more details.
+ *
+ * @return True if touch filtering is enabled.
+ *
+ * @see #setFilterTouchesWhenObscured(boolean)
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ */
+ @ViewDebug.ExportedProperty
+ public boolean getFilterTouchesWhenObscured() {
+ return (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0;
+ }
+
+ /**
+ * Sets whether the framework should discard touches when the view's
+ * window is obscured by another visible window.
+ * Refer to the {@link View} security documentation for more details.
+ *
+ * @param enabled True if touch filtering should be enabled.
+ *
+ * @see #getFilterTouchesWhenObscured
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ */
+ public void setFilterTouchesWhenObscured(boolean enabled) {
+ setFlags(enabled ? FILTER_TOUCHES_WHEN_OBSCURED : 0,
+ FILTER_TOUCHES_WHEN_OBSCURED);
+ }
+
+ /**
+ * Indicates whether the entire hierarchy under this view will save its
+ * state when a state saving traversal occurs from its parent. The default
+ * is true; if false, these views will not be saved unless
+ * {@link #saveHierarchyState(SparseArray)} is called directly on this view.
+ *
+ * @return Returns true if the view state saving from parent is enabled, else false.
+ *
+ * @see #setSaveFromParentEnabled(boolean)
+ */
+ public boolean isSaveFromParentEnabled() {
+ return (mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED;
+ }
+
+ /**
+ * Controls whether the entire hierarchy under this view will save its
+ * state when a state saving traversal occurs from its parent. The default
+ * is true; if false, these views will not be saved unless
+ * {@link #saveHierarchyState(SparseArray)} is called directly on this view.
+ *
+ * @param enabled Set to false to <em>disable</em> state saving, or true
+ * (the default) to allow it.
+ *
+ * @see #isSaveFromParentEnabled()
+ * @see #setId(int)
+ * @see #onSaveInstanceState()
+ */
+ public void setSaveFromParentEnabled(boolean enabled) {
+ setFlags(enabled ? 0 : PARENT_SAVE_DISABLED, PARENT_SAVE_DISABLED_MASK);
+ }
+
+
+ /**
+ * Returns whether this View is currently able to take focus.
+ *
+ * @return True if this view can take focus, or false otherwise.
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public final boolean isFocusable() {
+ return FOCUSABLE == (mViewFlags & FOCUSABLE);
+ }
+
+ /**
+ * Returns the focusable setting for this view.
+ *
+ * @return One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE}, or {@link #FOCUSABLE_AUTO}.
+ * @attr ref android.R.styleable#View_focusable
+ */
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = NOT_FOCUSABLE, to = "NOT_FOCUSABLE"),
+ @ViewDebug.IntToString(from = FOCUSABLE, to = "FOCUSABLE"),
+ @ViewDebug.IntToString(from = FOCUSABLE_AUTO, to = "FOCUSABLE_AUTO")
+ }, category = "focus")
+ @Focusable
+ public int getFocusable() {
+ return (mViewFlags & FOCUSABLE_AUTO) > 0 ? FOCUSABLE_AUTO : mViewFlags & FOCUSABLE;
+ }
+
+ /**
+ * When a view is focusable, it may not want to take focus when in touch mode.
+ * For example, a button would like focus when the user is navigating via a D-pad
+ * so that the user can click on it, but once the user starts touching the screen,
+ * the button shouldn't take focus
+ * @return Whether the view is focusable in touch mode.
+ * @attr ref android.R.styleable#View_focusableInTouchMode
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public final boolean isFocusableInTouchMode() {
+ return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
+ }
+
+ /**
+ * Find the nearest view in the specified direction that can take focus.
+ * This does not actually give focus to that view.
+ *
+ * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+ *
+ * @return The nearest focusable in the specified direction, or null if none
+ * can be found.
+ */
+ public View focusSearch(@FocusRealDirection int direction) {
+ if (mParent != null) {
+ return mParent.focusSearch(this, direction);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns whether this View is a root of a keyboard navigation cluster.
+ *
+ * @return True if this view is a root of a cluster, or false otherwise.
+ * @attr ref android.R.styleable#View_keyboardNavigationCluster
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public final boolean isKeyboardNavigationCluster() {
+ return (mPrivateFlags3 & PFLAG3_CLUSTER) != 0;
+ }
+
+ /**
+ * Searches up the view hierarchy to find the top-most cluster. All deeper/nested clusters
+ * will be ignored.
+ *
+ * @return the keyboard navigation cluster that this view is in (can be this view)
+ * or {@code null} if not in one
+ */
+ View findKeyboardNavigationCluster() {
+ if (mParent instanceof View) {
+ View cluster = ((View) mParent).findKeyboardNavigationCluster();
+ if (cluster != null) {
+ return cluster;
+ } else if (isKeyboardNavigationCluster()) {
+ return this;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Set whether this view is a root of a keyboard navigation cluster.
+ *
+ * @param isCluster If true, this view is a root of a cluster.
+ *
+ * @attr ref android.R.styleable#View_keyboardNavigationCluster
+ */
+ public void setKeyboardNavigationCluster(boolean isCluster) {
+ if (isCluster) {
+ mPrivateFlags3 |= PFLAG3_CLUSTER;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_CLUSTER;
+ }
+ }
+
+ /**
+ * Sets this View as the one which receives focus the next time cluster navigation jumps
+ * to the cluster containing this View. This does NOT change focus even if the cluster
+ * containing this view is current.
+ *
+ * @hide
+ */
+ @TestApi
+ public final void setFocusedInCluster() {
+ setFocusedInCluster(findKeyboardNavigationCluster());
+ }
+
+ private void setFocusedInCluster(View cluster) {
+ if (this instanceof ViewGroup) {
+ ((ViewGroup) this).mFocusedInCluster = null;
+ }
+ if (cluster == this) {
+ return;
+ }
+ ViewParent parent = mParent;
+ View child = this;
+ while (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).mFocusedInCluster = child;
+ if (parent == cluster) {
+ break;
+ }
+ child = (View) parent;
+ parent = parent.getParent();
+ }
+ }
+
+ private void updateFocusedInCluster(View oldFocus, @FocusDirection int direction) {
+ if (oldFocus != null) {
+ View oldCluster = oldFocus.findKeyboardNavigationCluster();
+ View cluster = findKeyboardNavigationCluster();
+ if (oldCluster != cluster) {
+ // Going from one cluster to another, so save last-focused.
+ // This covers cluster jumps because they are always FOCUS_DOWN
+ oldFocus.setFocusedInCluster(oldCluster);
+ if (!(oldFocus.mParent instanceof ViewGroup)) {
+ return;
+ }
+ if (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) {
+ // This is a result of ordered navigation so consider navigation through
+ // the previous cluster "complete" and clear its last-focused memory.
+ ((ViewGroup) oldFocus.mParent).clearFocusedInCluster(oldFocus);
+ } else if (oldFocus instanceof ViewGroup
+ && ((ViewGroup) oldFocus).getDescendantFocusability()
+ == ViewGroup.FOCUS_AFTER_DESCENDANTS
+ && ViewRootImpl.isViewDescendantOf(this, oldFocus)) {
+ // This means oldFocus is not focusable since it obviously has a focusable
+ // child (this). Don't restore focus to it in the future.
+ ((ViewGroup) oldFocus.mParent).clearFocusedInCluster(oldFocus);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether this View should receive focus when the focus is restored for the view
+ * hierarchy containing this view.
+ * <p>
+ * Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
+ * window or serves as a target of cluster navigation.
+ *
+ * @see #restoreDefaultFocus()
+ *
+ * @return {@code true} if this view is the default-focus view, {@code false} otherwise
+ * @attr ref android.R.styleable#View_focusedByDefault
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public final boolean isFocusedByDefault() {
+ return (mPrivateFlags3 & PFLAG3_FOCUSED_BY_DEFAULT) != 0;
+ }
+
+ /**
+ * Sets whether this View should receive focus when the focus is restored for the view
+ * hierarchy containing this view.
+ * <p>
+ * Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
+ * window or serves as a target of cluster navigation.
+ *
+ * @param isFocusedByDefault {@code true} to set this view as the default-focus view,
+ * {@code false} otherwise.
+ *
+ * @see #restoreDefaultFocus()
+ *
+ * @attr ref android.R.styleable#View_focusedByDefault
+ */
+ public void setFocusedByDefault(boolean isFocusedByDefault) {
+ if (isFocusedByDefault == ((mPrivateFlags3 & PFLAG3_FOCUSED_BY_DEFAULT) != 0)) {
+ return;
+ }
+
+ if (isFocusedByDefault) {
+ mPrivateFlags3 |= PFLAG3_FOCUSED_BY_DEFAULT;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_FOCUSED_BY_DEFAULT;
+ }
+
+ if (mParent instanceof ViewGroup) {
+ if (isFocusedByDefault) {
+ ((ViewGroup) mParent).setDefaultFocus(this);
+ } else {
+ ((ViewGroup) mParent).clearDefaultFocus(this);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the view hierarchy with this view as a root contain a default-focus view.
+ *
+ * @return {@code true} if this view has default focus, {@code false} otherwise
+ */
+ boolean hasDefaultFocus() {
+ return isFocusedByDefault();
+ }
+
+ /**
+ * Find the nearest keyboard navigation cluster in the specified direction.
+ * This does not actually give focus to that cluster.
+ *
+ * @param currentCluster The starting point of the search. Null means the current cluster is not
+ * found yet
+ * @param direction Direction to look
+ *
+ * @return The nearest keyboard navigation cluster in the specified direction, or null if none
+ * can be found
+ */
+ public View keyboardNavigationClusterSearch(View currentCluster,
+ @FocusDirection int direction) {
+ if (isKeyboardNavigationCluster()) {
+ currentCluster = this;
+ }
+ if (isRootNamespace()) {
+ // Root namespace means we should consider ourselves the top of the
+ // tree for group searching; otherwise we could be group searching
+ // into other tabs. see LocalActivityManager and TabHost for more info.
+ return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
+ this, currentCluster, direction);
+ } else if (mParent != null) {
+ return mParent.keyboardNavigationClusterSearch(currentCluster, direction);
+ }
+ return null;
+ }
+
+ /**
+ * This method is the last chance for the focused view and its ancestors to
+ * respond to an arrow key. This is called when the focused view did not
+ * consume the key internally, nor could the view system find a new view in
+ * the requested direction to give focus to.
+ *
+ * @param focused The currently focused view.
+ * @param direction The direction focus wants to move. One of FOCUS_UP,
+ * FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT.
+ * @return True if the this view consumed this unhandled move.
+ */
+ public boolean dispatchUnhandledMove(View focused, @FocusRealDirection int direction) {
+ return false;
+ }
+
+ /**
+ * Sets whether this View should use a default focus highlight when it gets focused but doesn't
+ * have {@link android.R.attr#state_focused} defined in its background.
+ *
+ * @param defaultFocusHighlightEnabled {@code true} to set this view to use a default focus
+ * highlight, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#View_defaultFocusHighlightEnabled
+ */
+ public void setDefaultFocusHighlightEnabled(boolean defaultFocusHighlightEnabled) {
+ mDefaultFocusHighlightEnabled = defaultFocusHighlightEnabled;
+ }
+
+ /**
+
+ /**
+ * Returns whether this View should use a default focus highlight when it gets focused but
+ * doesn't have {@link android.R.attr#state_focused} defined in its background.
+ *
+ * @return True if this View should use a default focus highlight.
+ * @attr ref android.R.styleable#View_defaultFocusHighlightEnabled
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public final boolean getDefaultFocusHighlightEnabled() {
+ return mDefaultFocusHighlightEnabled;
+ }
+
+ /**
+ * If a user manually specified the next view id for a particular direction,
+ * use the root to look up the view.
+ * @param root The root view of the hierarchy containing this view.
+ * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD,
+ * or FOCUS_BACKWARD.
+ * @return The user specified next view, or null if there is none.
+ */
+ View findUserSetNextFocus(View root, @FocusDirection int direction) {
+ switch (direction) {
+ case FOCUS_LEFT:
+ if (mNextFocusLeftId == View.NO_ID) return null;
+ return findViewInsideOutShouldExist(root, mNextFocusLeftId);
+ case FOCUS_RIGHT:
+ if (mNextFocusRightId == View.NO_ID) return null;
+ return findViewInsideOutShouldExist(root, mNextFocusRightId);
+ case FOCUS_UP:
+ if (mNextFocusUpId == View.NO_ID) return null;
+ return findViewInsideOutShouldExist(root, mNextFocusUpId);
+ case FOCUS_DOWN:
+ if (mNextFocusDownId == View.NO_ID) return null;
+ return findViewInsideOutShouldExist(root, mNextFocusDownId);
+ case FOCUS_FORWARD:
+ if (mNextFocusForwardId == View.NO_ID) return null;
+ return findViewInsideOutShouldExist(root, mNextFocusForwardId);
+ case FOCUS_BACKWARD: {
+ if (mID == View.NO_ID) return null;
+ final int id = mID;
+ return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
+ @Override
+ public boolean test(View t) {
+ return t.mNextFocusForwardId == id;
+ }
+ });
+ }
+ }
+ return null;
+ }
+
+ /**
+ * If a user manually specified the next keyboard-navigation cluster for a particular direction,
+ * use the root to look up the view.
+ *
+ * @param root the root view of the hierarchy containing this view
+ * @param direction {@link #FOCUS_FORWARD} or {@link #FOCUS_BACKWARD}
+ * @return the user-specified next cluster, or {@code null} if there is none
+ */
+ View findUserSetNextKeyboardNavigationCluster(View root, @FocusDirection int direction) {
+ switch (direction) {
+ case FOCUS_FORWARD:
+ if (mNextClusterForwardId == View.NO_ID) return null;
+ return findViewInsideOutShouldExist(root, mNextClusterForwardId);
+ case FOCUS_BACKWARD: {
+ if (mID == View.NO_ID) return null;
+ final int id = mID;
+ return root.findViewByPredicateInsideOut(this,
+ (Predicate<View>) t -> t.mNextClusterForwardId == id);
+ }
+ }
+ return null;
+ }
+
+ private View findViewInsideOutShouldExist(View root, int id) {
+ if (mMatchIdPredicate == null) {
+ mMatchIdPredicate = new MatchIdPredicate();
+ }
+ mMatchIdPredicate.mId = id;
+ View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate);
+ if (result == null) {
+ Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id);
+ }
+ return result;
+ }
+
+ /**
+ * Find and return all focusable views that are descendants of this view,
+ * possibly including this view if it is focusable itself.
+ *
+ * @param direction The direction of the focus
+ * @return A list of focusable views
+ */
+ public ArrayList<View> getFocusables(@FocusDirection int direction) {
+ ArrayList<View> result = new ArrayList<View>(24);
+ addFocusables(result, direction);
+ return result;
+ }
+
+ /**
+ * Add any focusable views that are descendants of this view (possibly
+ * including this view if it is focusable itself) to views. If we are in touch mode,
+ * only add views that are also focusable in touch mode.
+ *
+ * @param views Focusable views found so far
+ * @param direction The direction of the focus
+ */
+ public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
+ addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
+ }
+
+ /**
+ * Adds any focusable views that are descendants of this view (possibly
+ * including this view if it is focusable itself) to views. This method
+ * adds all focusable views regardless if we are in touch mode or
+ * only views focusable in touch mode if we are in touch mode or
+ * only views that can take accessibility focus if accessibility is enabled
+ * depending on the focusable mode parameter.
+ *
+ * @param views Focusable views found so far or null if all we are interested is
+ * the number of focusables.
+ * @param direction The direction of the focus.
+ * @param focusableMode The type of focusables to be added.
+ *
+ * @see #FOCUSABLES_ALL
+ * @see #FOCUSABLES_TOUCH_MODE
+ */
+ public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
+ @FocusableMode int focusableMode) {
+ if (views == null) {
+ return;
+ }
+ if (!isFocusable() || !isEnabled()) {
+ return;
+ }
+ if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
+ && !isFocusableInTouchMode()) {
+ return;
+ }
+ views.add(this);
+ }
+
+ /**
+ * Adds any keyboard navigation cluster roots that are descendants of this view (possibly
+ * including this view if it is a cluster root itself) to views.
+ *
+ * @param views Keyboard navigation cluster roots found so far
+ * @param direction Direction to look
+ */
+ public void addKeyboardNavigationClusters(
+ @NonNull Collection<View> views,
+ int direction) {
+ if (!isKeyboardNavigationCluster()) {
+ return;
+ }
+ if (!hasFocusable()) {
+ return;
+ }
+ views.add(this);
+ }
+
+ /**
+ * Finds the Views that contain given text. The containment is case insensitive.
+ * The search is performed by either the text that the View renders or the content
+ * description that describes the view for accessibility purposes and the view does
+ * not render or both. Clients can specify how the search is to be performed via
+ * passing the {@link #FIND_VIEWS_WITH_TEXT} and
+ * {@link #FIND_VIEWS_WITH_CONTENT_DESCRIPTION} flags.
+ *
+ * @param outViews The output list of matching Views.
+ * @param searched The text to match against.
+ *
+ * @see #FIND_VIEWS_WITH_TEXT
+ * @see #FIND_VIEWS_WITH_CONTENT_DESCRIPTION
+ * @see #setContentDescription(CharSequence)
+ */
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence searched,
+ @FindViewFlags int flags) {
+ if (getAccessibilityNodeProvider() != null) {
+ if ((flags & FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS) != 0) {
+ outViews.add(this);
+ }
+ } else if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0
+ && (searched != null && searched.length() > 0)
+ && (mContentDescription != null && mContentDescription.length() > 0)) {
+ String searchedLowerCase = searched.toString().toLowerCase();
+ String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase();
+ if (contentDescriptionLowerCase.contains(searchedLowerCase)) {
+ outViews.add(this);
+ }
+ }
+ }
+
+ /**
+ * Find and return all touchable views that are descendants of this view,
+ * possibly including this view if it is touchable itself.
+ *
+ * @return A list of touchable views
+ */
+ public ArrayList<View> getTouchables() {
+ ArrayList<View> result = new ArrayList<View>();
+ addTouchables(result);
+ return result;
+ }
+
+ /**
+ * Add any touchable views that are descendants of this view (possibly
+ * including this view if it is touchable itself) to views.
+ *
+ * @param views Touchable views found so far
+ */
+ public void addTouchables(ArrayList<View> views) {
+ final int viewFlags = mViewFlags;
+
+ if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE
+ || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE)
+ && (viewFlags & ENABLED_MASK) == ENABLED) {
+ views.add(this);
+ }
+ }
+
+ /**
+ * Returns whether this View is accessibility focused.
+ *
+ * @return True if this View is accessibility focused.
+ */
+ public boolean isAccessibilityFocused() {
+ return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0;
+ }
+
+ /**
+ * Call this to try to give accessibility focus to this view.
+ *
+ * A view will not actually take focus if {@link AccessibilityManager#isEnabled()}
+ * returns false or the view is no visible or the view already has accessibility
+ * focus.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * @return Whether this view actually took accessibility focus.
+ *
+ * @hide
+ */
+ public boolean requestAccessibilityFocus() {
+ AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+ if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+ return false;
+ }
+ if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+ return false;
+ }
+ if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) == 0) {
+ mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_FOCUSED;
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocus(this, null);
+ }
+ invalidate();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Call this to try to clear accessibility focus of this view.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * @hide
+ */
+ public void clearAccessibilityFocus() {
+ clearAccessibilityFocusNoCallbacks(0);
+
+ // Clear the global reference of accessibility focus if this view or
+ // any of its descendants had accessibility focus. This will NOT send
+ // an event or update internal state if focus is cleared from a
+ // descendant view, which may leave views in inconsistent states.
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
+ if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) {
+ viewRootImpl.setAccessibilityFocus(null, null);
+ }
+ }
+ }
+
+ private void sendAccessibilityHoverEvent(int eventType) {
+ // Since we are not delivering to a client accessibility events from not
+ // important views (unless the clinet request that) we need to fire the
+ // event from the deepest view exposed to the client. As a consequence if
+ // the user crosses a not exposed view the client will see enter and exit
+ // of the exposed predecessor followed by and enter and exit of that same
+ // predecessor when entering and exiting the not exposed descendant. This
+ // is fine since the client has a clear idea which view is hovered at the
+ // price of a couple more events being sent. This is a simple and
+ // working solution.
+ View source = this;
+ while (true) {
+ if (source.includeForAccessibility()) {
+ source.sendAccessibilityEvent(eventType);
+ return;
+ }
+ ViewParent parent = source.getParent();
+ if (parent instanceof View) {
+ source = (View) parent;
+ } else {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Clears accessibility focus without calling any callback methods
+ * normally invoked in {@link #clearAccessibilityFocus()}. This method
+ * is used separately from that one for clearing accessibility focus when
+ * giving this focus to another view.
+ *
+ * @param action The action, if any, that led to focus being cleared. Set to
+ * AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS to specify that focus is moving within
+ * the window.
+ */
+ void clearAccessibilityFocusNoCallbacks(int action) {
+ if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
+ mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
+ invalidate();
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ event.setAction(action);
+ if (mAccessibilityDelegate != null) {
+ mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
+ } else {
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+ }
+ }
+
+ /**
+ * Call this to try to give focus to a specific view or to one of its
+ * descendants.
+ *
+ * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
+ * false), or if it is focusable and it is not focusable in touch mode
+ * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments
+ * {@link #FOCUS_DOWN} and <code>null</code>.
+ *
+ * @return Whether this view or one of its descendants actually took focus.
+ */
+ public final boolean requestFocus() {
+ return requestFocus(View.FOCUS_DOWN);
+ }
+
+ /**
+ * This will request focus for whichever View was last focused within this
+ * cluster before a focus-jump out of it.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+ // Prioritize focusableByDefault over algorithmic focus selection.
+ if (restoreDefaultFocus()) {
+ return true;
+ }
+ return requestFocus(direction);
+ }
+
+ /**
+ * This will request focus for whichever View not in a cluster was last focused before a
+ * focus-jump to a cluster. If no non-cluster View has previously had focus, this will focus
+ * the "first" focusable view it finds.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean restoreFocusNotInCluster() {
+ return requestFocus(View.FOCUS_DOWN);
+ }
+
+ /**
+ * Gives focus to the default-focus view in the view hierarchy that has this view as a root.
+ * If the default-focus view cannot be found, falls back to calling {@link #requestFocus(int)}.
+ *
+ * @return Whether this view or one of its descendants actually took focus
+ */
+ public boolean restoreDefaultFocus() {
+ return requestFocus(View.FOCUS_DOWN);
+ }
+
+ /**
+ * Call this to try to give focus to a specific view or to one of its
+ * descendants and give it a hint about what direction focus is heading.
+ *
+ * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
+ * false), or if it is focusable and it is not focusable in touch mode
+ * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * This is equivalent to calling {@link #requestFocus(int, Rect)} with
+ * <code>null</code> set for the previously focused rectangle.
+ *
+ * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+ * @return Whether this view or one of its descendants actually took focus.
+ */
+ public final boolean requestFocus(int direction) {
+ return requestFocus(direction, null);
+ }
+
+ /**
+ * Call this to try to give focus to a specific view or to one of its descendants
+ * and give it hints about the direction and a specific rectangle that the focus
+ * is coming from. The rectangle can help give larger views a finer grained hint
+ * about where focus is coming from, and therefore, where to show selection, or
+ * forward focus change internally.
+ *
+ * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
+ * false), or if it is focusable and it is not focusable in touch mode
+ * ({@link #isFocusableInTouchMode}) while the device is in touch mode.
+ *
+ * A View will not take focus if it is not visible.
+ *
+ * A View will not take focus if one of its parents has
+ * {@link android.view.ViewGroup#getDescendantFocusability()} equal to
+ * {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * You may wish to override this method if your custom {@link View} has an internal
+ * {@link View} that it wishes to forward the request to.
+ *
+ * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+ * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
+ * to give a finer grained hint about where focus is coming from. May be null
+ * if there is no hint.
+ * @return Whether this view or one of its descendants actually took focus.
+ */
+ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ return requestFocusNoSearch(direction, previouslyFocusedRect);
+ }
+
+ private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
+ // need to be focusable
+ if ((mViewFlags & FOCUSABLE) != FOCUSABLE
+ || (mViewFlags & VISIBILITY_MASK) != VISIBLE
+ || (mViewFlags & ENABLED_MASK) != ENABLED) {
+ return false;
+ }
+
+ // need to be focusable in touch mode if in touch mode
+ if (isInTouchMode() &&
+ (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
+ return false;
+ }
+
+ // need to not have any parents blocking us
+ if (hasAncestorThatBlocksDescendantFocus()) {
+ return false;
+ }
+
+ handleFocusGainInternal(direction, previouslyFocusedRect);
+ return true;
+ }
+
+ /**
+ * Call this to try to give focus to a specific view or to one of its descendants. This is a
+ * special variant of {@link #requestFocus() } that will allow views that are not focusable in
+ * touch mode to request focus when they are touched.
+ *
+ * @return Whether this view or one of its descendants actually took focus.
+ *
+ * @see #isInTouchMode()
+ *
+ */
+ public final boolean requestFocusFromTouch() {
+ // Leave touch mode if we need to
+ if (isInTouchMode()) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.ensureTouchMode(false);
+ }
+ }
+ return requestFocus(View.FOCUS_DOWN);
+ }
+
+ /**
+ * @return Whether any ancestor of this view blocks descendant focus.
+ */
+ private boolean hasAncestorThatBlocksDescendantFocus() {
+ final boolean focusableInTouchMode = isFocusableInTouchMode();
+ ViewParent ancestor = mParent;
+ while (ancestor instanceof ViewGroup) {
+ final ViewGroup vgAncestor = (ViewGroup) ancestor;
+ if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS
+ || (!focusableInTouchMode && vgAncestor.shouldBlockFocusForTouchscreen())) {
+ return true;
+ } else {
+ ancestor = vgAncestor.getParent();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the mode for determining whether this View is important for accessibility.
+ * A view is important for accessibility if it fires accessibility events and if it
+ * is reported to accessibility services that query the screen.
+ *
+ * @return The mode for determining whether a view is important for accessibility, one
+ * of {@link #IMPORTANT_FOR_ACCESSIBILITY_AUTO}, {@link #IMPORTANT_FOR_ACCESSIBILITY_YES},
+ * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO}, or
+ * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}.
+ *
+ * @attr ref android.R.styleable#View_importantForAccessibility
+ *
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ */
+ @ViewDebug.ExportedProperty(category = "accessibility", mapping = {
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_AUTO, to = "auto"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_YES, to = "yes"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO, to = "no"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+ to = "noHideDescendants")
+ })
+ public int getImportantForAccessibility() {
+ return (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK)
+ >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ }
+
+ /**
+ * Sets the live region mode for this view. This indicates to accessibility
+ * services whether they should automatically notify the user about changes
+ * to the view's content description or text, or to the content descriptions
+ * or text of the view's children (where applicable).
+ * <p>
+ * For example, in a login screen with a TextView that displays an "incorrect
+ * password" notification, that view should be marked as a live region with
+ * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
+ * <p>
+ * To disable change notifications for this view, use
+ * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region
+ * mode for most views.
+ * <p>
+ * To indicate that the user should be notified of changes, use
+ * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
+ * <p>
+ * If the view's changes should interrupt ongoing speech and notify the user
+ * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}.
+ *
+ * @param mode The live region mode for this view, one of:
+ * <ul>
+ * <li>{@link #ACCESSIBILITY_LIVE_REGION_NONE}
+ * <li>{@link #ACCESSIBILITY_LIVE_REGION_POLITE}
+ * <li>{@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}
+ * </ul>
+ * @attr ref android.R.styleable#View_accessibilityLiveRegion
+ */
+ public void setAccessibilityLiveRegion(int mode) {
+ if (mode != getAccessibilityLiveRegion()) {
+ mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
+ mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT)
+ & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+
+ /**
+ * Gets the live region mode for this View.
+ *
+ * @return The live region mode for the view.
+ *
+ * @attr ref android.R.styleable#View_accessibilityLiveRegion
+ *
+ * @see #setAccessibilityLiveRegion(int)
+ */
+ public int getAccessibilityLiveRegion() {
+ return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK)
+ >> PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT;
+ }
+
+ /**
+ * Sets how to determine whether this view is important for accessibility
+ * which is if it fires accessibility events and if it is reported to
+ * accessibility services that query the screen.
+ *
+ * @param mode How to determine whether this view is important for accessibility.
+ *
+ * @attr ref android.R.styleable#View_importantForAccessibility
+ *
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ */
+ public void setImportantForAccessibility(int mode) {
+ final int oldMode = getImportantForAccessibility();
+ if (mode != oldMode) {
+ final boolean hideDescendants =
+ mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+
+ // If this node or its descendants are no longer important, try to
+ // clear accessibility focus.
+ if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO || hideDescendants) {
+ final View focusHost = findAccessibilityFocusHost(hideDescendants);
+ if (focusHost != null) {
+ focusHost.clearAccessibilityFocus();
+ }
+ }
+
+ // If we're moving between AUTO and another state, we might not need
+ // to send a subtree changed notification. We'll store the computed
+ // importance, since we'll need to check it later to make sure.
+ final boolean maySkipNotify = oldMode == IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ || mode == IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ final boolean oldIncludeForAccessibility = maySkipNotify && includeForAccessibility();
+ mPrivateFlags2 &= ~PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
+ & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ } else {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+ }
+
+ /**
+ * Returns the view within this view's hierarchy that is hosting
+ * accessibility focus.
+ *
+ * @param searchDescendants whether to search for focus in descendant views
+ * @return the view hosting accessibility focus, or {@code null}
+ */
+ private View findAccessibilityFocusHost(boolean searchDescendants) {
+ if (isAccessibilityFocusedViewOrHost()) {
+ return this;
+ }
+
+ if (searchDescendants) {
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ final View focusHost = viewRoot.getAccessibilityFocusedHost();
+ if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) {
+ return focusHost;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Computes whether this view should be exposed for accessibility. In
+ * general, views that are interactive or provide information are exposed
+ * while views that serve only as containers are hidden.
+ * <p>
+ * If an ancestor of this view has importance
+ * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, this method
+ * returns <code>false</code>.
+ * <p>
+ * Otherwise, the value is computed according to the view's
+ * {@link #getImportantForAccessibility()} value:
+ * <ol>
+ * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_NO} or
+ * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, return <code>false
+ * </code>
+ * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_YES}, return <code>true</code>
+ * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_AUTO}, return <code>true</code> if
+ * view satisfies any of the following:
+ * <ul>
+ * <li>Is actionable, e.g. {@link #isClickable()},
+ * {@link #isLongClickable()}, or {@link #isFocusable()}
+ * <li>Has an {@link AccessibilityDelegate}
+ * <li>Has an interaction listener, e.g. {@link OnTouchListener},
+ * {@link OnKeyListener}, etc.
+ * <li>Is an accessibility live region, e.g.
+ * {@link #getAccessibilityLiveRegion()} is not
+ * {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
+ * </ul>
+ * </ol>
+ *
+ * @return Whether the view is exposed for accessibility.
+ * @see #setImportantForAccessibility(int)
+ * @see #getImportantForAccessibility()
+ */
+ public boolean isImportantForAccessibility() {
+ final int mode = (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK)
+ >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO
+ || mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+ return false;
+ }
+
+ // Check parent mode to ensure we're not hidden.
+ ViewParent parent = mParent;
+ while (parent instanceof View) {
+ if (((View) parent).getImportantForAccessibility()
+ == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+ return false;
+ }
+ parent = parent.getParent();
+ }
+
+ return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
+ || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null
+ || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE;
+ }
+
+ /**
+ * Gets the parent for accessibility purposes. Note that the parent for
+ * accessibility is not necessary the immediate parent. It is the first
+ * predecessor that is important for accessibility.
+ *
+ * @return The parent for accessibility purposes.
+ */
+ public ViewParent getParentForAccessibility() {
+ if (mParent instanceof View) {
+ View parentView = (View) mParent;
+ if (parentView.includeForAccessibility()) {
+ return mParent;
+ } else {
+ return mParent.getParentForAccessibility();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds the children of this View relevant for accessibility to the given list
+ * as output. Since some Views are not important for accessibility the added
+ * child views are not necessarily direct children of this view, rather they are
+ * the first level of descendants important for accessibility.
+ *
+ * @param outChildren The output list that will receive children for accessibility.
+ */
+ public void addChildrenForAccessibility(ArrayList<View> outChildren) {
+
+ }
+
+ /**
+ * Whether to regard this view for accessibility. A view is regarded for
+ * accessibility if it is important for accessibility or the querying
+ * accessibility service has explicitly requested that view not
+ * important for accessibility are regarded.
+ *
+ * @return Whether to regard the view for accessibility.
+ *
+ * @hide
+ */
+ public boolean includeForAccessibility() {
+ if (mAttachInfo != null) {
+ return (mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ || isImportantForAccessibility();
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the View is considered actionable from
+ * accessibility perspective. Such view are important for
+ * accessibility.
+ *
+ * @return True if the view is actionable for accessibility.
+ *
+ * @hide
+ */
+ public boolean isActionableForAccessibility() {
+ return (isClickable() || isLongClickable() || isFocusable());
+ }
+
+ /**
+ * Returns whether the View has registered callbacks which makes it
+ * important for accessibility.
+ *
+ * @return True if the view is actionable for accessibility.
+ */
+ private boolean hasListenersForAccessibility() {
+ ListenerInfo info = getListenerInfo();
+ return mTouchDelegate != null || info.mOnKeyListener != null
+ || info.mOnTouchListener != null || info.mOnGenericMotionListener != null
+ || info.mOnHoverListener != null || info.mOnDragListener != null;
+ }
+
+ /**
+ * Notifies that the accessibility state of this view changed. The change
+ * is local to this view and does not represent structural changes such
+ * as children and parent. For example, the view became focusable. The
+ * notification is at at most once every
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+ * to avoid unnecessary load to the system. Also once a view has a pending
+ * notification this method is a NOP until the notification has been sent.
+ *
+ * @hide
+ */
+ public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+ return;
+ }
+ // If this is a live region, we should send a subtree change event
+ // from this view immediately. Otherwise, we can let it propagate up.
+ if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(changeType);
+ sendAccessibilityEventUnchecked(event);
+ } else if (mParent != null) {
+ try {
+ mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType);
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ }
+
+ /**
+ * Notifies that the accessibility state of this view changed. The change
+ * is *not* local to this view and does represent structural changes such
+ * as children and parent. For example, the view size changed. The
+ * notification is at at most once every
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+ * to avoid unnecessary load to the system. Also once a view has a pending
+ * notification this method is a NOP until the notification has been sent.
+ *
+ * @hide
+ */
+ public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+ return;
+ }
+ if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
+ mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
+ if (mParent != null) {
+ try {
+ mParent.notifySubtreeAccessibilityStateChanged(
+ this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Change the visibility of the View without triggering any other changes. This is
+ * important for transitions, where visibility changes should not adjust focus or
+ * trigger a new layout. This is only used when the visibility has already been changed
+ * and we need a transient value during an animation. When the animation completes,
+ * the original visibility value is always restored.
+ *
+ * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+ * @hide
+ */
+ public void setTransitionVisibility(@Visibility int visibility) {
+ mViewFlags = (mViewFlags & ~View.VISIBILITY_MASK) | visibility;
+ }
+
+ /**
+ * Reset the flag indicating the accessibility state of the subtree rooted
+ * at this view changed.
+ */
+ void resetSubtreeAccessibilityStateChanged() {
+ mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
+ }
+
+ /**
+ * Report an accessibility action to this view's parents for delegated processing.
+ *
+ * <p>Implementations of {@link #performAccessibilityAction(int, Bundle)} may internally
+ * call this method to delegate an accessibility action to a supporting parent. If the parent
+ * returns true from its
+ * {@link ViewParent#onNestedPrePerformAccessibilityAction(View, int, android.os.Bundle)}
+ * method this method will return true to signify that the action was consumed.</p>
+ *
+ * <p>This method is useful for implementing nested scrolling child views. If
+ * {@link #isNestedScrollingEnabled()} returns true and the action is a scrolling action
+ * a custom view implementation may invoke this method to allow a parent to consume the
+ * scroll first. If this method returns true the custom view should skip its own scrolling
+ * behavior.</p>
+ *
+ * @param action Accessibility action to delegate
+ * @param arguments Optional action arguments
+ * @return true if the action was consumed by a parent
+ */
+ public boolean dispatchNestedPrePerformAccessibilityAction(int action, Bundle arguments) {
+ for (ViewParent p = getParent(); p != null; p = p.getParent()) {
+ if (p.onNestedPrePerformAccessibilityAction(this, action, arguments)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Performs the specified accessibility action on the view. For
+ * possible accessibility actions look at {@link AccessibilityNodeInfo}.
+ * <p>
+ * If an {@link AccessibilityDelegate} has been specified via calling
+ * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
+ * {@link AccessibilityDelegate#performAccessibilityAction(View, int, Bundle)}
+ * is responsible for handling this call.
+ * </p>
+ *
+ * <p>The default implementation will delegate
+ * {@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD} and
+ * {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD} to nested scrolling parents if
+ * {@link #isNestedScrollingEnabled() nested scrolling is enabled} on this view.</p>
+ *
+ * @param action The action to perform.
+ * @param arguments Optional action arguments.
+ * @return Whether the action was performed.
+ */
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (mAccessibilityDelegate != null) {
+ return mAccessibilityDelegate.performAccessibilityAction(this, action, arguments);
+ } else {
+ return performAccessibilityActionInternal(action, arguments);
+ }
+ }
+
+ /**
+ * @see #performAccessibilityAction(int, Bundle)
+ *
+ * Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (isNestedScrollingEnabled()
+ && (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+ || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
+ || action == R.id.accessibilityActionScrollUp
+ || action == R.id.accessibilityActionScrollLeft
+ || action == R.id.accessibilityActionScrollDown
+ || action == R.id.accessibilityActionScrollRight)) {
+ if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) {
+ return true;
+ }
+ }
+
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK: {
+ if (isClickable()) {
+ performClick();
+ return true;
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
+ if (isLongClickable()) {
+ performLongClick();
+ return true;
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_FOCUS: {
+ if (!hasFocus()) {
+ // Get out of touch mode since accessibility
+ // wants to move focus around.
+ getViewRootImpl().ensureTouchMode(false);
+ return requestFocus();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+ if (hasFocus()) {
+ clearFocus();
+ return !isFocused();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SELECT: {
+ if (!isSelected()) {
+ setSelected(true);
+ return isSelected();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
+ if (isSelected()) {
+ setSelected(false);
+ return !isSelected();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+ if (!isAccessibilityFocused()) {
+ return requestAccessibilityFocus();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+ if (isAccessibilityFocused()) {
+ clearAccessibilityFocus();
+ return true;
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: {
+ if (arguments != null) {
+ final int granularity = arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+ final boolean extendSelection = arguments.getBoolean(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+ return traverseAtGranularity(granularity, true, extendSelection);
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
+ if (arguments != null) {
+ final int granularity = arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+ final boolean extendSelection = arguments.getBoolean(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+ return traverseAtGranularity(granularity, false, extendSelection);
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text == null) {
+ return false;
+ }
+ final int start = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
+ final int end = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
+ // Only cursor position can be specified (selection length == 0)
+ if ((getAccessibilitySelectionStart() != start
+ || getAccessibilitySelectionEnd() != end)
+ && (start == end)) {
+ setAccessibilitySelection(start, end);
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ return true;
+ }
+ } break;
+ case R.id.accessibilityActionShowOnScreen: {
+ if (mAttachInfo != null) {
+ final Rect r = mAttachInfo.mTmpInvalRect;
+ getDrawingRect(r);
+ return requestRectangleOnScreen(r, true);
+ }
+ } break;
+ case R.id.accessibilityActionContextClick: {
+ if (isContextClickable()) {
+ performContextClick();
+ return true;
+ }
+ } break;
+ }
+ return false;
+ }
+
+ private boolean traverseAtGranularity(int granularity, boolean forward,
+ boolean extendSelection) {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text == null || text.length() == 0) {
+ return false;
+ }
+ TextSegmentIterator iterator = getIteratorForGranularity(granularity);
+ if (iterator == null) {
+ return false;
+ }
+ int current = getAccessibilitySelectionEnd();
+ if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+ current = forward ? 0 : text.length();
+ }
+ final int[] range = forward ? iterator.following(current) : iterator.preceding(current);
+ if (range == null) {
+ return false;
+ }
+ final int segmentStart = range[0];
+ final int segmentEnd = range[1];
+ int selectionStart;
+ int selectionEnd;
+ if (extendSelection && isAccessibilitySelectionExtendable()) {
+ selectionStart = getAccessibilitySelectionStart();
+ if (selectionStart == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+ selectionStart = forward ? segmentStart : segmentEnd;
+ }
+ selectionEnd = forward ? segmentEnd : segmentStart;
+ } else {
+ selectionStart = selectionEnd= forward ? segmentEnd : segmentStart;
+ }
+ setAccessibilitySelection(selectionStart, selectionEnd);
+ final int action = forward ? AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ : AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+ sendViewTextTraversedAtGranularityEvent(action, granularity, segmentStart, segmentEnd);
+ return true;
+ }
+
+ /**
+ * Gets the text reported for accessibility purposes.
+ *
+ * @return The accessibility text.
+ *
+ * @hide
+ */
+ public CharSequence getIterableTextForAccessibility() {
+ return getContentDescription();
+ }
+
+ /**
+ * Gets whether accessibility selection can be extended.
+ *
+ * @return If selection is extensible.
+ *
+ * @hide
+ */
+ public boolean isAccessibilitySelectionExtendable() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ public int getAccessibilitySelectionStart() {
+ return mAccessibilityCursorPosition;
+ }
+
+ /**
+ * @hide
+ */
+ public int getAccessibilitySelectionEnd() {
+ return getAccessibilitySelectionStart();
+ }
+
+ /**
+ * @hide
+ */
+ public void setAccessibilitySelection(int start, int end) {
+ if (start == end && end == mAccessibilityCursorPosition) {
+ return;
+ }
+ if (start >= 0 && start == end && end <= getIterableTextForAccessibility().length()) {
+ mAccessibilityCursorPosition = start;
+ } else {
+ mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+ }
+
+ private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
+ int fromIndex, int toIndex) {
+ if (mParent == null) {
+ return;
+ }
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
+ onInitializeAccessibilityEvent(event);
+ onPopulateAccessibilityEvent(event);
+ event.setFromIndex(fromIndex);
+ event.setToIndex(toIndex);
+ event.setAction(action);
+ event.setMovementGranularity(granularity);
+ mParent.requestSendAccessibilityEvent(this, event);
+ }
+
+ /**
+ * @hide
+ */
+ public TextSegmentIterator getIteratorForGranularity(int granularity) {
+ switch (granularity) {
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ CharacterTextSegmentIterator iterator =
+ CharacterTextSegmentIterator.getInstance(
+ mContext.getResources().getConfiguration().locale);
+ iterator.initialize(text.toString());
+ return iterator;
+ }
+ } break;
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ WordTextSegmentIterator iterator =
+ WordTextSegmentIterator.getInstance(
+ mContext.getResources().getConfiguration().locale);
+ iterator.initialize(text.toString());
+ return iterator;
+ }
+ } break;
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ ParagraphTextSegmentIterator iterator =
+ ParagraphTextSegmentIterator.getInstance();
+ iterator.initialize(text.toString());
+ return iterator;
+ }
+ } break;
+ }
+ return null;
+ }
+
+ /**
+ * Tells whether the {@link View} is in the state between {@link #onStartTemporaryDetach()}
+ * and {@link #onFinishTemporaryDetach()}.
+ *
+ * <p>This method always returns {@code true} when called directly or indirectly from
+ * {@link #onStartTemporaryDetach()}. The return value when called directly or indirectly from
+ * {@link #onFinishTemporaryDetach()}, however, depends on the OS version.
+ * <ul>
+ * <li>{@code true} on {@link android.os.Build.VERSION_CODES#N API 24}</li>
+ * <li>{@code false} on {@link android.os.Build.VERSION_CODES#N_MR1 API 25}} and later</li>
+ * </ul>
+ * </p>
+ *
+ * @return {@code true} when the View is in the state between {@link #onStartTemporaryDetach()}
+ * and {@link #onFinishTemporaryDetach()}.
+ */
+ public final boolean isTemporarilyDetached() {
+ return (mPrivateFlags3 & PFLAG3_TEMPORARY_DETACH) != 0;
+ }
+
+ /**
+ * Dispatch {@link #onStartTemporaryDetach()} to this View and its direct children if this is
+ * a container View.
+ */
+ @CallSuper
+ public void dispatchStartTemporaryDetach() {
+ mPrivateFlags3 |= PFLAG3_TEMPORARY_DETACH;
+ notifyEnterOrExitForAutoFillIfNeeded(false);
+ onStartTemporaryDetach();
+ }
+
+ /**
+ * This is called when a container is going to temporarily detach a child, with
+ * {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}.
+ * It will either be followed by {@link #onFinishTemporaryDetach()} or
+ * {@link #onDetachedFromWindow()} when the container is done.
+ */
+ public void onStartTemporaryDetach() {
+ removeUnsetPressCallback();
+ mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
+ }
+
+ /**
+ * Dispatch {@link #onFinishTemporaryDetach()} to this View and its direct children if this is
+ * a container View.
+ */
+ @CallSuper
+ public void dispatchFinishTemporaryDetach() {
+ mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
+ onFinishTemporaryDetach();
+ if (hasWindowFocus() && hasFocus()) {
+ InputMethodManager.getInstance().focusIn(this);
+ }
+ notifyEnterOrExitForAutoFillIfNeeded(true);
+ }
+
+ /**
+ * Called after {@link #onStartTemporaryDetach} when the container is done
+ * changing the view.
+ */
+ public void onFinishTemporaryDetach() {
+ }
+
+ /**
+ * Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}
+ * for this view's window. Returns null if the view is not currently attached
+ * to the window. Normally you will not need to use this directly, but
+ * just use the standard high-level event callbacks like
+ * {@link #onKeyDown(int, KeyEvent)}.
+ */
+ public KeyEvent.DispatcherState getKeyDispatcherState() {
+ return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;
+ }
+
+ /**
+ * Dispatch a key event before it is processed by any input method
+ * associated with the view hierarchy. This can be used to intercept
+ * key events in special situations before the IME consumes them; a
+ * typical example would be handling the BACK key to update the application's
+ * UI instead of allowing the IME to see it and close itself.
+ *
+ * @param event The key event to be dispatched.
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ return onKeyPreIme(event.getKeyCode(), event);
+ }
+
+ /**
+ * Dispatch a key event to the next view on the focus path. This path runs
+ * from the top of the view tree down to the currently focused view. If this
+ * view has focus, it will dispatch to itself. Otherwise it will dispatch
+ * the next node down the focus path. This method also fires any key
+ * listeners.
+ *
+ * @param event The key event to be dispatched.
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 0);
+ }
+
+ // Give any attached key listener a first crack at the event.
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
+ return true;
+ }
+
+ if (event.dispatch(this, mAttachInfo != null
+ ? mAttachInfo.mKeyDispatchState : null, this)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
+ }
+
+ /**
+ * Dispatches a key shortcut event.
+ *
+ * @param event The key event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return onKeyShortcut(event.getKeyCode(), event);
+ }
+
+ /**
+ * Pass the touch screen motion event down to the target view, or this
+ * view if it is the target.
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ // If the event should be handled by accessibility focus first.
+ if (event.isTargetAccessibilityFocus()) {
+ // We don't have focus or no virtual descendant has it, do not handle the event.
+ if (!isAccessibilityFocusedViewOrHost()) {
+ return false;
+ }
+ // We have focus and got the event, then use normal event dispatch.
+ event.setTargetAccessibilityFocus(false);
+ }
+
+ boolean result = false;
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+ }
+
+ final int actionMasked = event.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Defensive cleanup for new gesture
+ stopNestedScroll();
+ }
+
+ if (onFilterTouchEventForSecurity(event)) {
+ if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
+ result = true;
+ }
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnTouchListener != null
+ && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnTouchListener.onTouch(this, event)) {
+ result = true;
+ }
+
+ if (!result && onTouchEvent(event)) {
+ result = true;
+ }
+ }
+
+ if (!result && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+
+ // Clean up after nested scrolls if this is the end of a gesture;
+ // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
+ // of the gesture.
+ if (actionMasked == MotionEvent.ACTION_UP ||
+ actionMasked == MotionEvent.ACTION_CANCEL ||
+ (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
+ stopNestedScroll();
+ }
+
+ return result;
+ }
+
+ boolean isAccessibilityFocusedViewOrHost() {
+ return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
+ .getAccessibilityFocusedHost() == this);
+ }
+
+ /**
+ * Filter the touch event to apply security policies.
+ *
+ * @param event The motion event to be filtered.
+ * @return True if the event should be dispatched, false if the event should be dropped.
+ *
+ * @see #getFilterTouchesWhenObscured
+ */
+ public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+ //noinspection RedundantIfStatement
+ if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
+ && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
+ // Window is obscured, drop this touch.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Pass a trackball motion event down to the focused view.
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
+ }
+
+ return onTrackballEvent(event);
+ }
+
+ /**
+ * Pass a captured pointer event down to the focused view.
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+ if (!hasPointerCapture()) {
+ return false;
+ }
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnCapturedPointerListener != null
+ && li.mOnCapturedPointerListener.onCapturedPointer(this, event)) {
+ return true;
+ }
+ return onCapturedPointerEvent(event);
+ }
+
+ /**
+ * Dispatch a generic motion event.
+ * <p>
+ * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+ * are delivered to the view under the pointer. All other generic motion events are
+ * delivered to the focused view. Hover events are handled specially and are delivered
+ * to {@link #onHoverEvent(MotionEvent)}.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ }
+
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE
+ || action == MotionEvent.ACTION_HOVER_EXIT) {
+ if (dispatchHoverEvent(event)) {
+ return true;
+ }
+ } else if (dispatchGenericPointerEvent(event)) {
+ return true;
+ }
+ } else if (dispatchGenericFocusedEvent(event)) {
+ return true;
+ }
+
+ if (dispatchGenericMotionEventInternal(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
+ }
+
+ private boolean dispatchGenericMotionEventInternal(MotionEvent event) {
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnGenericMotionListener != null
+ && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnGenericMotionListener.onGenericMotion(this, event)) {
+ return true;
+ }
+
+ if (onGenericMotionEvent(event)) {
+ return true;
+ }
+
+ final int actionButton = event.getActionButton();
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_BUTTON_PRESS:
+ if (isContextClickable() && !mInContextButtonPress && !mHasPerformedLongPress
+ && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+ || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+ if (performContextClick(event.getX(), event.getY())) {
+ mInContextButtonPress = true;
+ setPressed(true, event.getX(), event.getY());
+ removeTapCallback();
+ removeLongPressCallback();
+ return true;
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_BUTTON_RELEASE:
+ if (mInContextButtonPress && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
+ || actionButton == MotionEvent.BUTTON_SECONDARY)) {
+ mInContextButtonPress = false;
+ mIgnoreNextUpEvent = true;
+ }
+ break;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch a hover event.
+ * <p>
+ * Do not call this method directly.
+ * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ ListenerInfo li = mListenerInfo;
+ //noinspection SimplifiableIfStatement
+ if (li != null && li.mOnHoverListener != null
+ && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnHoverListener.onHover(this, event)) {
+ return true;
+ }
+
+ return onHoverEvent(event);
+ }
+
+ /**
+ * Returns true if the view has a child to which it has recently sent
+ * {@link MotionEvent#ACTION_HOVER_ENTER}. If this view is hovered and
+ * it does not have a hovered child, then it must be the innermost hovered view.
+ * @hide
+ */
+ protected boolean hasHoveredChild() {
+ return false;
+ }
+
+ /**
+ * Dispatch a generic motion event to the view under the first pointer.
+ * <p>
+ * Do not call this method directly.
+ * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Dispatch a generic motion event to the currently focused view.
+ * <p>
+ * Do not call this method directly.
+ * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Dispatch a pointer event.
+ * <p>
+ * Dispatches touch related pointer events to {@link #onTouchEvent(MotionEvent)} and all
+ * other events to {@link #onGenericMotionEvent(MotionEvent)}. This separation of concerns
+ * reinforces the invariant that {@link #onTouchEvent(MotionEvent)} is really about touches
+ * and should not be expected to handle other pointing device features.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ * @hide
+ */
+ public final boolean dispatchPointerEvent(MotionEvent event) {
+ if (event.isTouchEvent()) {
+ return dispatchTouchEvent(event);
+ } else {
+ return dispatchGenericMotionEvent(event);
+ }
+ }
+
+ /**
+ * Called when the window containing this view gains or loses window focus.
+ * ViewGroups should override to route to their children.
+ *
+ * @param hasFocus True if the window containing this view now has focus,
+ * false otherwise.
+ */
+ public void dispatchWindowFocusChanged(boolean hasFocus) {
+ onWindowFocusChanged(hasFocus);
+ }
+
+ /**
+ * Called when the window containing this view gains or loses focus. Note
+ * that this is separate from view focus: to receive key events, both
+ * your view and its window must have focus. If a window is displayed
+ * on top of yours that takes input focus, then your own window will lose
+ * focus but the view focus will remain unchanged.
+ *
+ * @param hasWindowFocus True if the window containing this view now has
+ * focus, false otherwise.
+ */
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (!hasWindowFocus) {
+ if (isPressed()) {
+ setPressed(false);
+ }
+ mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+ if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
+ imm.focusOut(this);
+ }
+ removeLongPressCallback();
+ removeTapCallback();
+ onFocusLost();
+ } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
+ imm.focusIn(this);
+ }
+
+ notifyEnterOrExitForAutoFillIfNeeded(hasWindowFocus);
+
+ refreshDrawableState();
+ }
+
+ /**
+ * Returns true if this view is in a window that currently has window focus.
+ * Note that this is not the same as the view itself having focus.
+ *
+ * @return True if this view is in a window that currently has window focus.
+ */
+ public boolean hasWindowFocus() {
+ return mAttachInfo != null && mAttachInfo.mHasWindowFocus;
+ }
+
+ /**
+ * Dispatch a view visibility change down the view hierarchy.
+ * ViewGroups should override to route to their children.
+ * @param changedView The view whose visibility changed. Could be 'this' or
+ * an ancestor view.
+ * @param visibility The new visibility of changedView: {@link #VISIBLE},
+ * {@link #INVISIBLE} or {@link #GONE}.
+ */
+ protected void dispatchVisibilityChanged(@NonNull View changedView,
+ @Visibility int visibility) {
+ onVisibilityChanged(changedView, visibility);
+ }
+
+ /**
+ * Called when the visibility of the view or an ancestor of the view has
+ * changed.
+ *
+ * @param changedView The view whose visibility changed. May be
+ * {@code this} or an ancestor view.
+ * @param visibility The new visibility, one of {@link #VISIBLE},
+ * {@link #INVISIBLE} or {@link #GONE}.
+ */
+ protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {
+ }
+
+ /**
+ * Dispatch a hint about whether this view is displayed. For instance, when
+ * a View moves out of the screen, it might receives a display hint indicating
+ * the view is not displayed. Applications should not <em>rely</em> on this hint
+ * as there is no guarantee that they will receive one.
+ *
+ * @param hint A hint about whether or not this view is displayed:
+ * {@link #VISIBLE} or {@link #INVISIBLE}.
+ */
+ public void dispatchDisplayHint(@Visibility int hint) {
+ onDisplayHint(hint);
+ }
+
+ /**
+ * Gives this view a hint about whether is displayed or not. For instance, when
+ * a View moves out of the screen, it might receives a display hint indicating
+ * the view is not displayed. Applications should not <em>rely</em> on this hint
+ * as there is no guarantee that they will receive one.
+ *
+ * @param hint A hint about whether or not this view is displayed:
+ * {@link #VISIBLE} or {@link #INVISIBLE}.
+ */
+ protected void onDisplayHint(@Visibility int hint) {
+ }
+
+ /**
+ * Dispatch a window visibility change down the view hierarchy.
+ * ViewGroups should override to route to their children.
+ *
+ * @param visibility The new visibility of the window.
+ *
+ * @see #onWindowVisibilityChanged(int)
+ */
+ public void dispatchWindowVisibilityChanged(@Visibility int visibility) {
+ onWindowVisibilityChanged(visibility);
+ }
+
+ /**
+ * Called when the window containing has change its visibility
+ * (between {@link #GONE}, {@link #INVISIBLE}, and {@link #VISIBLE}). Note
+ * that this tells you whether or not your window is being made visible
+ * to the window manager; this does <em>not</em> tell you whether or not
+ * your window is obscured by other windows on the screen, even if it
+ * is itself visible.
+ *
+ * @param visibility The new visibility of the window.
+ */
+ protected void onWindowVisibilityChanged(@Visibility int visibility) {
+ if (visibility == VISIBLE) {
+ initialAwakenScrollBars();
+ }
+ }
+
+ /**
+ * Internal dispatching method for {@link #onVisibilityAggregated}. Overridden by
+ * ViewGroup. Intended to only be called when {@link #isAttachedToWindow()},
+ * {@link #getWindowVisibility()} is {@link #VISIBLE} and this view's parent {@link #isShown()}.
+ *
+ * @param isVisible true if this view's visibility to the user is uninterrupted by its
+ * ancestors or by window visibility
+ * @return true if this view is visible to the user, not counting clipping or overlapping
+ */
+ boolean dispatchVisibilityAggregated(boolean isVisible) {
+ final boolean thisVisible = getVisibility() == VISIBLE;
+ // If we're not visible but something is telling us we are, ignore it.
+ if (thisVisible || !isVisible) {
+ onVisibilityAggregated(isVisible);
+ }
+ return thisVisible && isVisible;
+ }
+
+ /**
+ * Called when the user-visibility of this View is potentially affected by a change
+ * to this view itself, an ancestor view or the window this view is attached to.
+ *
+ * @param isVisible true if this view and all of its ancestors are {@link #VISIBLE}
+ * and this view's window is also visible
+ */
+ @CallSuper
+ public void onVisibilityAggregated(boolean isVisible) {
+ if (isVisible && mAttachInfo != null) {
+ initialAwakenScrollBars();
+ }
+
+ final Drawable dr = mBackground;
+ if (dr != null && isVisible != dr.isVisible()) {
+ dr.setVisible(isVisible, false);
+ }
+ final Drawable hl = mDefaultFocusHighlight;
+ if (hl != null && isVisible != hl.isVisible()) {
+ hl.setVisible(isVisible, false);
+ }
+ final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+ if (fg != null && isVisible != fg.isVisible()) {
+ fg.setVisible(isVisible, false);
+ }
+
+ if (isAutofillable()) {
+ AutofillManager afm = getAutofillManager();
+
+ if (afm != null && getAutofillViewId() > LAST_APP_AUTOFILL_ID) {
+ if (mVisibilityChangeForAutofillHandler != null) {
+ mVisibilityChangeForAutofillHandler.removeMessages(0);
+ }
+
+ // If the view is in the background but still part of the hierarchy this is called
+ // with isVisible=false. Hence visibility==false requires further checks
+ if (isVisible) {
+ afm.notifyViewVisibilityChanged(this, true);
+ } else {
+ if (mVisibilityChangeForAutofillHandler == null) {
+ mVisibilityChangeForAutofillHandler =
+ new VisibilityChangeForAutofillHandler(afm, this);
+ }
+ // Let current operation (e.g. removal of the view from the hierarchy)
+ // finish before checking state
+ mVisibilityChangeForAutofillHandler.obtainMessage(0, this).sendToTarget();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the current visibility of the window this view is attached to
+ * (either {@link #GONE}, {@link #INVISIBLE}, or {@link #VISIBLE}).
+ *
+ * @return Returns the current visibility of the view's window.
+ */
+ @Visibility
+ public int getWindowVisibility() {
+ return mAttachInfo != null ? mAttachInfo.mWindowVisibility : GONE;
+ }
+
+ /**
+ * Retrieve the overall visible display size in which the window this view is
+ * attached to has been positioned in. This takes into account screen
+ * decorations above the window, for both cases where the window itself
+ * is being position inside of them or the window is being placed under
+ * then and covered insets are used for the window to position its content
+ * inside. In effect, this tells you the available area where content can
+ * be placed and remain visible to users.
+ *
+ * <p>This function requires an IPC back to the window manager to retrieve
+ * the requested information, so should not be used in performance critical
+ * code like drawing.
+ *
+ * @param outRect Filled in with the visible display frame. If the view
+ * is not attached to a window, this is simply the raw display size.
+ */
+ public void getWindowVisibleDisplayFrame(Rect outRect) {
+ if (mAttachInfo != null) {
+ try {
+ mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
+ } catch (RemoteException e) {
+ return;
+ }
+ // XXX This is really broken, and probably all needs to be done
+ // in the window manager, and we need to know more about whether
+ // we want the area behind or in front of the IME.
+ final Rect insets = mAttachInfo.mVisibleInsets;
+ outRect.left += insets.left;
+ outRect.top += insets.top;
+ outRect.right -= insets.right;
+ outRect.bottom -= insets.bottom;
+ return;
+ }
+ // The view is not attached to a display so we don't have a context.
+ // Make a best guess about the display size.
+ Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+ d.getRectSize(outRect);
+ }
+
+ /**
+ * Like {@link #getWindowVisibleDisplayFrame}, but returns the "full" display frame this window
+ * is currently in without any insets.
+ *
+ * @hide
+ */
+ public void getWindowDisplayFrame(Rect outRect) {
+ if (mAttachInfo != null) {
+ try {
+ mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
+ } catch (RemoteException e) {
+ return;
+ }
+ return;
+ }
+ // The view is not attached to a display so we don't have a context.
+ // Make a best guess about the display size.
+ Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+ d.getRectSize(outRect);
+ }
+
+ /**
+ * Dispatch a notification about a resource configuration change down
+ * the view hierarchy.
+ * ViewGroups should override to route to their children.
+ *
+ * @param newConfig The new resource configuration.
+ *
+ * @see #onConfigurationChanged(android.content.res.Configuration)
+ */
+ public void dispatchConfigurationChanged(Configuration newConfig) {
+ onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Called when the current configuration of the resources being used
+ * by the application have changed. You can use this to decide when
+ * to reload resources that can changed based on orientation and other
+ * configuration characteristics. You only need to use this if you are
+ * not relying on the normal {@link android.app.Activity} mechanism of
+ * recreating the activity instance upon a configuration change.
+ *
+ * @param newConfig The new resource configuration.
+ */
+ protected void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ /**
+ * Private function to aggregate all per-view attributes in to the view
+ * root.
+ */
+ void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+ performCollectViewAttributes(attachInfo, visibility);
+ }
+
+ void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+ if ((visibility & VISIBILITY_MASK) == VISIBLE) {
+ if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) {
+ attachInfo.mKeepScreenOn = true;
+ }
+ attachInfo.mSystemUiVisibility |= mSystemUiVisibility;
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
+ attachInfo.mHasSystemUiListeners = true;
+ }
+ }
+ }
+
+ void needGlobalAttributesUpdate(boolean force) {
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null && !ai.mRecomputeGlobalAttributes) {
+ if (force || ai.mKeepScreenOn || (ai.mSystemUiVisibility != 0)
+ || ai.mHasSystemUiListeners) {
+ ai.mRecomputeGlobalAttributes = true;
+ }
+ }
+ }
+
+ /**
+ * Returns whether the device is currently in touch mode. Touch mode is entered
+ * once the user begins interacting with the device by touch, and affects various
+ * things like whether focus is always visible to the user.
+ *
+ * @return Whether the device is in touch mode.
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isInTouchMode() {
+ if (mAttachInfo != null) {
+ return mAttachInfo.mInTouchMode;
+ } else {
+ return ViewRootImpl.isInTouchMode();
+ }
+ }
+
+ /**
+ * Returns the context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ *
+ * @return The view's Context.
+ */
+ @ViewDebug.CapturedViewProperty
+ public final Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Handle a key event before it is processed by any input method
+ * associated with the view hierarchy. This can be used to intercept
+ * key events in special situations before the IME consumes them; a
+ * typical example would be handling the BACK key to update the application's
+ * UI instead of allowing the IME to see it and close itself.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return true. If you want to allow the
+ * event to be handled by the next receiver, return false.
+ */
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
+ * KeyEvent.Callback.onKeyDown()}: perform press of the view
+ * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
+ * is released, if the view is enabled and clickable.
+ * <p>
+ * Key presses in software keyboards will generally NOT trigger this
+ * listener, although some may elect to do so in some situations. Do not
+ * rely on this to catch software key presses.
+ *
+ * @param keyCode a key code that represents the button pressed, from
+ * {@link android.view.KeyEvent}
+ * @param event the KeyEvent object that defines the button action
+ */
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+ return true;
+ }
+
+ if (event.getRepeatCount() == 0) {
+ // Long clickable items don't necessarily have to be clickable.
+ final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
+ || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+ if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
+ // For the purposes of menu anchoring and drawable hotspots,
+ // key events are considered to be at the center of the view.
+ final float x = getWidth() / 2f;
+ final float y = getHeight() / 2f;
+ if (clickable) {
+ setPressed(true, x, y);
+ }
+ checkForLongClick(0, x, y);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+ * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+ * the event).
+ * <p>Key presses in software keyboards will generally NOT trigger this listener,
+ * although some may elect to do so in some situations. Do not rely on this to
+ * catch software key presses.
+ */
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)
+ * KeyEvent.Callback.onKeyUp()}: perform clicking of the view
+ * when {@link KeyEvent#KEYCODE_DPAD_CENTER}, {@link KeyEvent#KEYCODE_ENTER}
+ * or {@link KeyEvent#KEYCODE_SPACE} is released.
+ * <p>Key presses in software keyboards will generally NOT trigger this listener,
+ * although some may elect to do so in some situations. Do not rely on this to
+ * catch software key presses.
+ *
+ * @param keyCode A key code that represents the button pressed, from
+ * {@link android.view.KeyEvent}.
+ * @param event The KeyEvent object that defines the button action.
+ */
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+ return true;
+ }
+ if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
+ setPressed(false);
+
+ if (!mHasPerformedLongPress) {
+ // This is a tap, so remove the longpress check
+ removeLongPressCallback();
+ if (!event.isCanceled()) {
+ return performClick();
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+ * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
+ * the event).
+ * <p>Key presses in software keyboards will generally NOT trigger this listener,
+ * although some may elect to do so in some situations. Do not rely on this to
+ * catch software key presses.
+ *
+ * @param keyCode A key code that represents the button pressed, from
+ * {@link android.view.KeyEvent}.
+ * @param repeatCount The number of times the action was made.
+ * @param event The KeyEvent object that defines the button action.
+ */
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Called on the focused view when a key shortcut event is not handled.
+ * Override this method to implement local key shortcuts for the View.
+ * Key shortcuts can also be implemented by setting the
+ * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return true. If you want to allow the
+ * event to be handled by the next receiver, return false.
+ */
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Check whether the called view is a text editor, in which case it
+ * would make sense to automatically display a soft input window for
+ * it. Subclasses should override this if they implement
+ * {@link #onCreateInputConnection(EditorInfo)} to return true if
+ * a call on that method would return a non-null InputConnection, and
+ * they are really a first-class editor that the user would normally
+ * start typing on when the go into a window containing your view.
+ *
+ * <p>The default implementation always returns false. This does
+ * <em>not</em> mean that its {@link #onCreateInputConnection(EditorInfo)}
+ * will not be called or the user can not otherwise perform edits on your
+ * view; it is just a hint to the system that this is not the primary
+ * purpose of this view.
+ *
+ * @return Returns true if this view is a text editor, else false.
+ */
+ public boolean onCheckIsTextEditor() {
+ return false;
+ }
+
+ /**
+ * Create a new InputConnection for an InputMethod to interact
+ * with the view. The default implementation returns null, since it doesn't
+ * support input methods. You can override this to implement such support.
+ * This is only needed for views that take focus and text input.
+ *
+ * <p>When implementing this, you probably also want to implement
+ * {@link #onCheckIsTextEditor()} to indicate you will return a
+ * non-null InputConnection.</p>
+ *
+ * <p>Also, take good care to fill in the {@link android.view.inputmethod.EditorInfo}
+ * object correctly and in its entirety, so that the connected IME can rely
+ * on its values. For example, {@link android.view.inputmethod.EditorInfo#initialSelStart}
+ * and {@link android.view.inputmethod.EditorInfo#initialSelEnd} members
+ * must be filled in with the correct cursor position for IMEs to work correctly
+ * with your application.</p>
+ *
+ * @param outAttrs Fill in with attribute information about the connection.
+ */
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return null;
+ }
+
+ /**
+ * Called by the {@link android.view.inputmethod.InputMethodManager}
+ * when a view who is not the current
+ * input connection target is trying to make a call on the manager. The
+ * default implementation returns false; you can override this to return
+ * true for certain views if you are performing InputConnection proxying
+ * to them.
+ * @param view The View that is making the InputMethodManager call.
+ * @return Return true to allow the call, false to reject.
+ */
+ public boolean checkInputConnectionProxy(View view) {
+ return false;
+ }
+
+ /**
+ * Show the context menu for this view. It is not safe to hold on to the
+ * menu after returning from this method.
+ *
+ * You should normally not overload this method. Overload
+ * {@link #onCreateContextMenu(ContextMenu)} or define an
+ * {@link OnCreateContextMenuListener} to add items to the context menu.
+ *
+ * @param menu The context menu to populate
+ */
+ public void createContextMenu(ContextMenu menu) {
+ ContextMenuInfo menuInfo = getContextMenuInfo();
+
+ // Sets the current menu info so all items added to menu will have
+ // my extra info set.
+ ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
+
+ onCreateContextMenu(menu);
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnCreateContextMenuListener != null) {
+ li.mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
+ }
+
+ // Clear the extra information so subsequent items that aren't mine don't
+ // have my extra info.
+ ((MenuBuilder)menu).setCurrentMenuInfo(null);
+
+ if (mParent != null) {
+ mParent.createContextMenu(menu);
+ }
+ }
+
+ /**
+ * Views should implement this if they have extra information to associate
+ * with the context menu. The return result is supplied as a parameter to
+ * the {@link OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)}
+ * callback.
+ *
+ * @return Extra information about the item for which the context menu
+ * should be shown. This information will vary across different
+ * subclasses of View.
+ */
+ protected ContextMenuInfo getContextMenuInfo() {
+ return null;
+ }
+
+ /**
+ * Views should implement this if the view itself is going to add items to
+ * the context menu.
+ *
+ * @param menu the context menu to populate
+ */
+ protected void onCreateContextMenu(ContextMenu menu) {
+ }
+
+ /**
+ * Implement this method to handle trackball motion events. The
+ * <em>relative</em> movement of the trackball since the last event
+ * can be retrieve with {@link MotionEvent#getX MotionEvent.getX()} and
+ * {@link MotionEvent#getY MotionEvent.getY()}. These are normalized so
+ * that a movement of 1 corresponds to the user pressing one DPAD key (so
+ * they will often be fractional values, representing the more fine-grained
+ * movement information available from a trackball).
+ *
+ * @param event The motion event.
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean onTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle generic motion events.
+ * <p>
+ * Generic motion events describe joystick movements, mouse hovers, track pad
+ * touches, scroll wheel movements and other input events. The
+ * {@link MotionEvent#getSource() source} of the motion event specifies
+ * the class of input that was received. Implementations of this method
+ * must examine the bits in the source before processing the event.
+ * The following code example shows how this is done.
+ * </p><p>
+ * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+ * are delivered to the view under the pointer. All other generic motion events are
+ * delivered to the focused view.
+ * </p>
+ * <pre> public boolean onGenericMotionEvent(MotionEvent event) {
+ * if (event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) {
+ * if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ * // process the joystick movement...
+ * return true;
+ * }
+ * }
+ * if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+ * switch (event.getAction()) {
+ * case MotionEvent.ACTION_HOVER_MOVE:
+ * // process the mouse hover movement...
+ * return true;
+ * case MotionEvent.ACTION_SCROLL:
+ * // process the scroll wheel movement...
+ * return true;
+ * }
+ * }
+ * return super.onGenericMotionEvent(event);
+ * }</pre>
+ *
+ * @param event The generic motion event being processed.
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle hover events.
+ * <p>
+ * This method is called whenever a pointer is hovering into, over, or out of the
+ * bounds of a view and the view is not currently being touched.
+ * Hover events are represented as pointer events with action
+ * {@link MotionEvent#ACTION_HOVER_ENTER}, {@link MotionEvent#ACTION_HOVER_MOVE},
+ * or {@link MotionEvent#ACTION_HOVER_EXIT}.
+ * </p>
+ * <ul>
+ * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_ENTER}
+ * when the pointer enters the bounds of the view.</li>
+ * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_MOVE}
+ * when the pointer has already entered the bounds of the view and has moved.</li>
+ * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_EXIT}
+ * when the pointer has exited the bounds of the view or when the pointer is
+ * about to go down due to a button click, tap, or similar user action that
+ * causes the view to be touched.</li>
+ * </ul>
+ * <p>
+ * The view should implement this method to return true to indicate that it is
+ * handling the hover event, such as by changing its drawable state.
+ * </p><p>
+ * The default implementation calls {@link #setHovered} to update the hovered state
+ * of the view when a hover enter or hover exit event is received, if the view
+ * is enabled and is clickable. The default implementation also sends hover
+ * accessibility events.
+ * </p>
+ *
+ * @param event The motion event that describes the hover.
+ * @return True if the view handled the hover event.
+ *
+ * @see #isHovered
+ * @see #setHovered
+ * @see #onHoverChanged
+ */
+ public boolean onHoverEvent(MotionEvent event) {
+ // The root view may receive hover (or touch) events that are outside the bounds of
+ // the window. This code ensures that we only send accessibility events for
+ // hovers that are actually within the bounds of the root view.
+ final int action = event.getActionMasked();
+ if (!mSendingHoverAccessibilityEvents) {
+ if ((action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE)
+ && !hasHoveredChild()
+ && pointInView(event.getX(), event.getY())) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mSendingHoverAccessibilityEvents = true;
+ }
+ } else {
+ if (action == MotionEvent.ACTION_HOVER_EXIT
+ || (action == MotionEvent.ACTION_MOVE
+ && !pointInView(event.getX(), event.getY()))) {
+ mSendingHoverAccessibilityEvents = false;
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ }
+ }
+
+ if ((action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE)
+ && event.isFromSource(InputDevice.SOURCE_MOUSE)
+ && isOnScrollbar(event.getX(), event.getY())) {
+ awakenScrollBars();
+ }
+
+ // If we consider ourself hoverable, or if we we're already hovered,
+ // handle changing state in response to ENTER and EXIT events.
+ if (isHoverable() || isHovered()) {
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ setHovered(true);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ setHovered(false);
+ break;
+ }
+
+ // Dispatch the event to onGenericMotionEvent before returning true.
+ // This is to provide compatibility with existing applications that
+ // handled HOVER_MOVE events in onGenericMotionEvent and that would
+ // break because of the new default handling for hoverable views
+ // in onHoverEvent.
+ // Note that onGenericMotionEvent will be called by default when
+ // onHoverEvent returns false (refer to dispatchGenericMotionEvent).
+ dispatchGenericMotionEventInternal(event);
+ // The event was already handled by calling setHovered(), so always
+ // return true.
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the view should handle {@link #onHoverEvent}
+ * by calling {@link #setHovered} to change its hovered state.
+ *
+ * @return True if the view is hoverable.
+ */
+ private boolean isHoverable() {
+ final int viewFlags = mViewFlags;
+ if ((viewFlags & ENABLED_MASK) == DISABLED) {
+ return false;
+ }
+
+ return (viewFlags & CLICKABLE) == CLICKABLE
+ || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE
+ || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+ }
+
+ /**
+ * Returns true if the view is currently hovered.
+ *
+ * @return True if the view is currently hovered.
+ *
+ * @see #setHovered
+ * @see #onHoverChanged
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isHovered() {
+ return (mPrivateFlags & PFLAG_HOVERED) != 0;
+ }
+
+ /**
+ * Sets whether the view is currently hovered.
+ * <p>
+ * Calling this method also changes the drawable state of the view. This
+ * enables the view to react to hover by using different drawable resources
+ * to change its appearance.
+ * </p><p>
+ * The {@link #onHoverChanged} method is called when the hovered state changes.
+ * </p>
+ *
+ * @param hovered True if the view is hovered.
+ *
+ * @see #isHovered
+ * @see #onHoverChanged
+ */
+ public void setHovered(boolean hovered) {
+ if (hovered) {
+ if ((mPrivateFlags & PFLAG_HOVERED) == 0) {
+ mPrivateFlags |= PFLAG_HOVERED;
+ refreshDrawableState();
+ onHoverChanged(true);
+ }
+ } else {
+ if ((mPrivateFlags & PFLAG_HOVERED) != 0) {
+ mPrivateFlags &= ~PFLAG_HOVERED;
+ refreshDrawableState();
+ onHoverChanged(false);
+ }
+ }
+ }
+
+ /**
+ * Implement this method to handle hover state changes.
+ * <p>
+ * This method is called whenever the hover state changes as a result of a
+ * call to {@link #setHovered}.
+ * </p>
+ *
+ * @param hovered The current hover state, as returned by {@link #isHovered}.
+ *
+ * @see #isHovered
+ * @see #setHovered
+ */
+ public void onHoverChanged(boolean hovered) {
+ }
+
+ /**
+ * Handles scroll bar dragging by mouse input.
+ *
+ * @hide
+ * @param event The motion event.
+ *
+ * @return true if the event was handled as a scroll bar dragging, false otherwise.
+ */
+ protected boolean handleScrollBarDragging(MotionEvent event) {
+ if (mScrollCache == null) {
+ return false;
+ }
+ final float x = event.getX();
+ final float y = event.getY();
+ final int action = event.getAction();
+ if ((mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING
+ && action != MotionEvent.ACTION_DOWN)
+ || !event.isFromSource(InputDevice.SOURCE_MOUSE)
+ || !event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
+ mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING;
+ return false;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ if (mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING) {
+ return false;
+ }
+ if (mScrollCache.mScrollBarDraggingState
+ == ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR) {
+ final Rect bounds = mScrollCache.mScrollBarBounds;
+ getVerticalScrollBarBounds(bounds, null);
+ final int range = computeVerticalScrollRange();
+ final int offset = computeVerticalScrollOffset();
+ final int extent = computeVerticalScrollExtent();
+
+ final int thumbLength = ScrollBarUtils.getThumbLength(
+ bounds.height(), bounds.width(), extent, range);
+ final int thumbOffset = ScrollBarUtils.getThumbOffset(
+ bounds.height(), thumbLength, extent, range, offset);
+
+ final float diff = y - mScrollCache.mScrollBarDraggingPos;
+ final float maxThumbOffset = bounds.height() - thumbLength;
+ final float newThumbOffset =
+ Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset);
+ final int height = getHeight();
+ if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0
+ && height > 0 && extent > 0) {
+ final int newY = Math.round((range - extent)
+ / ((float)extent / height) * (newThumbOffset / maxThumbOffset));
+ if (newY != getScrollY()) {
+ mScrollCache.mScrollBarDraggingPos = y;
+ setScrollY(newY);
+ }
+ }
+ return true;
+ }
+ if (mScrollCache.mScrollBarDraggingState
+ == ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR) {
+ final Rect bounds = mScrollCache.mScrollBarBounds;
+ getHorizontalScrollBarBounds(bounds, null);
+ final int range = computeHorizontalScrollRange();
+ final int offset = computeHorizontalScrollOffset();
+ final int extent = computeHorizontalScrollExtent();
+
+ final int thumbLength = ScrollBarUtils.getThumbLength(
+ bounds.width(), bounds.height(), extent, range);
+ final int thumbOffset = ScrollBarUtils.getThumbOffset(
+ bounds.width(), thumbLength, extent, range, offset);
+
+ final float diff = x - mScrollCache.mScrollBarDraggingPos;
+ final float maxThumbOffset = bounds.width() - thumbLength;
+ final float newThumbOffset =
+ Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset);
+ final int width = getWidth();
+ if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0
+ && width > 0 && extent > 0) {
+ final int newX = Math.round((range - extent)
+ / ((float)extent / width) * (newThumbOffset / maxThumbOffset));
+ if (newX != getScrollX()) {
+ mScrollCache.mScrollBarDraggingPos = x;
+ setScrollX(newX);
+ }
+ }
+ return true;
+ }
+ case MotionEvent.ACTION_DOWN:
+ if (mScrollCache.state == ScrollabilityCache.OFF) {
+ return false;
+ }
+ if (isOnVerticalScrollbarThumb(x, y)) {
+ mScrollCache.mScrollBarDraggingState =
+ ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR;
+ mScrollCache.mScrollBarDraggingPos = y;
+ return true;
+ }
+ if (isOnHorizontalScrollbarThumb(x, y)) {
+ mScrollCache.mScrollBarDraggingState =
+ ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR;
+ mScrollCache.mScrollBarDraggingPos = x;
+ return true;
+ }
+ }
+ mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING;
+ return false;
+ }
+
+ /**
+ * Implement this method to handle touch screen motion events.
+ * <p>
+ * If this method is used to detect click actions, it is recommended that
+ * the actions be performed by implementing and calling
+ * {@link #performClick()}. This will ensure consistent system behavior,
+ * including:
+ * <ul>
+ * <li>obeying click sound preferences
+ * <li>dispatching OnClickListener calls
+ * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
+ * accessibility features are enabled
+ * </ul>
+ *
+ * @param event The motion event.
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getY();
+ final int viewFlags = mViewFlags;
+ final int action = event.getAction();
+
+ final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
+ || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+ || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+
+ if ((viewFlags & ENABLED_MASK) == DISABLED) {
+ if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
+ setPressed(false);
+ }
+ mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+ // A disabled view that is clickable still consumes the touch
+ // events, it just doesn't respond to them.
+ return clickable;
+ }
+ if (mTouchDelegate != null) {
+ if (mTouchDelegate.onTouchEvent(event)) {
+ return true;
+ }
+ }
+
+ if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+ if ((viewFlags & TOOLTIP) == TOOLTIP) {
+ handleTooltipUp();
+ }
+ if (!clickable) {
+ removeTapCallback();
+ removeLongPressCallback();
+ mInContextButtonPress = false;
+ mHasPerformedLongPress = false;
+ mIgnoreNextUpEvent = false;
+ break;
+ }
+ boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
+ if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
+ // take focus if we don't have it already and we should in
+ // touch mode.
+ boolean focusTaken = false;
+ if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
+ focusTaken = requestFocus();
+ }
+
+ if (prepressed) {
+ // The button is being released before we actually
+ // showed it as pressed. Make it show the pressed
+ // state now (before scheduling the click) to ensure
+ // the user sees it.
+ setPressed(true, x, y);
+ }
+
+ if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
+ // This is a tap, so remove the longpress check
+ removeLongPressCallback();
+
+ // Only perform take click actions if we were in the pressed state
+ if (!focusTaken) {
+ // Use a Runnable and post this rather than calling
+ // performClick directly. This lets other visual state
+ // of the view update before click actions start.
+ if (mPerformClick == null) {
+ mPerformClick = new PerformClick();
+ }
+ if (!post(mPerformClick)) {
+ performClick();
+ }
+ }
+ }
+
+ if (mUnsetPressedState == null) {
+ mUnsetPressedState = new UnsetPressedState();
+ }
+
+ if (prepressed) {
+ postDelayed(mUnsetPressedState,
+ ViewConfiguration.getPressedStateDuration());
+ } else if (!post(mUnsetPressedState)) {
+ // If the post failed, unpress right now
+ mUnsetPressedState.run();
+ }
+
+ removeTapCallback();
+ }
+ mIgnoreNextUpEvent = false;
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
+ mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
+ }
+ mHasPerformedLongPress = false;
+
+ if (!clickable) {
+ checkForLongClick(0, x, y);
+ break;
+ }
+
+ if (performButtonActionOnTouchDown(event)) {
+ break;
+ }
+
+ // Walk up the hierarchy to determine if we're inside a scrolling container.
+ boolean isInScrollingContainer = isInScrollingContainer();
+
+ // For views inside a scrolling container, delay the pressed feedback for
+ // a short period in case this is a scroll.
+ if (isInScrollingContainer) {
+ mPrivateFlags |= PFLAG_PREPRESSED;
+ if (mPendingCheckForTap == null) {
+ mPendingCheckForTap = new CheckForTap();
+ }
+ mPendingCheckForTap.x = event.getX();
+ mPendingCheckForTap.y = event.getY();
+ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+ } else {
+ // Not inside a scrolling container, so show the feedback right away
+ setPressed(true, x, y);
+ checkForLongClick(0, x, y);
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (clickable) {
+ setPressed(false);
+ }
+ removeTapCallback();
+ removeLongPressCallback();
+ mInContextButtonPress = false;
+ mHasPerformedLongPress = false;
+ mIgnoreNextUpEvent = false;
+ mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (clickable) {
+ drawableHotspotChanged(x, y);
+ }
+
+ // Be lenient about moving outside of buttons
+ if (!pointInView(x, y, mTouchSlop)) {
+ // Outside button
+ // Remove any future long press/tap checks
+ removeTapCallback();
+ removeLongPressCallback();
+ if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
+ setPressed(false);
+ }
+ mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isInScrollingContainer() {
+ ViewParent p = getParent();
+ while (p != null && p instanceof ViewGroup) {
+ if (((ViewGroup) p).shouldDelayChildPressedState()) {
+ return true;
+ }
+ p = p.getParent();
+ }
+ return false;
+ }
+
+ /**
+ * Remove the longpress detection timer.
+ */
+ private void removeLongPressCallback() {
+ if (mPendingCheckForLongPress != null) {
+ removeCallbacks(mPendingCheckForLongPress);
+ }
+ }
+
+ /**
+ * Remove the pending click action
+ */
+ private void removePerformClickCallback() {
+ if (mPerformClick != null) {
+ removeCallbacks(mPerformClick);
+ }
+ }
+
+ /**
+ * Remove the prepress detection timer.
+ */
+ private void removeUnsetPressCallback() {
+ if ((mPrivateFlags & PFLAG_PRESSED) != 0 && mUnsetPressedState != null) {
+ setPressed(false);
+ removeCallbacks(mUnsetPressedState);
+ }
+ }
+
+ /**
+ * Remove the tap detection timer.
+ */
+ private void removeTapCallback() {
+ if (mPendingCheckForTap != null) {
+ mPrivateFlags &= ~PFLAG_PREPRESSED;
+ removeCallbacks(mPendingCheckForTap);
+ }
+ }
+
+ /**
+ * Cancels a pending long press. Your subclass can use this if you
+ * want the context menu to come up if the user presses and holds
+ * at the same place, but you don't want it to come up if they press
+ * and then move around enough to cause scrolling.
+ */
+ public void cancelLongPress() {
+ removeLongPressCallback();
+
+ /*
+ * The prepressed state handled by the tap callback is a display
+ * construct, but the tap callback will post a long press callback
+ * less its own timeout. Remove it here.
+ */
+ removeTapCallback();
+ }
+
+ /**
+ * Sets the TouchDelegate for this View.
+ */
+ public void setTouchDelegate(TouchDelegate delegate) {
+ mTouchDelegate = delegate;
+ }
+
+ /**
+ * Gets the TouchDelegate for this View.
+ */
+ public TouchDelegate getTouchDelegate() {
+ return mTouchDelegate;
+ }
+
+ /**
+ * Request unbuffered dispatch of the given stream of MotionEvents to this View.
+ *
+ * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input
+ * system not batch {@link MotionEvent}s but instead deliver them as soon as they're
+ * available. This method should only be called for touch events.
+ *
+ * <p class="note">This api is not intended for most applications. Buffered dispatch
+ * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent
+ * streams will not improve your input latency. Side effects include: increased latency,
+ * jittery scrolls and inability to take advantage of system resampling. Talk to your input
+ * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for
+ * you.</p>
+ */
+ public final void requestUnbufferedDispatch(MotionEvent event) {
+ final int action = event.getAction();
+ if (mAttachInfo == null
+ || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE
+ || !event.isTouchEvent()) {
+ return;
+ }
+ mAttachInfo.mUnbufferedDispatchRequested = true;
+ }
+
+ /**
+ * Set flags controlling behavior of this view.
+ *
+ * @param flags Constant indicating the value which should be set
+ * @param mask Constant indicating the bit range that should be changed
+ */
+ void setFlags(int flags, int mask) {
+ final boolean accessibilityEnabled =
+ AccessibilityManager.getInstance(mContext).isEnabled();
+ final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();
+
+ int old = mViewFlags;
+ mViewFlags = (mViewFlags & ~mask) | (flags & mask);
+
+ int changed = mViewFlags ^ old;
+ if (changed == 0) {
+ return;
+ }
+ int privateFlags = mPrivateFlags;
+
+ // If focusable is auto, update the FOCUSABLE bit.
+ int focusableChangedByAuto = 0;
+ if (((mViewFlags & FOCUSABLE_AUTO) != 0)
+ && (changed & (FOCUSABLE_MASK | CLICKABLE)) != 0) {
+ // Heuristic only takes into account whether view is clickable.
+ final int newFocus;
+ if ((mViewFlags & CLICKABLE) != 0) {
+ newFocus = FOCUSABLE;
+ } else {
+ newFocus = NOT_FOCUSABLE;
+ }
+ mViewFlags = (mViewFlags & ~FOCUSABLE) | newFocus;
+ focusableChangedByAuto = (old & FOCUSABLE) ^ (newFocus & FOCUSABLE);
+ changed = (changed & ~FOCUSABLE) | focusableChangedByAuto;
+ }
+
+ /* Check if the FOCUSABLE bit has changed */
+ if (((changed & FOCUSABLE) != 0) && ((privateFlags & PFLAG_HAS_BOUNDS) != 0)) {
+ if (((old & FOCUSABLE) == FOCUSABLE)
+ && ((privateFlags & PFLAG_FOCUSED) != 0)) {
+ /* Give up focus if we are no longer focusable */
+ clearFocus();
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).clearFocusedInCluster();
+ }
+ } else if (((old & FOCUSABLE) == NOT_FOCUSABLE)
+ && ((privateFlags & PFLAG_FOCUSED) == 0)) {
+ /*
+ * Tell the view system that we are now available to take focus
+ * if no one else already has it.
+ */
+ if (mParent != null) {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (!sAutoFocusableOffUIThreadWontNotifyParents
+ || focusableChangedByAuto == 0
+ || viewRootImpl == null
+ || viewRootImpl.mThread == Thread.currentThread()) {
+ mParent.focusableViewAvailable(this);
+ }
+ }
+ }
+ }
+
+ final int newVisibility = flags & VISIBILITY_MASK;
+ if (newVisibility == VISIBLE) {
+ if ((changed & VISIBILITY_MASK) != 0) {
+ /*
+ * If this view is becoming visible, invalidate it in case it changed while
+ * it was not visible. Marking it drawn ensures that the invalidation will
+ * go through.
+ */
+ mPrivateFlags |= PFLAG_DRAWN;
+ invalidate(true);
+
+ needGlobalAttributesUpdate(true);
+
+ // a view becoming visible is worth notifying the parent
+ // about in case nothing has focus. even if this specific view
+ // isn't focusable, it may contain something that is, so let
+ // the root view try to give this focus if nothing else does.
+ if ((mParent != null) && ((mViewFlags & ENABLED_MASK) == ENABLED)
+ && (mBottom > mTop) && (mRight > mLeft)) {
+ mParent.focusableViewAvailable(this);
+ }
+ }
+ }
+
+ if ((changed & ENABLED_MASK) != 0) {
+ if ((mViewFlags & ENABLED_MASK) == ENABLED) {
+ // a view becoming enabled should notify the parent as long as the view is also
+ // visible and the parent wasn't already notified by becoming visible during this
+ // setFlags invocation.
+ if ((mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && ((changed & VISIBILITY_MASK) == 0)) {
+ if ((mParent != null) && (mViewFlags & ENABLED_MASK) == ENABLED) {
+ mParent.focusableViewAvailable(this);
+ }
+ }
+ } else {
+ if (hasFocus()) clearFocus();
+ }
+ }
+
+ /* Check if the GONE bit has changed */
+ if ((changed & GONE) != 0) {
+ needGlobalAttributesUpdate(false);
+ requestLayout();
+
+ if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
+ if (hasFocus()) {
+ clearFocus();
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).clearFocusedInCluster();
+ }
+ }
+ clearAccessibilityFocus();
+ destroyDrawingCache();
+ if (mParent instanceof View) {
+ // GONE views noop invalidation, so invalidate the parent
+ ((View) mParent).invalidate(true);
+ }
+ // Mark the view drawn to ensure that it gets invalidated properly the next
+ // time it is visible and gets invalidated
+ mPrivateFlags |= PFLAG_DRAWN;
+ }
+ if (mAttachInfo != null) {
+ mAttachInfo.mViewVisibilityChanged = true;
+ }
+ }
+
+ /* Check if the VISIBLE bit has changed */
+ if ((changed & INVISIBLE) != 0) {
+ needGlobalAttributesUpdate(false);
+ /*
+ * If this view is becoming invisible, set the DRAWN flag so that
+ * the next invalidate() will not be skipped.
+ */
+ mPrivateFlags |= PFLAG_DRAWN;
+
+ if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {
+ // root view becoming invisible shouldn't clear focus and accessibility focus
+ if (getRootView() != this) {
+ if (hasFocus()) {
+ clearFocus();
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).clearFocusedInCluster();
+ }
+ }
+ clearAccessibilityFocus();
+ }
+ }
+ if (mAttachInfo != null) {
+ mAttachInfo.mViewVisibilityChanged = true;
+ }
+ }
+
+ if ((changed & VISIBILITY_MASK) != 0) {
+ // If the view is invisible, cleanup its display list to free up resources
+ if (newVisibility != VISIBLE && mAttachInfo != null) {
+ cleanupDraw();
+ }
+
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).onChildVisibilityChanged(this,
+ (changed & VISIBILITY_MASK), newVisibility);
+ ((View) mParent).invalidate(true);
+ } else if (mParent != null) {
+ mParent.invalidateChild(this, null);
+ }
+
+ if (mAttachInfo != null) {
+ dispatchVisibilityChanged(this, newVisibility);
+
+ // Aggregated visibility changes are dispatched to attached views
+ // in visible windows where the parent is currently shown/drawn
+ // or the parent is not a ViewGroup (and therefore assumed to be a ViewRoot),
+ // discounting clipping or overlapping. This makes it a good place
+ // to change animation states.
+ if (mParent != null && getWindowVisibility() == VISIBLE &&
+ ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
+ dispatchVisibilityAggregated(newVisibility == VISIBLE);
+ }
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
+ destroyDrawingCache();
+ }
+
+ if ((changed & DRAWING_CACHE_ENABLED) != 0) {
+ destroyDrawingCache();
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ invalidateParentCaches();
+ }
+
+ if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
+ destroyDrawingCache();
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ }
+
+ if ((changed & DRAW_MASK) != 0) {
+ if ((mViewFlags & WILL_NOT_DRAW) != 0) {
+ if (mBackground != null
+ || mDefaultFocusHighlight != null
+ || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ } else {
+ mPrivateFlags |= PFLAG_SKIP_DRAW;
+ }
+ } else {
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ }
+ requestLayout();
+ invalidate(true);
+ }
+
+ if ((changed & KEEP_SCREEN_ON) != 0) {
+ if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+ mParent.recomputeViewAttributes(this);
+ }
+ }
+
+ if (accessibilityEnabled) {
+ if ((changed & FOCUSABLE) != 0 || (changed & VISIBILITY_MASK) != 0
+ || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
+ || (changed & CONTEXT_CLICKABLE) != 0) {
+ if (oldIncludeForAccessibility != includeForAccessibility()) {
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ } else {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ } else if ((changed & ENABLED_MASK) != 0) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+ }
+
+ /**
+ * Change the view's z order in the tree, so it's on top of other sibling
+ * views. This ordering change may affect layout, if the parent container
+ * uses an order-dependent layout scheme (e.g., LinearLayout). Prior
+ * to {@link android.os.Build.VERSION_CODES#KITKAT} this
+ * method should be followed by calls to {@link #requestLayout()} and
+ * {@link View#invalidate()} on the view's parent to force the parent to redraw
+ * with the new child ordering.
+ *
+ * @see ViewGroup#bringChildToFront(View)
+ */
+ public void bringToFront() {
+ if (mParent != null) {
+ mParent.bringChildToFront(this);
+ }
+ }
+
+ /**
+ * This is called in response to an internal scroll in this view (i.e., the
+ * view scrolled its own contents). This is typically as a result of
+ * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
+ * called.
+ *
+ * @param l Current horizontal scroll origin.
+ * @param t Current vertical scroll origin.
+ * @param oldl Previous horizontal scroll origin.
+ * @param oldt Previous vertical scroll origin.
+ */
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
+ }
+
+ mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (mForegroundInfo != null) {
+ mForegroundInfo.mBoundsChanged = true;
+ }
+
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ ai.mViewScrollChanged = true;
+ }
+
+ if (mListenerInfo != null && mListenerInfo.mOnScrollChangeListener != null) {
+ mListenerInfo.mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
+ }
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the scroll
+ * X or Y positions of a view change.
+ * <p>
+ * <b>Note:</b> Some views handle scrolling independently from View and may
+ * have their own separate listeners for scroll-type events. For example,
+ * {@link android.widget.ListView ListView} allows clients to register an
+ * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+ * to listen for changes in list scroll position.
+ *
+ * @see #setOnScrollChangeListener(View.OnScrollChangeListener)
+ */
+ public interface OnScrollChangeListener {
+ /**
+ * Called when the scroll position of a view changes.
+ *
+ * @param v The view whose scroll position has changed.
+ * @param scrollX Current horizontal scroll origin.
+ * @param scrollY Current vertical scroll origin.
+ * @param oldScrollX Previous horizontal scroll origin.
+ * @param oldScrollY Previous vertical scroll origin.
+ */
+ void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the layout bounds of a view
+ * changes due to layout processing.
+ */
+ public interface OnLayoutChangeListener {
+ /**
+ * Called when the layout bounds of a view changes due to layout processing.
+ *
+ * @param v The view whose bounds have changed.
+ * @param left The new value of the view's left property.
+ * @param top The new value of the view's top property.
+ * @param right The new value of the view's right property.
+ * @param bottom The new value of the view's bottom property.
+ * @param oldLeft The previous value of the view's left property.
+ * @param oldTop The previous value of the view's top property.
+ * @param oldRight The previous value of the view's right property.
+ * @param oldBottom The previous value of the view's bottom property.
+ */
+ void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom);
+ }
+
+ /**
+ * This is called during layout when the size of this view has changed. If
+ * you were just added to the view hierarchy, you're called with the old
+ * values of 0.
+ *
+ * @param w Current width of this view.
+ * @param h Current height of this view.
+ * @param oldw Old width of this view.
+ * @param oldh Old height of this view.
+ */
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ }
+
+ /**
+ * Called by draw to draw the child views. This may be overridden
+ * by derived classes to gain control just before its children are drawn
+ * (but after its own view has been drawn).
+ * @param canvas the canvas on which to draw the view
+ */
+ protected void dispatchDraw(Canvas canvas) {
+
+ }
+
+ /**
+ * Gets the parent of this view. Note that the parent is a
+ * ViewParent and not necessarily a View.
+ *
+ * @return Parent of this view.
+ */
+ public final ViewParent getParent() {
+ return mParent;
+ }
+
+ /**
+ * Set the horizontal scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param value the x position to scroll to
+ */
+ public void setScrollX(int value) {
+ scrollTo(value, mScrollY);
+ }
+
+ /**
+ * Set the vertical scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param value the y position to scroll to
+ */
+ public void setScrollY(int value) {
+ scrollTo(mScrollX, value);
+ }
+
+ /**
+ * Return the scrolled left position of this view. This is the left edge of
+ * the displayed part of your view. You do not need to draw any pixels
+ * farther left, since those are outside of the frame of your view on
+ * screen.
+ *
+ * @return The left edge of the displayed part of your view, in pixels.
+ */
+ public final int getScrollX() {
+ return mScrollX;
+ }
+
+ /**
+ * Return the scrolled top position of this view. This is the top edge of
+ * the displayed part of your view. You do not need to draw any pixels above
+ * it, since those are outside of the frame of your view on screen.
+ *
+ * @return The top edge of the displayed part of your view, in pixels.
+ */
+ public final int getScrollY() {
+ return mScrollY;
+ }
+
+ /**
+ * Return the width of the your view.
+ *
+ * @return The width of your view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public final int getWidth() {
+ return mRight - mLeft;
+ }
+
+ /**
+ * Return the height of your view.
+ *
+ * @return The height of your view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public final int getHeight() {
+ return mBottom - mTop;
+ }
+
+ /**
+ * Return the visible drawing bounds of your view. Fills in the output
+ * rectangle with the values from getScrollX(), getScrollY(),
+ * getWidth(), and getHeight(). These bounds do not account for any
+ * transformation properties currently set on the view, such as
+ * {@link #setScaleX(float)} or {@link #setRotation(float)}.
+ *
+ * @param outRect The (scrolled) drawing bounds of the view.
+ */
+ public void getDrawingRect(Rect outRect) {
+ outRect.left = mScrollX;
+ outRect.top = mScrollY;
+ outRect.right = mScrollX + (mRight - mLeft);
+ outRect.bottom = mScrollY + (mBottom - mTop);
+ }
+
+ /**
+ * Like {@link #getMeasuredWidthAndState()}, but only returns the
+ * raw width component (that is the result is masked by
+ * {@link #MEASURED_SIZE_MASK}).
+ *
+ * @return The raw measured width of this view.
+ */
+ public final int getMeasuredWidth() {
+ return mMeasuredWidth & MEASURED_SIZE_MASK;
+ }
+
+ /**
+ * Return the full width measurement information for this view as computed
+ * by the most recent call to {@link #measure(int, int)}. This result is a bit mask
+ * as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
+ * This should be used during measurement and layout calculations only. Use
+ * {@link #getWidth()} to see how wide a view is after layout.
+ *
+ * @return The measured width of this view as a bit mask.
+ */
+ @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+ @ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
+ name = "MEASURED_STATE_TOO_SMALL"),
+ })
+ public final int getMeasuredWidthAndState() {
+ return mMeasuredWidth;
+ }
+
+ /**
+ * Like {@link #getMeasuredHeightAndState()}, but only returns the
+ * raw height component (that is the result is masked by
+ * {@link #MEASURED_SIZE_MASK}).
+ *
+ * @return The raw measured height of this view.
+ */
+ public final int getMeasuredHeight() {
+ return mMeasuredHeight & MEASURED_SIZE_MASK;
+ }
+
+ /**
+ * Return the full height measurement information for this view as computed
+ * by the most recent call to {@link #measure(int, int)}. This result is a bit mask
+ * as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
+ * This should be used during measurement and layout calculations only. Use
+ * {@link #getHeight()} to see how wide a view is after layout.
+ *
+ * @return The measured height of this view as a bit mask.
+ */
+ @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+ @ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
+ name = "MEASURED_STATE_TOO_SMALL"),
+ })
+ public final int getMeasuredHeightAndState() {
+ return mMeasuredHeight;
+ }
+
+ /**
+ * Return only the state bits of {@link #getMeasuredWidthAndState()}
+ * and {@link #getMeasuredHeightAndState()}, combined into one integer.
+ * The width component is in the regular bits {@link #MEASURED_STATE_MASK}
+ * and the height component is at the shifted bits
+ * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
+ */
+ public final int getMeasuredState() {
+ return (mMeasuredWidth&MEASURED_STATE_MASK)
+ | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
+ & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
+ }
+
+ /**
+ * The transform matrix of this view, which is calculated based on the current
+ * rotation, scale, and pivot properties.
+ *
+ * @see #getRotation()
+ * @see #getScaleX()
+ * @see #getScaleY()
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @return The current transform matrix for the view
+ */
+ public Matrix getMatrix() {
+ ensureTransformationInfo();
+ final Matrix matrix = mTransformationInfo.mMatrix;
+ mRenderNode.getMatrix(matrix);
+ return matrix;
+ }
+
+ /**
+ * Returns true if the transform matrix is the identity matrix.
+ * Recomputes the matrix if necessary.
+ *
+ * @return True if the transform matrix is the identity matrix, false otherwise.
+ */
+ final boolean hasIdentityMatrix() {
+ return mRenderNode.hasIdentityMatrix();
+ }
+
+ void ensureTransformationInfo() {
+ if (mTransformationInfo == null) {
+ mTransformationInfo = new TransformationInfo();
+ }
+ }
+
+ /**
+ * Utility method to retrieve the inverse of the current mMatrix property.
+ * We cache the matrix to avoid recalculating it when transform properties
+ * have not changed.
+ *
+ * @return The inverse of the current matrix of this view.
+ * @hide
+ */
+ public final Matrix getInverseMatrix() {
+ ensureTransformationInfo();
+ if (mTransformationInfo.mInverseMatrix == null) {
+ mTransformationInfo.mInverseMatrix = new Matrix();
+ }
+ final Matrix matrix = mTransformationInfo.mInverseMatrix;
+ mRenderNode.getInverseMatrix(matrix);
+ return matrix;
+ }
+
+ /**
+ * Gets the distance along the Z axis from the camera to this view.
+ *
+ * @see #setCameraDistance(float)
+ *
+ * @return The distance along the Z axis.
+ */
+ public float getCameraDistance() {
+ final float dpi = mResources.getDisplayMetrics().densityDpi;
+ return -(mRenderNode.getCameraDistance() * dpi);
+ }
+
+ /**
+ * <p>Sets the distance along the Z axis (orthogonal to the X/Y plane on which
+ * views are drawn) from the camera to this view. The camera's distance
+ * affects 3D transformations, for instance rotations around the X and Y
+ * axis. If the rotationX or rotationY properties are changed and this view is
+ * large (more than half the size of the screen), it is recommended to always
+ * use a camera distance that's greater than the height (X axis rotation) or
+ * the width (Y axis rotation) of this view.</p>
+ *
+ * <p>The distance of the camera from the view plane can have an affect on the
+ * perspective distortion of the view when it is rotated around the x or y axis.
+ * For example, a large distance will result in a large viewing angle, and there
+ * will not be much perspective distortion of the view as it rotates. A short
+ * distance may cause much more perspective distortion upon rotation, and can
+ * also result in some drawing artifacts if the rotated view ends up partially
+ * behind the camera (which is why the recommendation is to use a distance at
+ * least as far as the size of the view, if the view is to be rotated.)</p>
+ *
+ * <p>The distance is expressed in "depth pixels." The default distance depends
+ * on the screen density. For instance, on a medium density display, the
+ * default distance is 1280. On a high density display, the default distance
+ * is 1920.</p>
+ *
+ * <p>If you want to specify a distance that leads to visually consistent
+ * results across various densities, use the following formula:</p>
+ * <pre>
+ * float scale = context.getResources().getDisplayMetrics().density;
+ * view.setCameraDistance(distance * scale);
+ * </pre>
+ *
+ * <p>The density scale factor of a high density display is 1.5,
+ * and 1920 = 1280 * 1.5.</p>
+ *
+ * @param distance The distance in "depth pixels", if negative the opposite
+ * value is used
+ *
+ * @see #setRotationX(float)
+ * @see #setRotationY(float)
+ */
+ public void setCameraDistance(float distance) {
+ final float dpi = mResources.getDisplayMetrics().densityDpi;
+
+ invalidateViewProperty(true, false);
+ mRenderNode.setCameraDistance(-Math.abs(distance) / dpi);
+ invalidateViewProperty(false, false);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+
+ /**
+ * The degrees that the view is rotated around the pivot point.
+ *
+ * @see #setRotation(float)
+ * @see #getPivotX()
+ * @see #getPivotY()
+ *
+ * @return The degrees of rotation.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getRotation() {
+ return mRenderNode.getRotation();
+ }
+
+ /**
+ * Sets the degrees that the view is rotated around the pivot point. Increasing values
+ * result in clockwise rotation.
+ *
+ * @param rotation The degrees of rotation.
+ *
+ * @see #getRotation()
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @see #setRotationX(float)
+ * @see #setRotationY(float)
+ *
+ * @attr ref android.R.styleable#View_rotation
+ */
+ public void setRotation(float rotation) {
+ if (rotation != getRotation()) {
+ // Double-invalidation is necessary to capture view's old and new areas
+ invalidateViewProperty(true, false);
+ mRenderNode.setRotation(rotation);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * The degrees that the view is rotated around the vertical axis through the pivot point.
+ *
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @see #setRotationY(float)
+ *
+ * @return The degrees of Y rotation.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getRotationY() {
+ return mRenderNode.getRotationY();
+ }
+
+ /**
+ * Sets the degrees that the view is rotated around the vertical axis through the pivot point.
+ * Increasing values result in counter-clockwise rotation from the viewpoint of looking
+ * down the y axis.
+ *
+ * When rotating large views, it is recommended to adjust the camera distance
+ * accordingly. Refer to {@link #setCameraDistance(float)} for more information.
+ *
+ * @param rotationY The degrees of Y rotation.
+ *
+ * @see #getRotationY()
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @see #setRotation(float)
+ * @see #setRotationX(float)
+ * @see #setCameraDistance(float)
+ *
+ * @attr ref android.R.styleable#View_rotationY
+ */
+ public void setRotationY(float rotationY) {
+ if (rotationY != getRotationY()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setRotationY(rotationY);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * The degrees that the view is rotated around the horizontal axis through the pivot point.
+ *
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @see #setRotationX(float)
+ *
+ * @return The degrees of X rotation.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getRotationX() {
+ return mRenderNode.getRotationX();
+ }
+
+ /**
+ * Sets the degrees that the view is rotated around the horizontal axis through the pivot point.
+ * Increasing values result in clockwise rotation from the viewpoint of looking down the
+ * x axis.
+ *
+ * When rotating large views, it is recommended to adjust the camera distance
+ * accordingly. Refer to {@link #setCameraDistance(float)} for more information.
+ *
+ * @param rotationX The degrees of X rotation.
+ *
+ * @see #getRotationX()
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @see #setRotation(float)
+ * @see #setRotationY(float)
+ * @see #setCameraDistance(float)
+ *
+ * @attr ref android.R.styleable#View_rotationX
+ */
+ public void setRotationX(float rotationX) {
+ if (rotationX != getRotationX()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setRotationX(rotationX);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * The amount that the view is scaled in x around the pivot point, as a proportion of
+ * the view's unscaled width. A value of 1, the default, means that no scaling is applied.
+ *
+ * <p>By default, this is 1.0f.
+ *
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @return The scaling factor.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getScaleX() {
+ return mRenderNode.getScaleX();
+ }
+
+ /**
+ * Sets the amount that the view is scaled in x around the pivot point, as a proportion of
+ * the view's unscaled width. A value of 1 means that no scaling is applied.
+ *
+ * @param scaleX The scaling factor.
+ * @see #getPivotX()
+ * @see #getPivotY()
+ *
+ * @attr ref android.R.styleable#View_scaleX
+ */
+ public void setScaleX(float scaleX) {
+ if (scaleX != getScaleX()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setScaleX(scaleX);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * The amount that the view is scaled in y around the pivot point, as a proportion of
+ * the view's unscaled height. A value of 1, the default, means that no scaling is applied.
+ *
+ * <p>By default, this is 1.0f.
+ *
+ * @see #getPivotX()
+ * @see #getPivotY()
+ * @return The scaling factor.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getScaleY() {
+ return mRenderNode.getScaleY();
+ }
+
+ /**
+ * Sets the amount that the view is scaled in Y around the pivot point, as a proportion of
+ * the view's unscaled width. A value of 1 means that no scaling is applied.
+ *
+ * @param scaleY The scaling factor.
+ * @see #getPivotX()
+ * @see #getPivotY()
+ *
+ * @attr ref android.R.styleable#View_scaleY
+ */
+ public void setScaleY(float scaleY) {
+ if (scaleY != getScaleY()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setScaleY(scaleY);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * The x location of the point around which the view is {@link #setRotation(float) rotated}
+ * and {@link #setScaleX(float) scaled}.
+ *
+ * @see #getRotation()
+ * @see #getScaleX()
+ * @see #getScaleY()
+ * @see #getPivotY()
+ * @return The x location of the pivot point.
+ *
+ * @attr ref android.R.styleable#View_transformPivotX
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getPivotX() {
+ return mRenderNode.getPivotX();
+ }
+
+ /**
+ * Sets the x location of the point around which the view is
+ * {@link #setRotation(float) rotated} and {@link #setScaleX(float) scaled}.
+ * By default, the pivot point is centered on the object.
+ * Setting this property disables this behavior and causes the view to use only the
+ * explicitly set pivotX and pivotY values.
+ *
+ * @param pivotX The x location of the pivot point.
+ * @see #getRotation()
+ * @see #getScaleX()
+ * @see #getScaleY()
+ * @see #getPivotY()
+ *
+ * @attr ref android.R.styleable#View_transformPivotX
+ */
+ public void setPivotX(float pivotX) {
+ if (!mRenderNode.isPivotExplicitlySet() || pivotX != getPivotX()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setPivotX(pivotX);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
+
+ /**
+ * The y location of the point around which the view is {@link #setRotation(float) rotated}
+ * and {@link #setScaleY(float) scaled}.
+ *
+ * @see #getRotation()
+ * @see #getScaleX()
+ * @see #getScaleY()
+ * @see #getPivotY()
+ * @return The y location of the pivot point.
+ *
+ * @attr ref android.R.styleable#View_transformPivotY
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getPivotY() {
+ return mRenderNode.getPivotY();
+ }
+
+ /**
+ * Sets the y location of the point around which the view is {@link #setRotation(float) rotated}
+ * and {@link #setScaleY(float) scaled}. By default, the pivot point is centered on the object.
+ * Setting this property disables this behavior and causes the view to use only the
+ * explicitly set pivotX and pivotY values.
+ *
+ * @param pivotY The y location of the pivot point.
+ * @see #getRotation()
+ * @see #getScaleX()
+ * @see #getScaleY()
+ * @see #getPivotY()
+ *
+ * @attr ref android.R.styleable#View_transformPivotY
+ */
+ public void setPivotY(float pivotY) {
+ if (!mRenderNode.isPivotExplicitlySet() || pivotY != getPivotY()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setPivotY(pivotY);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
+
+ /**
+ * The opacity of the view. This is a value from 0 to 1, where 0 means the view is
+ * completely transparent and 1 means the view is completely opaque.
+ *
+ * <p>By default this is 1.0f.
+ * @return The opacity of the view.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getAlpha() {
+ return mTransformationInfo != null ? mTransformationInfo.mAlpha : 1;
+ }
+
+ /**
+ * Sets the behavior for overlapping rendering for this view (see {@link
+ * #hasOverlappingRendering()} for more details on this behavior). Calling this method
+ * is an alternative to overriding {@link #hasOverlappingRendering()} in a subclass,
+ * providing the value which is then used internally. That is, when {@link
+ * #forceHasOverlappingRendering(boolean)} is called, the value of {@link
+ * #hasOverlappingRendering()} is ignored and the value passed into this method is used
+ * instead.
+ *
+ * @param hasOverlappingRendering The value for overlapping rendering to be used internally
+ * instead of that returned by {@link #hasOverlappingRendering()}.
+ *
+ * @attr ref android.R.styleable#View_forceHasOverlappingRendering
+ */
+ public void forceHasOverlappingRendering(boolean hasOverlappingRendering) {
+ mPrivateFlags3 |= PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED;
+ if (hasOverlappingRendering) {
+ mPrivateFlags3 |= PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE;
+ }
+ }
+
+ /**
+ * Returns the value for overlapping rendering that is used internally. This is either
+ * the value passed into {@link #forceHasOverlappingRendering(boolean)}, if called, or
+ * the return value of {@link #hasOverlappingRendering()}, otherwise.
+ *
+ * @return The value for overlapping rendering being used internally.
+ */
+ public final boolean getHasOverlappingRendering() {
+ return (mPrivateFlags3 & PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED) != 0 ?
+ (mPrivateFlags3 & PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE) != 0 :
+ hasOverlappingRendering();
+ }
+
+ /**
+ * Returns whether this View has content which overlaps.
+ *
+ * <p>This function, intended to be overridden by specific View types, is an optimization when
+ * alpha is set on a view. If rendering overlaps in a view with alpha < 1, that view is drawn to
+ * an offscreen buffer and then composited into place, which can be expensive. If the view has
+ * no overlapping rendering, the view can draw each primitive with the appropriate alpha value
+ * directly. An example of overlapping rendering is a TextView with a background image, such as
+ * a Button. An example of non-overlapping rendering is a TextView with no background, or an
+ * ImageView with only the foreground image. The default implementation returns true; subclasses
+ * should override if they have cases which can be optimized.</p>
+ *
+ * <p>The current implementation of the saveLayer and saveLayerAlpha methods in {@link Canvas}
+ * necessitates that a View return true if it uses the methods internally without passing the
+ * {@link Canvas#CLIP_TO_LAYER_SAVE_FLAG}.</p>
+ *
+ * <p><strong>Note:</strong> The return value of this method is ignored if {@link
+ * #forceHasOverlappingRendering(boolean)} has been called on this view.</p>
+ *
+ * @return true if the content in this view might overlap, false otherwise.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean hasOverlappingRendering() {
+ return true;
+ }
+
+ /**
+ * Sets the opacity of the view to a value from 0 to 1, where 0 means the view is
+ * completely transparent and 1 means the view is completely opaque.
+ *
+ * <p class="note"><strong>Note:</strong> setting alpha to a translucent value (0 < alpha < 1)
+ * can have significant performance implications, especially for large views. It is best to use
+ * the alpha property sparingly and transiently, as in the case of fading animations.</p>
+ *
+ * <p>For a view with a frequently changing alpha, such as during a fading animation, it is
+ * strongly recommended for performance reasons to either override
+ * {@link #hasOverlappingRendering()} to return <code>false</code> if appropriate, or setting a
+ * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view for the duration
+ * of the animation. On versions {@link android.os.Build.VERSION_CODES#M} and below,
+ * the default path for rendering an unlayered View with alpha could add multiple milliseconds
+ * of rendering cost, even for simple or small views. Starting with
+ * {@link android.os.Build.VERSION_CODES#M}, {@link #LAYER_TYPE_HARDWARE} is automatically
+ * applied to the view at the rendering level.</p>
+ *
+ * <p>If this view overrides {@link #onSetAlpha(int)} to return true, then this view is
+ * responsible for applying the opacity itself.</p>
+ *
+ * <p>On versions {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and below, note that if
+ * the view is backed by a {@link #setLayerType(int, android.graphics.Paint) layer} and is
+ * associated with a {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an
+ * alpha value less than 1.0 will supersede the alpha of the layer paint.</p>
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#M}, setting a translucent alpha
+ * value will clip a View to its bounds, unless the View returns <code>false</code> from
+ * {@link #hasOverlappingRendering}.</p>
+ *
+ * @param alpha The opacity of the view.
+ *
+ * @see #hasOverlappingRendering()
+ * @see #setLayerType(int, android.graphics.Paint)
+ *
+ * @attr ref android.R.styleable#View_alpha
+ */
+ public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
+ ensureTransformationInfo();
+ if (mTransformationInfo.mAlpha != alpha) {
+ // Report visibility changes, which can affect children, to accessibility
+ if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) {
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ mTransformationInfo.mAlpha = alpha;
+ if (onSetAlpha((int) (alpha * 255))) {
+ mPrivateFlags |= PFLAG_ALPHA_SET;
+ // subclass is handling alpha - don't optimize rendering cache invalidation
+ invalidateParentCaches();
+ invalidate(true);
+ } else {
+ mPrivateFlags &= ~PFLAG_ALPHA_SET;
+ invalidateViewProperty(true, false);
+ mRenderNode.setAlpha(getFinalAlpha());
+ }
+ }
+ }
+
+ /**
+ * Faster version of setAlpha() which performs the same steps except there are
+ * no calls to invalidate(). The caller of this function should perform proper invalidation
+ * on the parent and this object. The return value indicates whether the subclass handles
+ * alpha (the return value for onSetAlpha()).
+ *
+ * @param alpha The new value for the alpha property
+ * @return true if the View subclass handles alpha (the return value for onSetAlpha()) and
+ * the new value for the alpha property is different from the old value
+ */
+ boolean setAlphaNoInvalidation(float alpha) {
+ ensureTransformationInfo();
+ if (mTransformationInfo.mAlpha != alpha) {
+ mTransformationInfo.mAlpha = alpha;
+ boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255));
+ if (subclassHandlesAlpha) {
+ mPrivateFlags |= PFLAG_ALPHA_SET;
+ return true;
+ } else {
+ mPrivateFlags &= ~PFLAG_ALPHA_SET;
+ mRenderNode.setAlpha(getFinalAlpha());
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This property is hidden and intended only for use by the Fade transition, which
+ * animates it to produce a visual translucency that does not side-effect (or get
+ * affected by) the real alpha property. This value is composited with the other
+ * alpha value (and the AlphaAnimation value, when that is present) to produce
+ * a final visual translucency result, which is what is passed into the DisplayList.
+ *
+ * @hide
+ */
+ public void setTransitionAlpha(float alpha) {
+ ensureTransformationInfo();
+ if (mTransformationInfo.mTransitionAlpha != alpha) {
+ mTransformationInfo.mTransitionAlpha = alpha;
+ mPrivateFlags &= ~PFLAG_ALPHA_SET;
+ invalidateViewProperty(true, false);
+ mRenderNode.setAlpha(getFinalAlpha());
+ }
+ }
+
+ /**
+ * Calculates the visual alpha of this view, which is a combination of the actual
+ * alpha value and the transitionAlpha value (if set).
+ */
+ private float getFinalAlpha() {
+ if (mTransformationInfo != null) {
+ return mTransformationInfo.mAlpha * mTransformationInfo.mTransitionAlpha;
+ }
+ return 1;
+ }
+
+ /**
+ * This property is hidden and intended only for use by the Fade transition, which
+ * animates it to produce a visual translucency that does not side-effect (or get
+ * affected by) the real alpha property. This value is composited with the other
+ * alpha value (and the AlphaAnimation value, when that is present) to produce
+ * a final visual translucency result, which is what is passed into the DisplayList.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getTransitionAlpha() {
+ return mTransformationInfo != null ? mTransformationInfo.mTransitionAlpha : 1;
+ }
+
+ /**
+ * Top position of this view relative to its parent.
+ *
+ * @return The top of this view, in pixels.
+ */
+ @ViewDebug.CapturedViewProperty
+ public final int getTop() {
+ return mTop;
+ }
+
+ /**
+ * Sets the top position of this view relative to its parent. This method is meant to be called
+ * by the layout system and should not generally be called otherwise, because the property
+ * may be changed at any time by the layout.
+ *
+ * @param top The top of this view, in pixels.
+ */
+ public final void setTop(int top) {
+ if (top != mTop) {
+ final boolean matrixIsIdentity = hasIdentityMatrix();
+ if (matrixIsIdentity) {
+ if (mAttachInfo != null) {
+ int minTop;
+ int yLoc;
+ if (top < mTop) {
+ minTop = top;
+ yLoc = top - mTop;
+ } else {
+ minTop = mTop;
+ yLoc = 0;
+ }
+ invalidate(0, yLoc, mRight - mLeft, mBottom - minTop);
+ }
+ } else {
+ // Double-invalidation is necessary to capture view's old and new areas
+ invalidate(true);
+ }
+
+ int width = mRight - mLeft;
+ int oldHeight = mBottom - mTop;
+
+ mTop = top;
+ mRenderNode.setTop(mTop);
+
+ sizeChange(width, mBottom - mTop, width, oldHeight);
+
+ if (!matrixIsIdentity) {
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+ invalidate(true);
+ }
+ mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (mForegroundInfo != null) {
+ mForegroundInfo.mBoundsChanged = true;
+ }
+ invalidateParentIfNeeded();
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
+ // View was rejected last time it was drawn by its parent; this may have changed
+ invalidateParentIfNeeded();
+ }
+ }
+ }
+
+ /**
+ * Bottom position of this view relative to its parent.
+ *
+ * @return The bottom of this view, in pixels.
+ */
+ @ViewDebug.CapturedViewProperty
+ public final int getBottom() {
+ return mBottom;
+ }
+
+ /**
+ * True if this view has changed since the last time being drawn.
+ *
+ * @return The dirty state of this view.
+ */
+ public boolean isDirty() {
+ return (mPrivateFlags & PFLAG_DIRTY_MASK) != 0;
+ }
+
+ /**
+ * Sets the bottom position of this view relative to its parent. This method is meant to be
+ * called by the layout system and should not generally be called otherwise, because the
+ * property may be changed at any time by the layout.
+ *
+ * @param bottom The bottom of this view, in pixels.
+ */
+ public final void setBottom(int bottom) {
+ if (bottom != mBottom) {
+ final boolean matrixIsIdentity = hasIdentityMatrix();
+ if (matrixIsIdentity) {
+ if (mAttachInfo != null) {
+ int maxBottom;
+ if (bottom < mBottom) {
+ maxBottom = mBottom;
+ } else {
+ maxBottom = bottom;
+ }
+ invalidate(0, 0, mRight - mLeft, maxBottom - mTop);
+ }
+ } else {
+ // Double-invalidation is necessary to capture view's old and new areas
+ invalidate(true);
+ }
+
+ int width = mRight - mLeft;
+ int oldHeight = mBottom - mTop;
+
+ mBottom = bottom;
+ mRenderNode.setBottom(mBottom);
+
+ sizeChange(width, mBottom - mTop, width, oldHeight);
+
+ if (!matrixIsIdentity) {
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+ invalidate(true);
+ }
+ mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (mForegroundInfo != null) {
+ mForegroundInfo.mBoundsChanged = true;
+ }
+ invalidateParentIfNeeded();
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
+ // View was rejected last time it was drawn by its parent; this may have changed
+ invalidateParentIfNeeded();
+ }
+ }
+ }
+
+ /**
+ * Left position of this view relative to its parent.
+ *
+ * @return The left edge of this view, in pixels.
+ */
+ @ViewDebug.CapturedViewProperty
+ public final int getLeft() {
+ return mLeft;
+ }
+
+ /**
+ * Sets the left position of this view relative to its parent. This method is meant to be called
+ * by the layout system and should not generally be called otherwise, because the property
+ * may be changed at any time by the layout.
+ *
+ * @param left The left of this view, in pixels.
+ */
+ public final void setLeft(int left) {
+ if (left != mLeft) {
+ final boolean matrixIsIdentity = hasIdentityMatrix();
+ if (matrixIsIdentity) {
+ if (mAttachInfo != null) {
+ int minLeft;
+ int xLoc;
+ if (left < mLeft) {
+ minLeft = left;
+ xLoc = left - mLeft;
+ } else {
+ minLeft = mLeft;
+ xLoc = 0;
+ }
+ invalidate(xLoc, 0, mRight - minLeft, mBottom - mTop);
+ }
+ } else {
+ // Double-invalidation is necessary to capture view's old and new areas
+ invalidate(true);
+ }
+
+ int oldWidth = mRight - mLeft;
+ int height = mBottom - mTop;
+
+ mLeft = left;
+ mRenderNode.setLeft(left);
+
+ sizeChange(mRight - mLeft, height, oldWidth, height);
+
+ if (!matrixIsIdentity) {
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+ invalidate(true);
+ }
+ mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (mForegroundInfo != null) {
+ mForegroundInfo.mBoundsChanged = true;
+ }
+ invalidateParentIfNeeded();
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
+ // View was rejected last time it was drawn by its parent; this may have changed
+ invalidateParentIfNeeded();
+ }
+ }
+ }
+
+ /**
+ * Right position of this view relative to its parent.
+ *
+ * @return The right edge of this view, in pixels.
+ */
+ @ViewDebug.CapturedViewProperty
+ public final int getRight() {
+ return mRight;
+ }
+
+ /**
+ * Sets the right position of this view relative to its parent. This method is meant to be called
+ * by the layout system and should not generally be called otherwise, because the property
+ * may be changed at any time by the layout.
+ *
+ * @param right The right of this view, in pixels.
+ */
+ public final void setRight(int right) {
+ if (right != mRight) {
+ final boolean matrixIsIdentity = hasIdentityMatrix();
+ if (matrixIsIdentity) {
+ if (mAttachInfo != null) {
+ int maxRight;
+ if (right < mRight) {
+ maxRight = mRight;
+ } else {
+ maxRight = right;
+ }
+ invalidate(0, 0, maxRight - mLeft, mBottom - mTop);
+ }
+ } else {
+ // Double-invalidation is necessary to capture view's old and new areas
+ invalidate(true);
+ }
+
+ int oldWidth = mRight - mLeft;
+ int height = mBottom - mTop;
+
+ mRight = right;
+ mRenderNode.setRight(mRight);
+
+ sizeChange(mRight - mLeft, height, oldWidth, height);
+
+ if (!matrixIsIdentity) {
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+ invalidate(true);
+ }
+ mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (mForegroundInfo != null) {
+ mForegroundInfo.mBoundsChanged = true;
+ }
+ invalidateParentIfNeeded();
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
+ // View was rejected last time it was drawn by its parent; this may have changed
+ invalidateParentIfNeeded();
+ }
+ }
+ }
+
+ /**
+ * The visual x position of this view, in pixels. This is equivalent to the
+ * {@link #setTranslationX(float) translationX} property plus the current
+ * {@link #getLeft() left} property.
+ *
+ * @return The visual x position of this view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getX() {
+ return mLeft + getTranslationX();
+ }
+
+ /**
+ * Sets the visual x position of this view, in pixels. This is equivalent to setting the
+ * {@link #setTranslationX(float) translationX} property to be the difference between
+ * the x value passed in and the current {@link #getLeft() left} property.
+ *
+ * @param x The visual x position of this view, in pixels.
+ */
+ public void setX(float x) {
+ setTranslationX(x - mLeft);
+ }
+
+ /**
+ * The visual y position of this view, in pixels. This is equivalent to the
+ * {@link #setTranslationY(float) translationY} property plus the current
+ * {@link #getTop() top} property.
+ *
+ * @return The visual y position of this view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getY() {
+ return mTop + getTranslationY();
+ }
+
+ /**
+ * Sets the visual y position of this view, in pixels. This is equivalent to setting the
+ * {@link #setTranslationY(float) translationY} property to be the difference between
+ * the y value passed in and the current {@link #getTop() top} property.
+ *
+ * @param y The visual y position of this view, in pixels.
+ */
+ public void setY(float y) {
+ setTranslationY(y - mTop);
+ }
+
+ /**
+ * The visual z position of this view, in pixels. This is equivalent to the
+ * {@link #setTranslationZ(float) translationZ} property plus the current
+ * {@link #getElevation() elevation} property.
+ *
+ * @return The visual z position of this view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getZ() {
+ return getElevation() + getTranslationZ();
+ }
+
+ /**
+ * Sets the visual z position of this view, in pixels. This is equivalent to setting the
+ * {@link #setTranslationZ(float) translationZ} property to be the difference between
+ * the x value passed in and the current {@link #getElevation() elevation} property.
+ *
+ * @param z The visual z position of this view, in pixels.
+ */
+ public void setZ(float z) {
+ setTranslationZ(z - getElevation());
+ }
+
+ /**
+ * The base elevation of this view relative to its parent, in pixels.
+ *
+ * @return The base depth position of the view, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getElevation() {
+ return mRenderNode.getElevation();
+ }
+
+ /**
+ * Sets the base elevation of this view, in pixels.
+ *
+ * @attr ref android.R.styleable#View_elevation
+ */
+ public void setElevation(float elevation) {
+ if (elevation != getElevation()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setElevation(elevation);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
+
+ /**
+ * The horizontal location of this view relative to its {@link #getLeft() left} position.
+ * This position is post-layout, in addition to wherever the object's
+ * layout placed it.
+ *
+ * @return The horizontal position of this view relative to its left position, in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getTranslationX() {
+ return mRenderNode.getTranslationX();
+ }
+
+ /**
+ * Sets the horizontal location of this view relative to its {@link #getLeft() left} position.
+ * This effectively positions the object post-layout, in addition to wherever the object's
+ * layout placed it.
+ *
+ * @param translationX The horizontal position of this view relative to its left position,
+ * in pixels.
+ *
+ * @attr ref android.R.styleable#View_translationX
+ */
+ public void setTranslationX(float translationX) {
+ if (translationX != getTranslationX()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setTranslationX(translationX);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * The vertical location of this view relative to its {@link #getTop() top} position.
+ * This position is post-layout, in addition to wherever the object's
+ * layout placed it.
+ *
+ * @return The vertical position of this view relative to its top position,
+ * in pixels.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getTranslationY() {
+ return mRenderNode.getTranslationY();
+ }
+
+ /**
+ * Sets the vertical location of this view relative to its {@link #getTop() top} position.
+ * This effectively positions the object post-layout, in addition to wherever the object's
+ * layout placed it.
+ *
+ * @param translationY The vertical position of this view relative to its top position,
+ * in pixels.
+ *
+ * @attr ref android.R.styleable#View_translationY
+ */
+ public void setTranslationY(float translationY) {
+ if (translationY != getTranslationY()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setTranslationY(translationY);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * The depth location of this view relative to its {@link #getElevation() elevation}.
+ *
+ * @return The depth of this view relative to its elevation.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getTranslationZ() {
+ return mRenderNode.getTranslationZ();
+ }
+
+ /**
+ * Sets the depth location of this view relative to its {@link #getElevation() elevation}.
+ *
+ * @attr ref android.R.styleable#View_translationZ
+ */
+ public void setTranslationZ(float translationZ) {
+ if (translationZ != getTranslationZ()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setTranslationZ(translationZ);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
+
+ /** @hide */
+ public void setAnimationMatrix(Matrix matrix) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setAnimationMatrix(matrix);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+
+ /**
+ * Returns the current StateListAnimator if exists.
+ *
+ * @return StateListAnimator or null if it does not exists
+ * @see #setStateListAnimator(android.animation.StateListAnimator)
+ */
+ public StateListAnimator getStateListAnimator() {
+ return mStateListAnimator;
+ }
+
+ /**
+ * Attaches the provided StateListAnimator to this View.
+ * <p>
+ * Any previously attached StateListAnimator will be detached.
+ *
+ * @param stateListAnimator The StateListAnimator to update the view
+ * @see android.animation.StateListAnimator
+ */
+ public void setStateListAnimator(StateListAnimator stateListAnimator) {
+ if (mStateListAnimator == stateListAnimator) {
+ return;
+ }
+ if (mStateListAnimator != null) {
+ mStateListAnimator.setTarget(null);
+ }
+ mStateListAnimator = stateListAnimator;
+ if (stateListAnimator != null) {
+ stateListAnimator.setTarget(this);
+ if (isAttachedToWindow()) {
+ stateListAnimator.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
+ * Returns whether the Outline should be used to clip the contents of the View.
+ * <p>
+ * Note that this flag will only be respected if the View's Outline returns true from
+ * {@link Outline#canClip()}.
+ *
+ * @see #setOutlineProvider(ViewOutlineProvider)
+ * @see #setClipToOutline(boolean)
+ */
+ public final boolean getClipToOutline() {
+ return mRenderNode.getClipToOutline();
+ }
+
+ /**
+ * Sets whether the View's Outline should be used to clip the contents of the View.
+ * <p>
+ * Only a single non-rectangular clip can be applied on a View at any time.
+ * Circular clips from a {@link ViewAnimationUtils#createCircularReveal(View, int, int, float, float)
+ * circular reveal} animation take priority over Outline clipping, and
+ * child Outline clipping takes priority over Outline clipping done by a
+ * parent.
+ * <p>
+ * Note that this flag will only be respected if the View's Outline returns true from
+ * {@link Outline#canClip()}.
+ *
+ * @see #setOutlineProvider(ViewOutlineProvider)
+ * @see #getClipToOutline()
+ */
+ public void setClipToOutline(boolean clipToOutline) {
+ damageInParent();
+ if (getClipToOutline() != clipToOutline) {
+ mRenderNode.setClipToOutline(clipToOutline);
+ }
+ }
+
+ // correspond to the enum values of View_outlineProvider
+ private static final int PROVIDER_BACKGROUND = 0;
+ private static final int PROVIDER_NONE = 1;
+ private static final int PROVIDER_BOUNDS = 2;
+ private static final int PROVIDER_PADDED_BOUNDS = 3;
+ private void setOutlineProviderFromAttribute(int providerInt) {
+ switch (providerInt) {
+ case PROVIDER_BACKGROUND:
+ setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+ break;
+ case PROVIDER_NONE:
+ setOutlineProvider(null);
+ break;
+ case PROVIDER_BOUNDS:
+ setOutlineProvider(ViewOutlineProvider.BOUNDS);
+ break;
+ case PROVIDER_PADDED_BOUNDS:
+ setOutlineProvider(ViewOutlineProvider.PADDED_BOUNDS);
+ break;
+ }
+ }
+
+ /**
+ * Sets the {@link ViewOutlineProvider} of the view, which generates the Outline that defines
+ * the shape of the shadow it casts, and enables outline clipping.
+ * <p>
+ * The default ViewOutlineProvider, {@link ViewOutlineProvider#BACKGROUND}, queries the Outline
+ * from the View's background drawable, via {@link Drawable#getOutline(Outline)}. Changing the
+ * outline provider with this method allows this behavior to be overridden.
+ * <p>
+ * If the ViewOutlineProvider is null, if querying it for an outline returns false,
+ * or if the produced Outline is {@link Outline#isEmpty()}, shadows will not be cast.
+ * <p>
+ * Only outlines that return true from {@link Outline#canClip()} may be used for clipping.
+ *
+ * @see #setClipToOutline(boolean)
+ * @see #getClipToOutline()
+ * @see #getOutlineProvider()
+ */
+ public void setOutlineProvider(ViewOutlineProvider provider) {
+ mOutlineProvider = provider;
+ invalidateOutline();
+ }
+
+ /**
+ * Returns the current {@link ViewOutlineProvider} of the view, which generates the Outline
+ * that defines the shape of the shadow it casts, and enables outline clipping.
+ *
+ * @see #setOutlineProvider(ViewOutlineProvider)
+ */
+ public ViewOutlineProvider getOutlineProvider() {
+ return mOutlineProvider;
+ }
+
+ /**
+ * Called to rebuild this View's Outline from its {@link ViewOutlineProvider outline provider}
+ *
+ * @see #setOutlineProvider(ViewOutlineProvider)
+ */
+ public void invalidateOutline() {
+ rebuildOutline();
+
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ invalidateViewProperty(false, false);
+ }
+
+ /**
+ * Internal version of {@link #invalidateOutline()} which invalidates the
+ * outline without invalidating the view itself. This is intended to be called from
+ * within methods in the View class itself which are the result of the view being
+ * invalidated already. For example, when we are drawing the background of a View,
+ * we invalidate the outline in case it changed in the meantime, but we do not
+ * need to invalidate the view because we're already drawing the background as part
+ * of drawing the view in response to an earlier invalidation of the view.
+ */
+ private void rebuildOutline() {
+ // Unattached views ignore this signal, and outline is recomputed in onAttachedToWindow()
+ if (mAttachInfo == null) return;
+
+ if (mOutlineProvider == null) {
+ // no provider, remove outline
+ mRenderNode.setOutline(null);
+ } else {
+ final Outline outline = mAttachInfo.mTmpOutline;
+ outline.setEmpty();
+ outline.setAlpha(1.0f);
+
+ mOutlineProvider.getOutline(this, outline);
+ mRenderNode.setOutline(outline);
+ }
+ }
+
+ /**
+ * HierarchyViewer only
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean hasShadow() {
+ return mRenderNode.hasShadow();
+ }
+
+
+ /** @hide */
+ public void setRevealClip(boolean shouldClip, float x, float y, float radius) {
+ mRenderNode.setRevealClip(shouldClip, x, y, radius);
+ invalidateViewProperty(false, false);
+ }
+
+ /**
+ * Hit rectangle in parent's coordinates
+ *
+ * @param outRect The hit rectangle of the view.
+ */
+ public void getHitRect(Rect outRect) {
+ if (hasIdentityMatrix() || mAttachInfo == null) {
+ outRect.set(mLeft, mTop, mRight, mBottom);
+ } else {
+ final RectF tmpRect = mAttachInfo.mTmpTransformRect;
+ tmpRect.set(0, 0, getWidth(), getHeight());
+ getMatrix().mapRect(tmpRect); // TODO: mRenderNode.mapRect(tmpRect)
+ outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
+ (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
+ }
+ }
+
+ /**
+ * Determines whether the given point, in local coordinates is inside the view.
+ */
+ /*package*/ final boolean pointInView(float localX, float localY) {
+ return pointInView(localX, localY, 0);
+ }
+
+ /**
+ * Utility method to determine whether the given point, in local coordinates,
+ * is inside the view, where the area of the view is expanded by the slop factor.
+ * This method is called while processing touch-move events to determine if the event
+ * is still within the view.
+ *
+ * @hide
+ */
+ public boolean pointInView(float localX, float localY, float slop) {
+ return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
+ localY < ((mBottom - mTop) + slop);
+ }
+
+ /**
+ * When a view has focus and the user navigates away from it, the next view is searched for
+ * starting from the rectangle filled in by this method.
+ *
+ * By default, the rectangle is the {@link #getDrawingRect(android.graphics.Rect)})
+ * of the view. However, if your view maintains some idea of internal selection,
+ * such as a cursor, or a selected row or column, you should override this method and
+ * fill in a more specific rectangle.
+ *
+ * @param r The rectangle to fill in, in this view's coordinates.
+ */
+ public void getFocusedRect(Rect r) {
+ getDrawingRect(r);
+ }
+
+ /**
+ * If some part of this view is not clipped by any of its parents, then
+ * return that area in r in global (root) coordinates. To convert r to local
+ * coordinates (without taking possible View rotations into account), offset
+ * it by -globalOffset (e.g. r.offset(-globalOffset.x, -globalOffset.y)).
+ * If the view is completely clipped or translated out, return false.
+ *
+ * @param r If true is returned, r holds the global coordinates of the
+ * visible portion of this view.
+ * @param globalOffset If true is returned, globalOffset holds the dx,dy
+ * between this view and its root. globalOffet may be null.
+ * @return true if r is non-empty (i.e. part of the view is visible at the
+ * root level.
+ */
+ public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+ if (width > 0 && height > 0) {
+ r.set(0, 0, width, height);
+ if (globalOffset != null) {
+ globalOffset.set(-mScrollX, -mScrollY);
+ }
+ return mParent == null || mParent.getChildVisibleRect(this, r, globalOffset);
+ }
+ return false;
+ }
+
+ public final boolean getGlobalVisibleRect(Rect r) {
+ return getGlobalVisibleRect(r, null);
+ }
+
+ public final boolean getLocalVisibleRect(Rect r) {
+ final Point offset = mAttachInfo != null ? mAttachInfo.mPoint : new Point();
+ if (getGlobalVisibleRect(r, offset)) {
+ r.offset(-offset.x, -offset.y); // make r local
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Offset this view's vertical location by the specified number of pixels.
+ *
+ * @param offset the number of pixels to offset the view by
+ */
+ public void offsetTopAndBottom(int offset) {
+ if (offset != 0) {
+ final boolean matrixIsIdentity = hasIdentityMatrix();
+ if (matrixIsIdentity) {
+ if (isHardwareAccelerated()) {
+ invalidateViewProperty(false, false);
+ } else {
+ final ViewParent p = mParent;
+ if (p != null && mAttachInfo != null) {
+ final Rect r = mAttachInfo.mTmpInvalRect;
+ int minTop;
+ int maxBottom;
+ int yLoc;
+ if (offset < 0) {
+ minTop = mTop + offset;
+ maxBottom = mBottom;
+ yLoc = offset;
+ } else {
+ minTop = mTop;
+ maxBottom = mBottom + offset;
+ yLoc = 0;
+ }
+ r.set(0, yLoc, mRight - mLeft, maxBottom - minTop);
+ p.invalidateChild(this, r);
+ }
+ }
+ } else {
+ invalidateViewProperty(false, false);
+ }
+
+ mTop += offset;
+ mBottom += offset;
+ mRenderNode.offsetTopAndBottom(offset);
+ if (isHardwareAccelerated()) {
+ invalidateViewProperty(false, false);
+ invalidateParentIfNeededAndWasQuickRejected();
+ } else {
+ if (!matrixIsIdentity) {
+ invalidateViewProperty(false, true);
+ }
+ invalidateParentIfNeeded();
+ }
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * Offset this view's horizontal location by the specified amount of pixels.
+ *
+ * @param offset the number of pixels to offset the view by
+ */
+ public void offsetLeftAndRight(int offset) {
+ if (offset != 0) {
+ final boolean matrixIsIdentity = hasIdentityMatrix();
+ if (matrixIsIdentity) {
+ if (isHardwareAccelerated()) {
+ invalidateViewProperty(false, false);
+ } else {
+ final ViewParent p = mParent;
+ if (p != null && mAttachInfo != null) {
+ final Rect r = mAttachInfo.mTmpInvalRect;
+ int minLeft;
+ int maxRight;
+ if (offset < 0) {
+ minLeft = mLeft + offset;
+ maxRight = mRight;
+ } else {
+ minLeft = mLeft;
+ maxRight = mRight + offset;
+ }
+ r.set(0, 0, maxRight - minLeft, mBottom - mTop);
+ p.invalidateChild(this, r);
+ }
+ }
+ } else {
+ invalidateViewProperty(false, false);
+ }
+
+ mLeft += offset;
+ mRight += offset;
+ mRenderNode.offsetLeftAndRight(offset);
+ if (isHardwareAccelerated()) {
+ invalidateViewProperty(false, false);
+ invalidateParentIfNeededAndWasQuickRejected();
+ } else {
+ if (!matrixIsIdentity) {
+ invalidateViewProperty(false, true);
+ }
+ invalidateParentIfNeeded();
+ }
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ }
+
+ /**
+ * Get the LayoutParams associated with this view. All views should have
+ * layout parameters. These supply parameters to the <i>parent</i> of this
+ * view specifying how it should be arranged. There are many subclasses of
+ * ViewGroup.LayoutParams, and these correspond to the different subclasses
+ * of ViewGroup that are responsible for arranging their children.
+ *
+ * This method may return null if this View is not attached to a parent
+ * ViewGroup or {@link #setLayoutParams(android.view.ViewGroup.LayoutParams)}
+ * was not invoked successfully. When a View is attached to a parent
+ * ViewGroup, this method must not return null.
+ *
+ * @return The LayoutParams associated with this view, or null if no
+ * parameters have been set yet
+ */
+ @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")
+ public ViewGroup.LayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+
+ /**
+ * Set the layout parameters associated with this view. These supply
+ * parameters to the <i>parent</i> of this view specifying how it should be
+ * arranged. There are many subclasses of ViewGroup.LayoutParams, and these
+ * correspond to the different subclasses of ViewGroup that are responsible
+ * for arranging their children.
+ *
+ * @param params The layout parameters for this view, cannot be null
+ */
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ if (params == null) {
+ throw new NullPointerException("Layout parameters cannot be null");
+ }
+ mLayoutParams = params;
+ resolveLayoutParams();
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).onSetLayoutParams(this, params);
+ }
+ requestLayout();
+ }
+
+ /**
+ * Resolve the layout parameters depending on the resolved layout direction
+ *
+ * @hide
+ */
+ public void resolveLayoutParams() {
+ if (mLayoutParams != null) {
+ mLayoutParams.resolveLayoutDirection(getLayoutDirection());
+ }
+ }
+
+ /**
+ * Set the scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param x the x position to scroll to
+ * @param y the y position to scroll to
+ */
+ public void scrollTo(int x, int y) {
+ if (mScrollX != x || mScrollY != y) {
+ int oldX = mScrollX;
+ int oldY = mScrollY;
+ mScrollX = x;
+ mScrollY = y;
+ invalidateParentCaches();
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+ if (!awakenScrollBars()) {
+ postInvalidateOnAnimation();
+ }
+ }
+ }
+
+ /**
+ * Move the scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param x the amount of pixels to scroll by horizontally
+ * @param y the amount of pixels to scroll by vertically
+ */
+ public void scrollBy(int x, int y) {
+ scrollTo(mScrollX + x, mScrollY + y);
+ }
+
+ /**
+ * <p>Trigger the scrollbars to draw. When invoked this method starts an
+ * animation to fade the scrollbars out after a default delay. If a subclass
+ * provides animated scrolling, the start delay should equal the duration
+ * of the scrolling animation.</p>
+ *
+ * <p>The animation starts only if at least one of the scrollbars is
+ * enabled, as specified by {@link #isHorizontalScrollBarEnabled()} and
+ * {@link #isVerticalScrollBarEnabled()}. When the animation is started,
+ * this method returns true, and false otherwise. If the animation is
+ * started, this method calls {@link #invalidate()}; in that case the
+ * caller should not call {@link #invalidate()}.</p>
+ *
+ * <p>This method should be invoked every time a subclass directly updates
+ * the scroll parameters.</p>
+ *
+ * <p>This method is automatically invoked by {@link #scrollBy(int, int)}
+ * and {@link #scrollTo(int, int)}.</p>
+ *
+ * @return true if the animation is played, false otherwise
+ *
+ * @see #awakenScrollBars(int)
+ * @see #scrollBy(int, int)
+ * @see #scrollTo(int, int)
+ * @see #isHorizontalScrollBarEnabled()
+ * @see #isVerticalScrollBarEnabled()
+ * @see #setHorizontalScrollBarEnabled(boolean)
+ * @see #setVerticalScrollBarEnabled(boolean)
+ */
+ protected boolean awakenScrollBars() {
+ return mScrollCache != null &&
+ awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade, true);
+ }
+
+ /**
+ * Trigger the scrollbars to draw.
+ * This method differs from awakenScrollBars() only in its default duration.
+ * initialAwakenScrollBars() will show the scroll bars for longer than
+ * usual to give the user more of a chance to notice them.
+ *
+ * @return true if the animation is played, false otherwise.
+ */
+ private boolean initialAwakenScrollBars() {
+ return mScrollCache != null &&
+ awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade * 4, true);
+ }
+
+ /**
+ * <p>
+ * Trigger the scrollbars to draw. When invoked this method starts an
+ * animation to fade the scrollbars out after a fixed delay. If a subclass
+ * provides animated scrolling, the start delay should equal the duration of
+ * the scrolling animation.
+ * </p>
+ *
+ * <p>
+ * The animation starts only if at least one of the scrollbars is enabled,
+ * as specified by {@link #isHorizontalScrollBarEnabled()} and
+ * {@link #isVerticalScrollBarEnabled()}. When the animation is started,
+ * this method returns true, and false otherwise. If the animation is
+ * started, this method calls {@link #invalidate()}; in that case the caller
+ * should not call {@link #invalidate()}.
+ * </p>
+ *
+ * <p>
+ * This method should be invoked every time a subclass directly updates the
+ * scroll parameters.
+ * </p>
+ *
+ * @param startDelay the delay, in milliseconds, after which the animation
+ * should start; when the delay is 0, the animation starts
+ * immediately
+ * @return true if the animation is played, false otherwise
+ *
+ * @see #scrollBy(int, int)
+ * @see #scrollTo(int, int)
+ * @see #isHorizontalScrollBarEnabled()
+ * @see #isVerticalScrollBarEnabled()
+ * @see #setHorizontalScrollBarEnabled(boolean)
+ * @see #setVerticalScrollBarEnabled(boolean)
+ */
+ protected boolean awakenScrollBars(int startDelay) {
+ return awakenScrollBars(startDelay, true);
+ }
+
+ /**
+ * <p>
+ * Trigger the scrollbars to draw. When invoked this method starts an
+ * animation to fade the scrollbars out after a fixed delay. If a subclass
+ * provides animated scrolling, the start delay should equal the duration of
+ * the scrolling animation.
+ * </p>
+ *
+ * <p>
+ * The animation starts only if at least one of the scrollbars is enabled,
+ * as specified by {@link #isHorizontalScrollBarEnabled()} and
+ * {@link #isVerticalScrollBarEnabled()}. When the animation is started,
+ * this method returns true, and false otherwise. If the animation is
+ * started, this method calls {@link #invalidate()} if the invalidate parameter
+ * is set to true; in that case the caller
+ * should not call {@link #invalidate()}.
+ * </p>
+ *
+ * <p>
+ * This method should be invoked every time a subclass directly updates the
+ * scroll parameters.
+ * </p>
+ *
+ * @param startDelay the delay, in milliseconds, after which the animation
+ * should start; when the delay is 0, the animation starts
+ * immediately
+ *
+ * @param invalidate Whether this method should call invalidate
+ *
+ * @return true if the animation is played, false otherwise
+ *
+ * @see #scrollBy(int, int)
+ * @see #scrollTo(int, int)
+ * @see #isHorizontalScrollBarEnabled()
+ * @see #isVerticalScrollBarEnabled()
+ * @see #setHorizontalScrollBarEnabled(boolean)
+ * @see #setVerticalScrollBarEnabled(boolean)
+ */
+ protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
+ final ScrollabilityCache scrollCache = mScrollCache;
+
+ if (scrollCache == null || !scrollCache.fadeScrollBars) {
+ return false;
+ }
+
+ if (scrollCache.scrollBar == null) {
+ scrollCache.scrollBar = new ScrollBarDrawable();
+ scrollCache.scrollBar.setState(getDrawableState());
+ scrollCache.scrollBar.setCallback(this);
+ }
+
+ if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {
+
+ if (invalidate) {
+ // Invalidate to show the scrollbars
+ postInvalidateOnAnimation();
+ }
+
+ if (scrollCache.state == ScrollabilityCache.OFF) {
+ // FIXME: this is copied from WindowManagerService.
+ // We should get this value from the system when it
+ // is possible to do so.
+ final int KEY_REPEAT_FIRST_DELAY = 750;
+ startDelay = Math.max(KEY_REPEAT_FIRST_DELAY, startDelay);
+ }
+
+ // Tell mScrollCache when we should start fading. This may
+ // extend the fade start time if one was already scheduled
+ long fadeStartTime = AnimationUtils.currentAnimationTimeMillis() + startDelay;
+ scrollCache.fadeStartTime = fadeStartTime;
+ scrollCache.state = ScrollabilityCache.ON;
+
+ // Schedule our fader to run, unscheduling any old ones first
+ if (mAttachInfo != null) {
+ mAttachInfo.mHandler.removeCallbacks(scrollCache);
+ mAttachInfo.mHandler.postAtTime(scrollCache, fadeStartTime);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Do not invalidate views which are not visible and which are not running an animation. They
+ * will not get drawn and they should not set dirty flags as if they will be drawn
+ */
+ private boolean skipInvalidate() {
+ return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
+ (!(mParent instanceof ViewGroup) ||
+ !((ViewGroup) mParent).isViewTransitioning(this));
+ }
+
+ /**
+ * Mark the area defined by dirty as needing to be drawn. If the view is
+ * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
+ * point in the future.
+ * <p>
+ * This must be called from a UI thread. To call from a non-UI thread, call
+ * {@link #postInvalidate()}.
+ * <p>
+ * <b>WARNING:</b> In API 19 and below, this method may be destructive to
+ * {@code dirty}.
+ *
+ * @param dirty the rectangle representing the bounds of the dirty region
+ */
+ public void invalidate(Rect dirty) {
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
+ dirty.right - scrollX, dirty.bottom - scrollY, true, false);
+ }
+
+ /**
+ * Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The
+ * coordinates of the dirty rect are relative to the view. If the view is
+ * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
+ * point in the future.
+ * <p>
+ * This must be called from a UI thread. To call from a non-UI thread, call
+ * {@link #postInvalidate()}.
+ *
+ * @param l the left position of the dirty region
+ * @param t the top position of the dirty region
+ * @param r the right position of the dirty region
+ * @param b the bottom position of the dirty region
+ */
+ public void invalidate(int l, int t, int r, int b) {
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
+ }
+
+ /**
+ * Invalidate the whole view. If the view is visible,
+ * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
+ * the future.
+ * <p>
+ * This must be called from a UI thread. To call from a non-UI thread, call
+ * {@link #postInvalidate()}.
+ */
+ public void invalidate() {
+ invalidate(true);
+ }
+
+ /**
+ * This is where the invalidate() work actually happens. A full invalidate()
+ * causes the drawing cache to be invalidated, but this function can be
+ * called with invalidateCache set to false to skip that invalidation step
+ * for cases that do not need it (for example, a component that remains at
+ * the same dimensions with the same content).
+ *
+ * @param invalidateCache Whether the drawing cache for this view should be
+ * invalidated as well. This is usually true for a full
+ * invalidate, but may be set to false if the View's contents or
+ * dimensions have not changed.
+ * @hide
+ */
+ public void invalidate(boolean invalidateCache) {
+ invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
+ }
+
+ void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
+ boolean fullInvalidate) {
+ if (mGhostView != null) {
+ mGhostView.invalidate(true);
+ return;
+ }
+
+ if (skipInvalidate()) {
+ return;
+ }
+
+ if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
+ || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
+ || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
+ || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
+ if (fullInvalidate) {
+ mLastIsOpaque = isOpaque();
+ mPrivateFlags &= ~PFLAG_DRAWN;
+ }
+
+ mPrivateFlags |= PFLAG_DIRTY;
+
+ if (invalidateCache) {
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ }
+
+ // Propagate the damage rectangle to the parent view.
+ final AttachInfo ai = mAttachInfo;
+ final ViewParent p = mParent;
+ if (p != null && ai != null && l < r && t < b) {
+ final Rect damage = ai.mTmpInvalRect;
+ damage.set(l, t, r, b);
+ p.invalidateChild(this, damage);
+ }
+
+ // Damage the entire projection receiver, if necessary.
+ if (mBackground != null && mBackground.isProjected()) {
+ final View receiver = getProjectionReceiver();
+ if (receiver != null) {
+ receiver.damageInParent();
+ }
+ }
+ }
+ }
+
+ /**
+ * @return this view's projection receiver, or {@code null} if none exists
+ */
+ private View getProjectionReceiver() {
+ ViewParent p = getParent();
+ while (p != null && p instanceof View) {
+ final View v = (View) p;
+ if (v.isProjectionReceiver()) {
+ return v;
+ }
+ p = p.getParent();
+ }
+
+ return null;
+ }
+
+ /**
+ * @return whether the view is a projection receiver
+ */
+ private boolean isProjectionReceiver() {
+ return mBackground != null;
+ }
+
+ /**
+ * Quick invalidation for View property changes (alpha, translationXY, etc.). We don't want to
+ * set any flags or handle all of the cases handled by the default invalidation methods.
+ * Instead, we just want to schedule a traversal in ViewRootImpl with the appropriate
+ * dirty rect. This method calls into fast invalidation methods in ViewGroup that
+ * walk up the hierarchy, transforming the dirty rect as necessary.
+ *
+ * The method also handles normal invalidation logic if display list properties are not
+ * being used in this view. The invalidateParent and forceRedraw flags are used by that
+ * backup approach, to handle these cases used in the various property-setting methods.
+ *
+ * @param invalidateParent Force a call to invalidateParentCaches() if display list properties
+ * are not being used in this view
+ * @param forceRedraw Mark the view as DRAWN to force the invalidation to propagate, if display
+ * list properties are not being used in this view
+ */
+ void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
+ if (!isHardwareAccelerated()
+ || !mRenderNode.isValid()
+ || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
+ if (invalidateParent) {
+ invalidateParentCaches();
+ }
+ if (forceRedraw) {
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
+ }
+ invalidate(false);
+ } else {
+ damageInParent();
+ }
+ }
+
+ /**
+ * Tells the parent view to damage this view's bounds.
+ *
+ * @hide
+ */
+ protected void damageInParent() {
+ if (mParent != null && mAttachInfo != null) {
+ mParent.onDescendantInvalidated(this, this);
+ }
+ }
+
+ /**
+ * Utility method to transform a given Rect by the current matrix of this view.
+ */
+ void transformRect(final Rect rect) {
+ if (!getMatrix().isIdentity()) {
+ RectF boundingRect = mAttachInfo.mTmpTransformRect;
+ boundingRect.set(rect);
+ getMatrix().mapRect(boundingRect);
+ rect.set((int) Math.floor(boundingRect.left),
+ (int) Math.floor(boundingRect.top),
+ (int) Math.ceil(boundingRect.right),
+ (int) Math.ceil(boundingRect.bottom));
+ }
+ }
+
+ /**
+ * Used to indicate that the parent of this view should clear its caches. This functionality
+ * is used to force the parent to rebuild its display list (when hardware-accelerated),
+ * which is necessary when various parent-managed properties of the view change, such as
+ * alpha, translationX/Y, scrollX/Y, scaleX/Y, and rotation/X/Y. This method only
+ * clears the parent caches and does not causes an invalidate event.
+ *
+ * @hide
+ */
+ protected void invalidateParentCaches() {
+ if (mParent instanceof View) {
+ ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
+ }
+ }
+
+ /**
+ * Used to indicate that the parent of this view should be invalidated. This functionality
+ * is used to force the parent to rebuild its display list (when hardware-accelerated),
+ * which is necessary when various parent-managed properties of the view change, such as
+ * alpha, translationX/Y, scrollX/Y, scaleX/Y, and rotation/X/Y. This method will propagate
+ * an invalidation event to the parent.
+ *
+ * @hide
+ */
+ protected void invalidateParentIfNeeded() {
+ if (isHardwareAccelerated() && mParent instanceof View) {
+ ((View) mParent).invalidate(true);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected void invalidateParentIfNeededAndWasQuickRejected() {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) != 0) {
+ // View was rejected last time it was drawn by its parent; this may have changed
+ invalidateParentIfNeeded();
+ }
+ }
+
+ /**
+ * Indicates whether this View is opaque. An opaque View guarantees that it will
+ * draw all the pixels overlapping its bounds using a fully opaque color.
+ *
+ * Subclasses of View should override this method whenever possible to indicate
+ * whether an instance is opaque. Opaque Views are treated in a special way by
+ * the View hierarchy, possibly allowing it to perform optimizations during
+ * invalidate/draw passes.
+ *
+ * @return True if this View is guaranteed to be fully opaque, false otherwise.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean isOpaque() {
+ return (mPrivateFlags & PFLAG_OPAQUE_MASK) == PFLAG_OPAQUE_MASK &&
+ getFinalAlpha() >= 1.0f;
+ }
+
+ /**
+ * @hide
+ */
+ protected void computeOpaqueFlags() {
+ // Opaque if:
+ // - Has a background
+ // - Background is opaque
+ // - Doesn't have scrollbars or scrollbars overlay
+
+ if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
+ mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
+ } else {
+ mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
+ }
+
+ final int flags = mViewFlags;
+ if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
+ (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
+ (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
+ mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
+ } else {
+ mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected boolean hasOpaqueScrollbars() {
+ return (mPrivateFlags & PFLAG_OPAQUE_SCROLLBARS) == PFLAG_OPAQUE_SCROLLBARS;
+ }
+
+ /**
+ * @return A handler associated with the thread running the View. This
+ * handler can be used to pump events in the UI events queue.
+ */
+ public Handler getHandler() {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ return attachInfo.mHandler;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the queue of runnable for this view.
+ *
+ * @return the queue of runnables for this view
+ */
+ private HandlerActionQueue getRunQueue() {
+ if (mRunQueue == null) {
+ mRunQueue = new HandlerActionQueue();
+ }
+ return mRunQueue;
+ }
+
+ /**
+ * Gets the view root associated with the View.
+ * @return The view root, or null if none.
+ * @hide
+ */
+ public ViewRootImpl getViewRootImpl() {
+ if (mAttachInfo != null) {
+ return mAttachInfo.mViewRootImpl;
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public ThreadedRenderer getThreadedRenderer() {
+ return mAttachInfo != null ? mAttachInfo.mThreadedRenderer : null;
+ }
+
+ /**
+ * <p>Causes the Runnable to be added to the message queue.
+ * The runnable will be run on the user interface thread.</p>
+ *
+ * @param action The Runnable that will be executed.
+ *
+ * @return Returns true if the Runnable was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ *
+ * @see #postDelayed
+ * @see #removeCallbacks
+ */
+ public boolean post(Runnable action) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ return attachInfo.mHandler.post(action);
+ }
+
+ // Postpone the runnable until we know on which thread it needs to run.
+ // Assume that the runnable will be successfully placed after attach.
+ getRunQueue().post(action);
+ return true;
+ }
+
+ /**
+ * <p>Causes the Runnable to be added to the message queue, to be run
+ * after the specified amount of time elapses.
+ * The runnable will be run on the user interface thread.</p>
+ *
+ * @param action The Runnable that will be executed.
+ * @param delayMillis The delay (in milliseconds) until the Runnable
+ * will be executed.
+ *
+ * @return true if the Runnable was successfully placed in to the
+ * message queue. Returns false on failure, usually because the
+ * looper processing the message queue is exiting. Note that a
+ * result of true does not mean the Runnable will be processed --
+ * if the looper is quit before the delivery time of the message
+ * occurs then the message will be dropped.
+ *
+ * @see #post
+ * @see #removeCallbacks
+ */
+ public boolean postDelayed(Runnable action, long delayMillis) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ return attachInfo.mHandler.postDelayed(action, delayMillis);
+ }
+
+ // Postpone the runnable until we know on which thread it needs to run.
+ // Assume that the runnable will be successfully placed after attach.
+ getRunQueue().postDelayed(action, delayMillis);
+ return true;
+ }
+
+ /**
+ * <p>Causes the Runnable to execute on the next animation time step.
+ * The runnable will be run on the user interface thread.</p>
+ *
+ * @param action The Runnable that will be executed.
+ *
+ * @see #postOnAnimationDelayed
+ * @see #removeCallbacks
+ */
+ public void postOnAnimation(Runnable action) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ attachInfo.mViewRootImpl.mChoreographer.postCallback(
+ Choreographer.CALLBACK_ANIMATION, action, null);
+ } else {
+ // Postpone the runnable until we know
+ // on which thread it needs to run.
+ getRunQueue().post(action);
+ }
+ }
+
+ /**
+ * <p>Causes the Runnable to execute on the next animation time step,
+ * after the specified amount of time elapses.
+ * The runnable will be run on the user interface thread.</p>
+ *
+ * @param action The Runnable that will be executed.
+ * @param delayMillis The delay (in milliseconds) until the Runnable
+ * will be executed.
+ *
+ * @see #postOnAnimation
+ * @see #removeCallbacks
+ */
+ public void postOnAnimationDelayed(Runnable action, long delayMillis) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ attachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
+ Choreographer.CALLBACK_ANIMATION, action, null, delayMillis);
+ } else {
+ // Postpone the runnable until we know
+ // on which thread it needs to run.
+ getRunQueue().postDelayed(action, delayMillis);
+ }
+ }
+
+ /**
+ * <p>Removes the specified Runnable from the message queue.</p>
+ *
+ * @param action The Runnable to remove from the message handling queue
+ *
+ * @return true if this view could ask the Handler to remove the Runnable,
+ * false otherwise. When the returned value is true, the Runnable
+ * may or may not have been actually removed from the message queue
+ * (for instance, if the Runnable was not in the queue already.)
+ *
+ * @see #post
+ * @see #postDelayed
+ * @see #postOnAnimation
+ * @see #postOnAnimationDelayed
+ */
+ public boolean removeCallbacks(Runnable action) {
+ if (action != null) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ attachInfo.mHandler.removeCallbacks(action);
+ attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
+ Choreographer.CALLBACK_ANIMATION, action, null);
+ }
+ getRunQueue().removeCallbacks(action);
+ }
+ return true;
+ }
+
+ /**
+ * <p>Cause an invalidate to happen on a subsequent cycle through the event loop.
+ * Use this to invalidate the View from a non-UI thread.</p>
+ *
+ * <p>This method can be invoked from outside of the UI thread
+ * only when this View is attached to a window.</p>
+ *
+ * @see #invalidate()
+ * @see #postInvalidateDelayed(long)
+ */
+ public void postInvalidate() {
+ postInvalidateDelayed(0);
+ }
+
+ /**
+ * <p>Cause an invalidate of the specified area to happen on a subsequent cycle
+ * through the event loop. Use this to invalidate the View from a non-UI thread.</p>
+ *
+ * <p>This method can be invoked from outside of the UI thread
+ * only when this View is attached to a window.</p>
+ *
+ * @param left The left coordinate of the rectangle to invalidate.
+ * @param top The top coordinate of the rectangle to invalidate.
+ * @param right The right coordinate of the rectangle to invalidate.
+ * @param bottom The bottom coordinate of the rectangle to invalidate.
+ *
+ * @see #invalidate(int, int, int, int)
+ * @see #invalidate(Rect)
+ * @see #postInvalidateDelayed(long, int, int, int, int)
+ */
+ public void postInvalidate(int left, int top, int right, int bottom) {
+ postInvalidateDelayed(0, left, top, right, bottom);
+ }
+
+ /**
+ * <p>Cause an invalidate to happen on a subsequent cycle through the event
+ * loop. Waits for the specified amount of time.</p>
+ *
+ * <p>This method can be invoked from outside of the UI thread
+ * only when this View is attached to a window.</p>
+ *
+ * @param delayMilliseconds the duration in milliseconds to delay the
+ * invalidation by
+ *
+ * @see #invalidate()
+ * @see #postInvalidate()
+ */
+ public void postInvalidateDelayed(long delayMilliseconds) {
+ // We try only with the AttachInfo because there's no point in invalidating
+ // if we are not attached to our window
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
+ }
+ }
+
+ /**
+ * <p>Cause an invalidate of the specified area to happen on a subsequent cycle
+ * through the event loop. Waits for the specified amount of time.</p>
+ *
+ * <p>This method can be invoked from outside of the UI thread
+ * only when this View is attached to a window.</p>
+ *
+ * @param delayMilliseconds the duration in milliseconds to delay the
+ * invalidation by
+ * @param left The left coordinate of the rectangle to invalidate.
+ * @param top The top coordinate of the rectangle to invalidate.
+ * @param right The right coordinate of the rectangle to invalidate.
+ * @param bottom The bottom coordinate of the rectangle to invalidate.
+ *
+ * @see #invalidate(int, int, int, int)
+ * @see #invalidate(Rect)
+ * @see #postInvalidate(int, int, int, int)
+ */
+ public void postInvalidateDelayed(long delayMilliseconds, int left, int top,
+ int right, int bottom) {
+
+ // We try only with the AttachInfo because there's no point in invalidating
+ // if we are not attached to our window
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
+ info.target = this;
+ info.left = left;
+ info.top = top;
+ info.right = right;
+ info.bottom = bottom;
+
+ attachInfo.mViewRootImpl.dispatchInvalidateRectDelayed(info, delayMilliseconds);
+ }
+ }
+
+ /**
+ * <p>Cause an invalidate to happen on the next animation time step, typically the
+ * next display frame.</p>
+ *
+ * <p>This method can be invoked from outside of the UI thread
+ * only when this View is attached to a window.</p>
+ *
+ * @see #invalidate()
+ */
+ public void postInvalidateOnAnimation() {
+ // We try only with the AttachInfo because there's no point in invalidating
+ // if we are not attached to our window
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this);
+ }
+ }
+
+ /**
+ * <p>Cause an invalidate of the specified area to happen on the next animation
+ * time step, typically the next display frame.</p>
+ *
+ * <p>This method can be invoked from outside of the UI thread
+ * only when this View is attached to a window.</p>
+ *
+ * @param left The left coordinate of the rectangle to invalidate.
+ * @param top The top coordinate of the rectangle to invalidate.
+ * @param right The right coordinate of the rectangle to invalidate.
+ * @param bottom The bottom coordinate of the rectangle to invalidate.
+ *
+ * @see #invalidate(int, int, int, int)
+ * @see #invalidate(Rect)
+ */
+ public void postInvalidateOnAnimation(int left, int top, int right, int bottom) {
+ // We try only with the AttachInfo because there's no point in invalidating
+ // if we are not attached to our window
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
+ info.target = this;
+ info.left = left;
+ info.top = top;
+ info.right = right;
+ info.bottom = bottom;
+
+ attachInfo.mViewRootImpl.dispatchInvalidateRectOnAnimation(info);
+ }
+ }
+
+ /**
+ * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+ * This event is sent at most once every
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
+ */
+ private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) {
+ if (mSendViewScrolledAccessibilityEvent == null) {
+ mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+ }
+ mSendViewScrolledAccessibilityEvent.post(dx, dy);
+ }
+
+ /**
+ * Called by a parent to request that a child update its values for mScrollX
+ * and mScrollY if necessary. This will typically be done if the child is
+ * animating a scroll using a {@link android.widget.Scroller Scroller}
+ * object.
+ */
+ public void computeScroll() {
+ }
+
+ /**
+ * <p>Indicate whether the horizontal edges are faded when the view is
+ * scrolled horizontally.</p>
+ *
+ * @return true if the horizontal edges should are faded on scroll, false
+ * otherwise
+ *
+ * @see #setHorizontalFadingEdgeEnabled(boolean)
+ *
+ * @attr ref android.R.styleable#View_requiresFadingEdge
+ */
+ public boolean isHorizontalFadingEdgeEnabled() {
+ return (mViewFlags & FADING_EDGE_HORIZONTAL) == FADING_EDGE_HORIZONTAL;
+ }
+
+ /**
+ * <p>Define whether the horizontal edges should be faded when this view
+ * is scrolled horizontally.</p>
+ *
+ * @param horizontalFadingEdgeEnabled true if the horizontal edges should
+ * be faded when the view is scrolled
+ * horizontally
+ *
+ * @see #isHorizontalFadingEdgeEnabled()
+ *
+ * @attr ref android.R.styleable#View_requiresFadingEdge
+ */
+ public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) {
+ if (isHorizontalFadingEdgeEnabled() != horizontalFadingEdgeEnabled) {
+ if (horizontalFadingEdgeEnabled) {
+ initScrollCache();
+ }
+
+ mViewFlags ^= FADING_EDGE_HORIZONTAL;
+ }
+ }
+
+ /**
+ * <p>Indicate whether the vertical edges are faded when the view is
+ * scrolled horizontally.</p>
+ *
+ * @return true if the vertical edges should are faded on scroll, false
+ * otherwise
+ *
+ * @see #setVerticalFadingEdgeEnabled(boolean)
+ *
+ * @attr ref android.R.styleable#View_requiresFadingEdge
+ */
+ public boolean isVerticalFadingEdgeEnabled() {
+ return (mViewFlags & FADING_EDGE_VERTICAL) == FADING_EDGE_VERTICAL;
+ }
+
+ /**
+ * <p>Define whether the vertical edges should be faded when this view
+ * is scrolled vertically.</p>
+ *
+ * @param verticalFadingEdgeEnabled true if the vertical edges should
+ * be faded when the view is scrolled
+ * vertically
+ *
+ * @see #isVerticalFadingEdgeEnabled()
+ *
+ * @attr ref android.R.styleable#View_requiresFadingEdge
+ */
+ public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) {
+ if (isVerticalFadingEdgeEnabled() != verticalFadingEdgeEnabled) {
+ if (verticalFadingEdgeEnabled) {
+ initScrollCache();
+ }
+
+ mViewFlags ^= FADING_EDGE_VERTICAL;
+ }
+ }
+
+ /**
+ * Returns the strength, or intensity, of the top faded edge. The strength is
+ * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+ * returns 0.0 or 1.0 but no value in between.
+ *
+ * Subclasses should override this method to provide a smoother fade transition
+ * when scrolling occurs.
+ *
+ * @return the intensity of the top fade as a float between 0.0f and 1.0f
+ */
+ protected float getTopFadingEdgeStrength() {
+ return computeVerticalScrollOffset() > 0 ? 1.0f : 0.0f;
+ }
+
+ /**
+ * Returns the strength, or intensity, of the bottom faded edge. The strength is
+ * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+ * returns 0.0 or 1.0 but no value in between.
+ *
+ * Subclasses should override this method to provide a smoother fade transition
+ * when scrolling occurs.
+ *
+ * @return the intensity of the bottom fade as a float between 0.0f and 1.0f
+ */
+ protected float getBottomFadingEdgeStrength() {
+ return computeVerticalScrollOffset() + computeVerticalScrollExtent() <
+ computeVerticalScrollRange() ? 1.0f : 0.0f;
+ }
+
+ /**
+ * Returns the strength, or intensity, of the left faded edge. The strength is
+ * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+ * returns 0.0 or 1.0 but no value in between.
+ *
+ * Subclasses should override this method to provide a smoother fade transition
+ * when scrolling occurs.
+ *
+ * @return the intensity of the left fade as a float between 0.0f and 1.0f
+ */
+ protected float getLeftFadingEdgeStrength() {
+ return computeHorizontalScrollOffset() > 0 ? 1.0f : 0.0f;
+ }
+
+ /**
+ * Returns the strength, or intensity, of the right faded edge. The strength is
+ * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation
+ * returns 0.0 or 1.0 but no value in between.
+ *
+ * Subclasses should override this method to provide a smoother fade transition
+ * when scrolling occurs.
+ *
+ * @return the intensity of the right fade as a float between 0.0f and 1.0f
+ */
+ protected float getRightFadingEdgeStrength() {
+ return computeHorizontalScrollOffset() + computeHorizontalScrollExtent() <
+ computeHorizontalScrollRange() ? 1.0f : 0.0f;
+ }
+
+ /**
+ * <p>Indicate whether the horizontal scrollbar should be drawn or not. The
+ * scrollbar is not drawn by default.</p>
+ *
+ * @return true if the horizontal scrollbar should be painted, false
+ * otherwise
+ *
+ * @see #setHorizontalScrollBarEnabled(boolean)
+ */
+ public boolean isHorizontalScrollBarEnabled() {
+ return (mViewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
+ }
+
+ /**
+ * <p>Define whether the horizontal scrollbar should be drawn or not. The
+ * scrollbar is not drawn by default.</p>
+ *
+ * @param horizontalScrollBarEnabled true if the horizontal scrollbar should
+ * be painted
+ *
+ * @see #isHorizontalScrollBarEnabled()
+ */
+ public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
+ if (isHorizontalScrollBarEnabled() != horizontalScrollBarEnabled) {
+ mViewFlags ^= SCROLLBARS_HORIZONTAL;
+ computeOpaqueFlags();
+ resolvePadding();
+ }
+ }
+
+ /**
+ * <p>Indicate whether the vertical scrollbar should be drawn or not. The
+ * scrollbar is not drawn by default.</p>
+ *
+ * @return true if the vertical scrollbar should be painted, false
+ * otherwise
+ *
+ * @see #setVerticalScrollBarEnabled(boolean)
+ */
+ public boolean isVerticalScrollBarEnabled() {
+ return (mViewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL;
+ }
+
+ /**
+ * <p>Define whether the vertical scrollbar should be drawn or not. The
+ * scrollbar is not drawn by default.</p>
+ *
+ * @param verticalScrollBarEnabled true if the vertical scrollbar should
+ * be painted
+ *
+ * @see #isVerticalScrollBarEnabled()
+ */
+ public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
+ if (isVerticalScrollBarEnabled() != verticalScrollBarEnabled) {
+ mViewFlags ^= SCROLLBARS_VERTICAL;
+ computeOpaqueFlags();
+ resolvePadding();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected void recomputePadding() {
+ internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);
+ }
+
+ /**
+ * Define whether scrollbars will fade when the view is not scrolling.
+ *
+ * @param fadeScrollbars whether to enable fading
+ *
+ * @attr ref android.R.styleable#View_fadeScrollbars
+ */
+ public void setScrollbarFadingEnabled(boolean fadeScrollbars) {
+ initScrollCache();
+ final ScrollabilityCache scrollabilityCache = mScrollCache;
+ scrollabilityCache.fadeScrollBars = fadeScrollbars;
+ if (fadeScrollbars) {
+ scrollabilityCache.state = ScrollabilityCache.OFF;
+ } else {
+ scrollabilityCache.state = ScrollabilityCache.ON;
+ }
+ }
+
+ /**
+ *
+ * Returns true if scrollbars will fade when this view is not scrolling
+ *
+ * @return true if scrollbar fading is enabled
+ *
+ * @attr ref android.R.styleable#View_fadeScrollbars
+ */
+ public boolean isScrollbarFadingEnabled() {
+ return mScrollCache != null && mScrollCache.fadeScrollBars;
+ }
+
+ /**
+ *
+ * Returns the delay before scrollbars fade.
+ *
+ * @return the delay before scrollbars fade
+ *
+ * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade
+ */
+ public int getScrollBarDefaultDelayBeforeFade() {
+ return mScrollCache == null ? ViewConfiguration.getScrollDefaultDelay() :
+ mScrollCache.scrollBarDefaultDelayBeforeFade;
+ }
+
+ /**
+ * Define the delay before scrollbars fade.
+ *
+ * @param scrollBarDefaultDelayBeforeFade - the delay before scrollbars fade
+ *
+ * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade
+ */
+ public void setScrollBarDefaultDelayBeforeFade(int scrollBarDefaultDelayBeforeFade) {
+ getScrollCache().scrollBarDefaultDelayBeforeFade = scrollBarDefaultDelayBeforeFade;
+ }
+
+ /**
+ *
+ * Returns the scrollbar fade duration.
+ *
+ * @return the scrollbar fade duration, in milliseconds
+ *
+ * @attr ref android.R.styleable#View_scrollbarFadeDuration
+ */
+ public int getScrollBarFadeDuration() {
+ return mScrollCache == null ? ViewConfiguration.getScrollBarFadeDuration() :
+ mScrollCache.scrollBarFadeDuration;
+ }
+
+ /**
+ * Define the scrollbar fade duration.
+ *
+ * @param scrollBarFadeDuration - the scrollbar fade duration, in milliseconds
+ *
+ * @attr ref android.R.styleable#View_scrollbarFadeDuration
+ */
+ public void setScrollBarFadeDuration(int scrollBarFadeDuration) {
+ getScrollCache().scrollBarFadeDuration = scrollBarFadeDuration;
+ }
+
+ /**
+ *
+ * Returns the scrollbar size.
+ *
+ * @return the scrollbar size
+ *
+ * @attr ref android.R.styleable#View_scrollbarSize
+ */
+ public int getScrollBarSize() {
+ return mScrollCache == null ? ViewConfiguration.get(mContext).getScaledScrollBarSize() :
+ mScrollCache.scrollBarSize;
+ }
+
+ /**
+ * Define the scrollbar size.
+ *
+ * @param scrollBarSize - the scrollbar size
+ *
+ * @attr ref android.R.styleable#View_scrollbarSize
+ */
+ public void setScrollBarSize(int scrollBarSize) {
+ getScrollCache().scrollBarSize = scrollBarSize;
+ }
+
+ /**
+ * <p>Specify the style of the scrollbars. The scrollbars can be overlaid or
+ * inset. When inset, they add to the padding of the view. And the scrollbars
+ * can be drawn inside the padding area or on the edge of the view. For example,
+ * if a view has a background drawable and you want to draw the scrollbars
+ * inside the padding specified by the drawable, you can use
+ * SCROLLBARS_INSIDE_OVERLAY or SCROLLBARS_INSIDE_INSET. If you want them to
+ * appear at the edge of the view, ignoring the padding, then you can use
+ * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.</p>
+ * @param style the style of the scrollbars. Should be one of
+ * SCROLLBARS_INSIDE_OVERLAY, SCROLLBARS_INSIDE_INSET,
+ * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.
+ * @see #SCROLLBARS_INSIDE_OVERLAY
+ * @see #SCROLLBARS_INSIDE_INSET
+ * @see #SCROLLBARS_OUTSIDE_OVERLAY
+ * @see #SCROLLBARS_OUTSIDE_INSET
+ *
+ * @attr ref android.R.styleable#View_scrollbarStyle
+ */
+ public void setScrollBarStyle(@ScrollBarStyle int style) {
+ if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
+ mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK);
+ computeOpaqueFlags();
+ resolvePadding();
+ }
+ }
+
+ /**
+ * <p>Returns the current scrollbar style.</p>
+ * @return the current scrollbar style
+ * @see #SCROLLBARS_INSIDE_OVERLAY
+ * @see #SCROLLBARS_INSIDE_INSET
+ * @see #SCROLLBARS_OUTSIDE_OVERLAY
+ * @see #SCROLLBARS_OUTSIDE_INSET
+ *
+ * @attr ref android.R.styleable#View_scrollbarStyle
+ */
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_OVERLAY, to = "INSIDE_OVERLAY"),
+ @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_INSET, to = "INSIDE_INSET"),
+ @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_OVERLAY, to = "OUTSIDE_OVERLAY"),
+ @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_INSET, to = "OUTSIDE_INSET")
+ })
+ @ScrollBarStyle
+ public int getScrollBarStyle() {
+ return mViewFlags & SCROLLBARS_STYLE_MASK;
+ }
+
+ /**
+ * <p>Compute the horizontal range that the horizontal scrollbar
+ * represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollExtent()} and
+ * {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>The default range is the drawing width of this view.</p>
+ *
+ * @return the total horizontal range represented by the horizontal
+ * scrollbar
+ *
+ * @see #computeHorizontalScrollExtent()
+ * @see #computeHorizontalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ protected int computeHorizontalScrollRange() {
+ return getWidth();
+ }
+
+ /**
+ * <p>Compute the horizontal offset of the horizontal scrollbar's thumb
+ * within the horizontal range. This value is used to compute the position
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollRange()} and
+ * {@link #computeHorizontalScrollExtent()}.</p>
+ *
+ * <p>The default offset is the scroll offset of this view.</p>
+ *
+ * @return the horizontal offset of the scrollbar's thumb
+ *
+ * @see #computeHorizontalScrollRange()
+ * @see #computeHorizontalScrollExtent()
+ * @see android.widget.ScrollBarDrawable
+ */
+ protected int computeHorizontalScrollOffset() {
+ return mScrollX;
+ }
+
+ /**
+ * <p>Compute the horizontal extent of the horizontal scrollbar's thumb
+ * within the horizontal range. This value is used to compute the length
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollRange()} and
+ * {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>The default extent is the drawing width of this view.</p>
+ *
+ * @return the horizontal extent of the scrollbar's thumb
+ *
+ * @see #computeHorizontalScrollRange()
+ * @see #computeHorizontalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ protected int computeHorizontalScrollExtent() {
+ return getWidth();
+ }
+
+ /**
+ * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollExtent()} and
+ * {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * @return the total vertical range represented by the vertical scrollbar
+ *
+ * <p>The default range is the drawing height of this view.</p>
+ *
+ * @see #computeVerticalScrollExtent()
+ * @see #computeVerticalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ protected int computeVerticalScrollRange() {
+ return getHeight();
+ }
+
+ /**
+ * <p>Compute the vertical offset of the vertical scrollbar's thumb
+ * within the horizontal range. This value is used to compute the position
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollRange()} and
+ * {@link #computeVerticalScrollExtent()}.</p>
+ *
+ * <p>The default offset is the scroll offset of this view.</p>
+ *
+ * @return the vertical offset of the scrollbar's thumb
+ *
+ * @see #computeVerticalScrollRange()
+ * @see #computeVerticalScrollExtent()
+ * @see android.widget.ScrollBarDrawable
+ */
+ protected int computeVerticalScrollOffset() {
+ return mScrollY;
+ }
+
+ /**
+ * <p>Compute the vertical extent of the vertical scrollbar's thumb
+ * within the vertical range. This value is used to compute the length
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollRange()} and
+ * {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * <p>The default extent is the drawing height of this view.</p>
+ *
+ * @return the vertical extent of the scrollbar's thumb
+ *
+ * @see #computeVerticalScrollRange()
+ * @see #computeVerticalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ protected int computeVerticalScrollExtent() {
+ return getHeight();
+ }
+
+ /**
+ * Check if this view can be scrolled horizontally in a certain direction.
+ *
+ * @param direction Negative to check scrolling left, positive to check scrolling right.
+ * @return true if this view can be scrolled in the specified direction, false otherwise.
+ */
+ public boolean canScrollHorizontally(int direction) {
+ final int offset = computeHorizontalScrollOffset();
+ final int range = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
+ if (range == 0) return false;
+ if (direction < 0) {
+ return offset > 0;
+ } else {
+ return offset < range - 1;
+ }
+ }
+
+ /**
+ * Check if this view can be scrolled vertically in a certain direction.
+ *
+ * @param direction Negative to check scrolling up, positive to check scrolling down.
+ * @return true if this view can be scrolled in the specified direction, false otherwise.
+ */
+ public boolean canScrollVertically(int direction) {
+ final int offset = computeVerticalScrollOffset();
+ final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
+ if (range == 0) return false;
+ if (direction < 0) {
+ return offset > 0;
+ } else {
+ return offset < range - 1;
+ }
+ }
+
+ void getScrollIndicatorBounds(@NonNull Rect out) {
+ out.left = mScrollX;
+ out.right = mScrollX + mRight - mLeft;
+ out.top = mScrollY;
+ out.bottom = mScrollY + mBottom - mTop;
+ }
+
+ private void onDrawScrollIndicators(Canvas c) {
+ if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) {
+ // No scroll indicators enabled.
+ return;
+ }
+
+ final Drawable dr = mScrollIndicatorDrawable;
+ if (dr == null) {
+ // Scroll indicators aren't supported here.
+ return;
+ }
+
+ final int h = dr.getIntrinsicHeight();
+ final int w = dr.getIntrinsicWidth();
+ final Rect rect = mAttachInfo.mTmpInvalRect;
+ getScrollIndicatorBounds(rect);
+
+ if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_TOP) != 0) {
+ final boolean canScrollUp = canScrollVertically(-1);
+ if (canScrollUp) {
+ dr.setBounds(rect.left, rect.top, rect.right, rect.top + h);
+ dr.draw(c);
+ }
+ }
+
+ if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_BOTTOM) != 0) {
+ final boolean canScrollDown = canScrollVertically(1);
+ if (canScrollDown) {
+ dr.setBounds(rect.left, rect.bottom - h, rect.right, rect.bottom);
+ dr.draw(c);
+ }
+ }
+
+ final int leftRtl;
+ final int rightRtl;
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ leftRtl = PFLAG3_SCROLL_INDICATOR_END;
+ rightRtl = PFLAG3_SCROLL_INDICATOR_START;
+ } else {
+ leftRtl = PFLAG3_SCROLL_INDICATOR_START;
+ rightRtl = PFLAG3_SCROLL_INDICATOR_END;
+ }
+
+ final int leftMask = PFLAG3_SCROLL_INDICATOR_LEFT | leftRtl;
+ if ((mPrivateFlags3 & leftMask) != 0) {
+ final boolean canScrollLeft = canScrollHorizontally(-1);
+ if (canScrollLeft) {
+ dr.setBounds(rect.left, rect.top, rect.left + w, rect.bottom);
+ dr.draw(c);
+ }
+ }
+
+ final int rightMask = PFLAG3_SCROLL_INDICATOR_RIGHT | rightRtl;
+ if ((mPrivateFlags3 & rightMask) != 0) {
+ final boolean canScrollRight = canScrollHorizontally(1);
+ if (canScrollRight) {
+ dr.setBounds(rect.right - w, rect.top, rect.right, rect.bottom);
+ dr.draw(c);
+ }
+ }
+ }
+
+ private void getHorizontalScrollBarBounds(@Nullable Rect drawBounds,
+ @Nullable Rect touchBounds) {
+ final Rect bounds = drawBounds != null ? drawBounds : touchBounds;
+ if (bounds == null) {
+ return;
+ }
+ final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
+ final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
+ && !isVerticalScrollBarHidden();
+ final int size = getHorizontalScrollbarHeight();
+ final int verticalScrollBarGap = drawVerticalScrollBar ?
+ getVerticalScrollbarWidth() : 0;
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+ bounds.top = mScrollY + height - size - (mUserPaddingBottom & inside);
+ bounds.left = mScrollX + (mPaddingLeft & inside);
+ bounds.right = mScrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
+ bounds.bottom = bounds.top + size;
+
+ if (touchBounds == null) {
+ return;
+ }
+ if (touchBounds != bounds) {
+ touchBounds.set(bounds);
+ }
+ final int minTouchTarget = mScrollCache.scrollBarMinTouchTarget;
+ if (touchBounds.height() < minTouchTarget) {
+ final int adjust = (minTouchTarget - touchBounds.height()) / 2;
+ touchBounds.bottom = Math.min(touchBounds.bottom + adjust, mScrollY + height);
+ touchBounds.top = touchBounds.bottom - minTouchTarget;
+ }
+ if (touchBounds.width() < minTouchTarget) {
+ final int adjust = (minTouchTarget - touchBounds.width()) / 2;
+ touchBounds.left -= adjust;
+ touchBounds.right = touchBounds.left + minTouchTarget;
+ }
+ }
+
+ private void getVerticalScrollBarBounds(@Nullable Rect bounds, @Nullable Rect touchBounds) {
+ if (mRoundScrollbarRenderer == null) {
+ getStraightVerticalScrollBarBounds(bounds, touchBounds);
+ } else {
+ getRoundVerticalScrollBarBounds(bounds != null ? bounds : touchBounds);
+ }
+ }
+
+ private void getRoundVerticalScrollBarBounds(Rect bounds) {
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+ // Do not take padding into account as we always want the scrollbars
+ // to hug the screen for round wearable devices.
+ bounds.left = mScrollX;
+ bounds.top = mScrollY;
+ bounds.right = bounds.left + width;
+ bounds.bottom = mScrollY + height;
+ }
+
+ private void getStraightVerticalScrollBarBounds(@Nullable Rect drawBounds,
+ @Nullable Rect touchBounds) {
+ final Rect bounds = drawBounds != null ? drawBounds : touchBounds;
+ if (bounds == null) {
+ return;
+ }
+ final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
+ final int size = getVerticalScrollbarWidth();
+ int verticalScrollbarPosition = mVerticalScrollbarPosition;
+ if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {
+ verticalScrollbarPosition = isLayoutRtl() ?
+ SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;
+ }
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+ switch (verticalScrollbarPosition) {
+ default:
+ case SCROLLBAR_POSITION_RIGHT:
+ bounds.left = mScrollX + width - size - (mUserPaddingRight & inside);
+ break;
+ case SCROLLBAR_POSITION_LEFT:
+ bounds.left = mScrollX + (mUserPaddingLeft & inside);
+ break;
+ }
+ bounds.top = mScrollY + (mPaddingTop & inside);
+ bounds.right = bounds.left + size;
+ bounds.bottom = mScrollY + height - (mUserPaddingBottom & inside);
+
+ if (touchBounds == null) {
+ return;
+ }
+ if (touchBounds != bounds) {
+ touchBounds.set(bounds);
+ }
+ final int minTouchTarget = mScrollCache.scrollBarMinTouchTarget;
+ if (touchBounds.width() < minTouchTarget) {
+ final int adjust = (minTouchTarget - touchBounds.width()) / 2;
+ if (verticalScrollbarPosition == SCROLLBAR_POSITION_RIGHT) {
+ touchBounds.right = Math.min(touchBounds.right + adjust, mScrollX + width);
+ touchBounds.left = touchBounds.right - minTouchTarget;
+ } else {
+ touchBounds.left = Math.max(touchBounds.left + adjust, mScrollX);
+ touchBounds.right = touchBounds.left + minTouchTarget;
+ }
+ }
+ if (touchBounds.height() < minTouchTarget) {
+ final int adjust = (minTouchTarget - touchBounds.height()) / 2;
+ touchBounds.top -= adjust;
+ touchBounds.bottom = touchBounds.top + minTouchTarget;
+ }
+ }
+
+ /**
+ * <p>Request the drawing of the horizontal and the vertical scrollbar. The
+ * scrollbars are painted only if they have been awakened first.</p>
+ *
+ * @param canvas the canvas on which to draw the scrollbars
+ *
+ * @see #awakenScrollBars(int)
+ */
+ protected final void onDrawScrollBars(Canvas canvas) {
+ // scrollbars are drawn only when the animation is running
+ final ScrollabilityCache cache = mScrollCache;
+
+ if (cache != null) {
+
+ int state = cache.state;
+
+ if (state == ScrollabilityCache.OFF) {
+ return;
+ }
+
+ boolean invalidate = false;
+
+ if (state == ScrollabilityCache.FADING) {
+ // We're fading -- get our fade interpolation
+ if (cache.interpolatorValues == null) {
+ cache.interpolatorValues = new float[1];
+ }
+
+ float[] values = cache.interpolatorValues;
+
+ // Stops the animation if we're done
+ if (cache.scrollBarInterpolator.timeToValues(values) ==
+ Interpolator.Result.FREEZE_END) {
+ cache.state = ScrollabilityCache.OFF;
+ } else {
+ cache.scrollBar.mutate().setAlpha(Math.round(values[0]));
+ }
+
+ // This will make the scroll bars inval themselves after
+ // drawing. We only want this when we're fading so that
+ // we prevent excessive redraws
+ invalidate = true;
+ } else {
+ // We're just on -- but we may have been fading before so
+ // reset alpha
+ cache.scrollBar.mutate().setAlpha(255);
+ }
+
+ final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled();
+ final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
+ && !isVerticalScrollBarHidden();
+
+ // Fork out the scroll bar drawing for round wearable devices.
+ if (mRoundScrollbarRenderer != null) {
+ if (drawVerticalScrollBar) {
+ final Rect bounds = cache.mScrollBarBounds;
+ getVerticalScrollBarBounds(bounds, null);
+ mRoundScrollbarRenderer.drawRoundScrollbars(
+ canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds);
+ if (invalidate) {
+ invalidate();
+ }
+ }
+ // Do not draw horizontal scroll bars for round wearable devices.
+ } else if (drawVerticalScrollBar || drawHorizontalScrollBar) {
+ final ScrollBarDrawable scrollBar = cache.scrollBar;
+
+ if (drawHorizontalScrollBar) {
+ scrollBar.setParameters(computeHorizontalScrollRange(),
+ computeHorizontalScrollOffset(),
+ computeHorizontalScrollExtent(), false);
+ final Rect bounds = cache.mScrollBarBounds;
+ getHorizontalScrollBarBounds(bounds, null);
+ onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
+ bounds.right, bounds.bottom);
+ if (invalidate) {
+ invalidate(bounds);
+ }
+ }
+
+ if (drawVerticalScrollBar) {
+ scrollBar.setParameters(computeVerticalScrollRange(),
+ computeVerticalScrollOffset(),
+ computeVerticalScrollExtent(), true);
+ final Rect bounds = cache.mScrollBarBounds;
+ getVerticalScrollBarBounds(bounds, null);
+ onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
+ bounds.right, bounds.bottom);
+ if (invalidate) {
+ invalidate(bounds);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Override this if the vertical scrollbar needs to be hidden in a subclass, like when
+ * FastScroller is visible.
+ * @return whether to temporarily hide the vertical scrollbar
+ * @hide
+ */
+ protected boolean isVerticalScrollBarHidden() {
+ return false;
+ }
+
+ /**
+ * <p>Draw the horizontal scrollbar if
+ * {@link #isHorizontalScrollBarEnabled()} returns true.</p>
+ *
+ * @param canvas the canvas on which to draw the scrollbar
+ * @param scrollBar the scrollbar's drawable
+ *
+ * @see #isHorizontalScrollBarEnabled()
+ * @see #computeHorizontalScrollRange()
+ * @see #computeHorizontalScrollExtent()
+ * @see #computeHorizontalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ * @hide
+ */
+ protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+ int l, int t, int r, int b) {
+ scrollBar.setBounds(l, t, r, b);
+ scrollBar.draw(canvas);
+ }
+
+ /**
+ * <p>Draw the vertical scrollbar if {@link #isVerticalScrollBarEnabled()}
+ * returns true.</p>
+ *
+ * @param canvas the canvas on which to draw the scrollbar
+ * @param scrollBar the scrollbar's drawable
+ *
+ * @see #isVerticalScrollBarEnabled()
+ * @see #computeVerticalScrollRange()
+ * @see #computeVerticalScrollExtent()
+ * @see #computeVerticalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ * @hide
+ */
+ protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+ int l, int t, int r, int b) {
+ scrollBar.setBounds(l, t, r, b);
+ scrollBar.draw(canvas);
+ }
+
+ /**
+ * Implement this to do your drawing.
+ *
+ * @param canvas the canvas on which the background will be drawn
+ */
+ protected void onDraw(Canvas canvas) {
+ }
+
+ /*
+ * Caller is responsible for calling requestLayout if necessary.
+ * (This allows addViewInLayout to not request a new layout.)
+ */
+ void assignParent(ViewParent parent) {
+ if (mParent == null) {
+ mParent = parent;
+ } else if (parent == null) {
+ mParent = null;
+ } else {
+ throw new RuntimeException("view " + this + " being added, but"
+ + " it already has a parent");
+ }
+ }
+
+ /**
+ * This is called when the view is attached to a window. At this point it
+ * has a Surface and will start drawing. Note that this function is
+ * guaranteed to be called before {@link #onDraw(android.graphics.Canvas)},
+ * however it may be called any time before the first onDraw -- including
+ * before or after {@link #onMeasure(int, int)}.
+ *
+ * @see #onDetachedFromWindow()
+ */
+ @CallSuper
+ protected void onAttachedToWindow() {
+ if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
+ mParent.requestTransparentRegion(this);
+ }
+
+ mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+
+ jumpDrawablesToCurrentState();
+
+ resetSubtreeAccessibilityStateChanged();
+
+ // rebuild, since Outline not maintained while View is detached
+ rebuildOutline();
+
+ if (isFocused()) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.focusIn(this);
+ }
+ }
+ }
+
+ /**
+ * Resolve all RTL related properties.
+ *
+ * @return true if resolution of RTL properties has been done
+ *
+ * @hide
+ */
+ public boolean resolveRtlPropertiesIfNeeded() {
+ if (!needRtlPropertiesResolution()) return false;
+
+ // Order is important here: LayoutDirection MUST be resolved first
+ if (!isLayoutDirectionResolved()) {
+ resolveLayoutDirection();
+ resolveLayoutParams();
+ }
+ // ... then we can resolve the others properties depending on the resolved LayoutDirection.
+ if (!isTextDirectionResolved()) {
+ resolveTextDirection();
+ }
+ if (!isTextAlignmentResolved()) {
+ resolveTextAlignment();
+ }
+ // Should resolve Drawables before Padding because we need the layout direction of the
+ // Drawable to correctly resolve Padding.
+ if (!areDrawablesResolved()) {
+ resolveDrawables();
+ }
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ onRtlPropertiesChanged(getLayoutDirection());
+ return true;
+ }
+
+ /**
+ * Reset resolution of all RTL related properties.
+ *
+ * @hide
+ */
+ public void resetRtlProperties() {
+ resetResolvedLayoutDirection();
+ resetResolvedTextDirection();
+ resetResolvedTextAlignment();
+ resetResolvedPadding();
+ resetResolvedDrawables();
+ }
+
+ /**
+ * @see #onScreenStateChanged(int)
+ */
+ void dispatchScreenStateChanged(int screenState) {
+ onScreenStateChanged(screenState);
+ }
+
+ /**
+ * This method is called whenever the state of the screen this view is
+ * attached to changes. A state change will usually occurs when the screen
+ * turns on or off (whether it happens automatically or the user does it
+ * manually.)
+ *
+ * @param screenState The new state of the screen. Can be either
+ * {@link #SCREEN_STATE_ON} or {@link #SCREEN_STATE_OFF}
+ */
+ public void onScreenStateChanged(int screenState) {
+ }
+
+ /**
+ * @see #onMovedToDisplay(int, Configuration)
+ */
+ void dispatchMovedToDisplay(Display display, Configuration config) {
+ mAttachInfo.mDisplay = display;
+ mAttachInfo.mDisplayState = display.getState();
+ onMovedToDisplay(display.getDisplayId(), config);
+ }
+
+ /**
+ * Called by the system when the hosting activity is moved from one display to another without
+ * recreation. This means that the activity is declared to handle all changes to configuration
+ * that happened when it was switched to another display, so it wasn't destroyed and created
+ * again.
+ *
+ * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+ * applied configuration actually changed. It is up to app developer to choose whether to handle
+ * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+ * call.
+ *
+ * <p>Use this callback to track changes to the displays if some functionality relies on an
+ * association with some display properties.
+ *
+ * @param displayId The id of the display to which the view was moved.
+ * @param config Configuration of the resources on new display after move.
+ *
+ * @see #onConfigurationChanged(Configuration)
+ * @hide
+ */
+ public void onMovedToDisplay(int displayId, Configuration config) {
+ }
+
+ /**
+ * Return true if the application tag in the AndroidManifest has set "supportRtl" to true
+ */
+ private boolean hasRtlSupport() {
+ return mContext.getApplicationInfo().hasRtlSupport();
+ }
+
+ /**
+ * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
+ * RTL not supported)
+ */
+ private boolean isRtlCompatibilityMode() {
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ return targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1 || !hasRtlSupport();
+ }
+
+ /**
+ * @return true if RTL properties need resolution.
+ *
+ */
+ private boolean needRtlPropertiesResolution() {
+ return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED;
+ }
+
+ /**
+ * Called when any RTL property (layout direction or text direction or text alignment) has
+ * been changed.
+ *
+ * Subclasses need to override this method to take care of cached information that depends on the
+ * resolved layout direction, or to inform child views that inherit their layout direction.
+ *
+ * The default implementation does nothing.
+ *
+ * @param layoutDirection the direction of the layout
+ *
+ * @see #LAYOUT_DIRECTION_LTR
+ * @see #LAYOUT_DIRECTION_RTL
+ */
+ public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+ }
+
+ /**
+ * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
+ * that the parent directionality can and will be resolved before its children.
+ *
+ * @return true if resolution has been done, false otherwise.
+ *
+ * @hide
+ */
+ public boolean resolveLayoutDirection() {
+ // Clear any previous layout direction resolution
+ mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
+
+ if (hasRtlSupport()) {
+ // Set resolved depending on layout direction
+ switch ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >>
+ PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) {
+ case LAYOUT_DIRECTION_INHERIT:
+ // We cannot resolve yet. LTR is by default and let the resolution happen again
+ // later to get the correct resolved value
+ if (!canResolveLayoutDirection()) return false;
+
+ // Parent has not yet resolved, LTR is still the default
+ try {
+ if (!mParent.isLayoutDirectionResolved()) return false;
+
+ if (mParent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
+ }
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ break;
+ case LAYOUT_DIRECTION_RTL:
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
+ break;
+ case LAYOUT_DIRECTION_LOCALE:
+ if((LAYOUT_DIRECTION_RTL ==
+ TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()))) {
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
+ }
+ break;
+ default:
+ // Nothing to do, LTR by default
+ }
+ }
+
+ // Set to resolved
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ return true;
+ }
+
+ /**
+ * Check if layout direction resolution can be done.
+ *
+ * @return true if layout direction resolution can be done otherwise return false.
+ */
+ public boolean canResolveLayoutDirection() {
+ switch (getRawLayoutDirection()) {
+ case LAYOUT_DIRECTION_INHERIT:
+ if (mParent != null) {
+ try {
+ return mParent.canResolveLayoutDirection();
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Reset the resolved layout direction. Layout direction will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
+ *
+ * @hide
+ */
+ public void resetResolvedLayoutDirection() {
+ // Reset the current resolved bits
+ mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
+ }
+
+ /**
+ * @return true if the layout direction is inherited.
+ *
+ * @hide
+ */
+ public boolean isLayoutDirectionInherited() {
+ return (getRawLayoutDirection() == LAYOUT_DIRECTION_INHERIT);
+ }
+
+ /**
+ * @return true if layout direction has been resolved.
+ */
+ public boolean isLayoutDirectionResolved() {
+ return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ }
+
+ /**
+ * Return if padding has been resolved
+ *
+ * @hide
+ */
+ boolean isPaddingResolved() {
+ return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) == PFLAG2_PADDING_RESOLVED;
+ }
+
+ /**
+ * Resolves padding depending on layout direction, if applicable, and
+ * recomputes internal padding values to adjust for scroll bars.
+ *
+ * @hide
+ */
+ public void resolvePadding() {
+ final int resolvedLayoutDirection = getLayoutDirection();
+
+ if (!isRtlCompatibilityMode()) {
+ // Post Jelly Bean MR1 case: we need to take the resolved layout direction into account.
+ // If start / end padding are defined, they will be resolved (hence overriding) to
+ // left / right or right / left depending on the resolved layout direction.
+ // If start / end padding are not defined, use the left / right ones.
+ if (mBackground != null && (!mLeftPaddingDefined || !mRightPaddingDefined)) {
+ Rect padding = sThreadLocal.get();
+ if (padding == null) {
+ padding = new Rect();
+ sThreadLocal.set(padding);
+ }
+ mBackground.getPadding(padding);
+ if (!mLeftPaddingDefined) {
+ mUserPaddingLeftInitial = padding.left;
+ }
+ if (!mRightPaddingDefined) {
+ mUserPaddingRightInitial = padding.right;
+ }
+ }
+ switch (resolvedLayoutDirection) {
+ case LAYOUT_DIRECTION_RTL:
+ if (mUserPaddingStart != UNDEFINED_PADDING) {
+ mUserPaddingRight = mUserPaddingStart;
+ } else {
+ mUserPaddingRight = mUserPaddingRightInitial;
+ }
+ if (mUserPaddingEnd != UNDEFINED_PADDING) {
+ mUserPaddingLeft = mUserPaddingEnd;
+ } else {
+ mUserPaddingLeft = mUserPaddingLeftInitial;
+ }
+ break;
+ case LAYOUT_DIRECTION_LTR:
+ default:
+ if (mUserPaddingStart != UNDEFINED_PADDING) {
+ mUserPaddingLeft = mUserPaddingStart;
+ } else {
+ mUserPaddingLeft = mUserPaddingLeftInitial;
+ }
+ if (mUserPaddingEnd != UNDEFINED_PADDING) {
+ mUserPaddingRight = mUserPaddingEnd;
+ } else {
+ mUserPaddingRight = mUserPaddingRightInitial;
+ }
+ }
+
+ mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom;
+ }
+
+ internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);
+ onRtlPropertiesChanged(resolvedLayoutDirection);
+
+ mPrivateFlags2 |= PFLAG2_PADDING_RESOLVED;
+ }
+
+ /**
+ * Reset the resolved layout direction.
+ *
+ * @hide
+ */
+ public void resetResolvedPadding() {
+ resetResolvedPaddingInternal();
+ }
+
+ /**
+ * Used when we only want to reset *this* view's padding and not trigger overrides
+ * in ViewGroup that reset children too.
+ */
+ void resetResolvedPaddingInternal() {
+ mPrivateFlags2 &= ~PFLAG2_PADDING_RESOLVED;
+ }
+
+ /**
+ * This is called when the view is detached from a window. At this point it
+ * no longer has a surface for drawing.
+ *
+ * @see #onAttachedToWindow()
+ */
+ @CallSuper
+ protected void onDetachedFromWindow() {
+ }
+
+ /**
+ * This is a framework-internal mirror of onDetachedFromWindow() that's called
+ * after onDetachedFromWindow().
+ *
+ * If you override this you *MUST* call super.onDetachedFromWindowInternal()!
+ * The super method should be called at the end of the overridden method to ensure
+ * subclasses are destroyed first
+ *
+ * @hide
+ */
+ @CallSuper
+ protected void onDetachedFromWindowInternal() {
+ mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
+ mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
+
+ removeUnsetPressCallback();
+ removeLongPressCallback();
+ removePerformClickCallback();
+ cancel(mSendViewScrolledAccessibilityEvent);
+ stopNestedScroll();
+
+ // Anything that started animating right before detach should already
+ // be in its final state when re-attached.
+ jumpDrawablesToCurrentState();
+
+ destroyDrawingCache();
+
+ cleanupDraw();
+ mCurrentAnimation = null;
+
+ if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+ hideTooltip();
+ }
+ }
+
+ private void cleanupDraw() {
+ resetDisplayList();
+ if (mAttachInfo != null) {
+ mAttachInfo.mViewRootImpl.cancelInvalidate(this);
+ }
+ }
+
+ void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
+ }
+
+ /**
+ * @return The number of times this view has been attached to a window
+ */
+ protected int getWindowAttachCount() {
+ return mWindowAttachCount;
+ }
+
+ /**
+ * Retrieve a unique token identifying the window this view is attached to.
+ * @return Return the window's token for use in
+ * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}.
+ */
+ public IBinder getWindowToken() {
+ return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
+ }
+
+ /**
+ * Retrieve the {@link WindowId} for the window this view is
+ * currently attached to.
+ */
+ public WindowId getWindowId() {
+ if (mAttachInfo == null) {
+ return null;
+ }
+ if (mAttachInfo.mWindowId == null) {
+ try {
+ mAttachInfo.mIWindowId = mAttachInfo.mSession.getWindowId(
+ mAttachInfo.mWindowToken);
+ mAttachInfo.mWindowId = new WindowId(
+ mAttachInfo.mIWindowId);
+ } catch (RemoteException e) {
+ }
+ }
+ return mAttachInfo.mWindowId;
+ }
+
+ /**
+ * Retrieve a unique token identifying the top-level "real" window of
+ * the window that this view is attached to. That is, this is like
+ * {@link #getWindowToken}, except if the window this view in is a panel
+ * window (attached to another containing window), then the token of
+ * the containing window is returned instead.
+ *
+ * @return Returns the associated window token, either
+ * {@link #getWindowToken()} or the containing window's token.
+ */
+ public IBinder getApplicationWindowToken() {
+ AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ IBinder appWindowToken = ai.mPanelParentWindowToken;
+ if (appWindowToken == null) {
+ appWindowToken = ai.mWindowToken;
+ }
+ return appWindowToken;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the logical display to which the view's window has been attached.
+ *
+ * @return The logical display, or null if the view is not currently attached to a window.
+ */
+ public Display getDisplay() {
+ return mAttachInfo != null ? mAttachInfo.mDisplay : null;
+ }
+
+ /**
+ * Retrieve private session object this view hierarchy is using to
+ * communicate with the window manager.
+ * @return the session object to communicate with the window manager
+ */
+ /*package*/ IWindowSession getWindowSession() {
+ return mAttachInfo != null ? mAttachInfo.mSession : null;
+ }
+
+ /**
+ * Return the visibility value of the least visible component passed.
+ */
+ int combineVisibility(int vis1, int vis2) {
+ // This works because VISIBLE < INVISIBLE < GONE.
+ return Math.max(vis1, vis2);
+ }
+
+ /**
+ * @param info the {@link android.view.View.AttachInfo} to associated with
+ * this view
+ */
+ void dispatchAttachedToWindow(AttachInfo info, int visibility) {
+ mAttachInfo = info;
+ if (mOverlay != null) {
+ mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
+ }
+ mWindowAttachCount++;
+ // We will need to evaluate the drawable state at least once.
+ mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
+ if (mFloatingTreeObserver != null) {
+ info.mTreeObserver.merge(mFloatingTreeObserver);
+ mFloatingTreeObserver = null;
+ }
+
+ registerPendingFrameMetricsObservers();
+
+ if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
+ mAttachInfo.mScrollContainers.add(this);
+ mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
+ }
+ // Transfer all pending runnables.
+ if (mRunQueue != null) {
+ mRunQueue.executeActions(info.mHandler);
+ mRunQueue = null;
+ }
+ performCollectViewAttributes(mAttachInfo, visibility);
+ onAttachedToWindow();
+
+ ListenerInfo li = mListenerInfo;
+ final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
+ li != null ? li.mOnAttachStateChangeListeners : null;
+ if (listeners != null && listeners.size() > 0) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ for (OnAttachStateChangeListener listener : listeners) {
+ listener.onViewAttachedToWindow(this);
+ }
+ }
+
+ int vis = info.mWindowVisibility;
+ if (vis != GONE) {
+ onWindowVisibilityChanged(vis);
+ if (isShown()) {
+ // Calling onVisibilityAggregated directly here since the subtree will also
+ // receive dispatchAttachedToWindow and this same call
+ onVisibilityAggregated(vis == VISIBLE);
+ }
+ }
+
+ // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
+ // As all views in the subtree will already receive dispatchAttachedToWindow
+ // traversing the subtree again here is not desired.
+ onVisibilityChanged(this, visibility);
+
+ if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
+ // If nobody has evaluated the drawable state yet, then do it now.
+ refreshDrawableState();
+ }
+ needGlobalAttributesUpdate(false);
+
+ notifyEnterOrExitForAutoFillIfNeeded(true);
+ }
+
+ void dispatchDetachedFromWindow() {
+ AttachInfo info = mAttachInfo;
+ if (info != null) {
+ int vis = info.mWindowVisibility;
+ if (vis != GONE) {
+ onWindowVisibilityChanged(GONE);
+ if (isShown()) {
+ // Invoking onVisibilityAggregated directly here since the subtree
+ // will also receive detached from window
+ onVisibilityAggregated(false);
+ }
+ }
+ }
+
+ onDetachedFromWindow();
+ onDetachedFromWindowInternal();
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.onViewDetachedFromWindow(this);
+ }
+
+ ListenerInfo li = mListenerInfo;
+ final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
+ li != null ? li.mOnAttachStateChangeListeners : null;
+ if (listeners != null && listeners.size() > 0) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ for (OnAttachStateChangeListener listener : listeners) {
+ listener.onViewDetachedFromWindow(this);
+ }
+ }
+
+ if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
+ mAttachInfo.mScrollContainers.remove(this);
+ mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
+ }
+
+ mAttachInfo = null;
+ if (mOverlay != null) {
+ mOverlay.getOverlayView().dispatchDetachedFromWindow();
+ }
+
+ notifyEnterOrExitForAutoFillIfNeeded(false);
+ }
+
+ /**
+ * 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.
+ *
+ * @see #restoreHierarchyState(android.util.SparseArray)
+ * @see #dispatchSaveInstanceState(android.util.SparseArray)
+ * @see #onSaveInstanceState()
+ */
+ public void saveHierarchyState(SparseArray<Parcelable> container) {
+ dispatchSaveInstanceState(container);
+ }
+
+ /**
+ * Called by {@link #saveHierarchyState(android.util.SparseArray)} to store the state for
+ * this view and its children. May be overridden to modify how freezing happens to a
+ * view's children; for example, some views may want to not store state for their children.
+ *
+ * @param container The SparseArray in which to save the view's state.
+ *
+ * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+ * @see #saveHierarchyState(android.util.SparseArray)
+ * @see #onSaveInstanceState()
+ */
+ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+ if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
+ mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
+ Parcelable state = onSaveInstanceState();
+ if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
+ throw new IllegalStateException(
+ "Derived class did not call super.onSaveInstanceState()");
+ }
+ if (state != null) {
+ // Log.i("View", "Freezing #" + Integer.toHexString(mID)
+ // + ": " + state);
+ container.put(mID, state);
+ }
+ }
+ }
+
+ /**
+ * Hook allowing a view to generate a representation of its internal state
+ * that can later be used to create a new instance with that same state.
+ * This state should only contain information that is not persistent or can
+ * not be reconstructed later. For example, you will never store your
+ * current position on screen because that will be computed again when a
+ * new instance of the view is placed in its view hierarchy.
+ * <p>
+ * Some examples of things you may store here: the current cursor position
+ * in a text view (but usually not the text itself since that is stored in a
+ * content provider or other persistent storage), the currently selected
+ * item in a list view.
+ *
+ * @return Returns a Parcelable object containing the view's current dynamic
+ * state, or null if there is nothing interesting to save.
+ * @see #onRestoreInstanceState(Parcelable)
+ * @see #saveHierarchyState(SparseArray)
+ * @see #dispatchSaveInstanceState(SparseArray)
+ * @see #setSaveEnabled(boolean)
+ */
+ @CallSuper
+ @Nullable protected Parcelable onSaveInstanceState() {
+ mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
+ if (mStartActivityRequestWho != null || isAutofilled()
+ || mAutofillViewId > LAST_APP_AUTOFILL_ID) {
+ BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
+
+ if (mStartActivityRequestWho != null) {
+ state.mSavedData |= BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED;
+ }
+
+ if (isAutofilled()) {
+ state.mSavedData |= BaseSavedState.IS_AUTOFILLED;
+ }
+
+ if (mAutofillViewId > LAST_APP_AUTOFILL_ID) {
+ state.mSavedData |= BaseSavedState.AUTOFILL_ID;
+ }
+
+ state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
+ state.mIsAutofilled = isAutofilled();
+ state.mAutofillViewId = mAutofillViewId;
+ return state;
+ }
+ return BaseSavedState.EMPTY_STATE;
+ }
+
+ /**
+ * Restore this view hierarchy's frozen state from the given container.
+ *
+ * @param container The SparseArray which holds previously frozen states.
+ *
+ * @see #saveHierarchyState(android.util.SparseArray)
+ * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+ * @see #onRestoreInstanceState(android.os.Parcelable)
+ */
+ public void restoreHierarchyState(SparseArray<Parcelable> container) {
+ dispatchRestoreInstanceState(container);
+ }
+
+ /**
+ * Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
+ * state for this view and its children. May be overridden to modify how restoring
+ * happens to a view's children; for example, some views may want to not store state
+ * for their children.
+ *
+ * @param container The SparseArray which holds previously saved state.
+ *
+ * @see #dispatchSaveInstanceState(android.util.SparseArray)
+ * @see #restoreHierarchyState(android.util.SparseArray)
+ * @see #onRestoreInstanceState(android.os.Parcelable)
+ */
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ if (mID != NO_ID) {
+ Parcelable state = container.get(mID);
+ if (state != null) {
+ // Log.i("View", "Restoreing #" + Integer.toHexString(mID)
+ // + ": " + state);
+ mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
+ onRestoreInstanceState(state);
+ if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
+ throw new IllegalStateException(
+ "Derived class did not call super.onRestoreInstanceState()");
+ }
+ }
+ }
+ }
+
+ /**
+ * Hook allowing a view to re-apply a representation of its internal state that had previously
+ * been generated by {@link #onSaveInstanceState}. This function will never be called with a
+ * null state.
+ *
+ * @param state The frozen state that had previously been returned by
+ * {@link #onSaveInstanceState}.
+ *
+ * @see #onSaveInstanceState()
+ * @see #restoreHierarchyState(android.util.SparseArray)
+ * @see #dispatchRestoreInstanceState(android.util.SparseArray)
+ */
+ @CallSuper
+ protected void onRestoreInstanceState(Parcelable state) {
+ mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
+ if (state != null && !(state instanceof AbsSavedState)) {
+ throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ + "received " + state.getClass().toString() + " instead. This usually happens "
+ + "when two views of different type have the same id in the same hierarchy. "
+ + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ + "other views do not use the same id.");
+ }
+ if (state != null && state instanceof BaseSavedState) {
+ BaseSavedState baseState = (BaseSavedState) state;
+
+ if ((baseState.mSavedData & BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED) != 0) {
+ mStartActivityRequestWho = baseState.mStartActivityRequestWhoSaved;
+ }
+ if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) {
+ setAutofilled(baseState.mIsAutofilled);
+ }
+ if ((baseState.mSavedData & BaseSavedState.AUTOFILL_ID) != 0) {
+ // It can happen that views have the same view id and the restoration path will not
+ // be able to distinguish between them. The autofill id needs to be unique though.
+ // Hence prevent the same autofill view id from being restored multiple times.
+ ((BaseSavedState) state).mSavedData &= ~BaseSavedState.AUTOFILL_ID;
+
+ mAutofillViewId = baseState.mAutofillViewId;
+ }
+ }
+ }
+
+ /**
+ * <p>Return the time at which the drawing of the view hierarchy started.</p>
+ *
+ * @return the drawing start time in milliseconds
+ */
+ public long getDrawingTime() {
+ return mAttachInfo != null ? mAttachInfo.mDrawingTime : 0;
+ }
+
+ /**
+ * <p>Enables or disables the duplication of the parent's state into this view. When
+ * duplication is enabled, this view gets its drawable state from its parent rather
+ * than from its own internal properties.</p>
+ *
+ * <p>Note: in the current implementation, setting this property to true after the
+ * view was added to a ViewGroup might have no effect at all. This property should
+ * always be used from XML or set to true before adding this view to a ViewGroup.</p>
+ *
+ * <p>Note: if this view's parent addStateFromChildren property is enabled and this
+ * property is enabled, an exception will be thrown.</p>
+ *
+ * <p>Note: if the child view uses and updates additional states which are unknown to the
+ * parent, these states should not be affected by this method.</p>
+ *
+ * @param enabled True to enable duplication of the parent's drawable state, false
+ * to disable it.
+ *
+ * @see #getDrawableState()
+ * @see #isDuplicateParentStateEnabled()
+ */
+ public void setDuplicateParentStateEnabled(boolean enabled) {
+ setFlags(enabled ? DUPLICATE_PARENT_STATE : 0, DUPLICATE_PARENT_STATE);
+ }
+
+ /**
+ * <p>Indicates whether this duplicates its drawable state from its parent.</p>
+ *
+ * @return True if this view's drawable state is duplicated from the parent,
+ * false otherwise
+ *
+ * @see #getDrawableState()
+ * @see #setDuplicateParentStateEnabled(boolean)
+ */
+ public boolean isDuplicateParentStateEnabled() {
+ return (mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE;
+ }
+
+ /**
+ * <p>Specifies the type of layer backing this view. The layer can be
+ * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+ * {@link #LAYER_TYPE_HARDWARE}.</p>
+ *
+ * <p>A layer is associated with an optional {@link android.graphics.Paint}
+ * instance that controls how the layer is composed on screen. The following
+ * properties of the paint are taken into account when composing the layer:</p>
+ * <ul>
+ * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
+ * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
+ * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
+ * </ul>
+ *
+ * <p>If this view has an alpha value set to < 1.0 by calling
+ * {@link #setAlpha(float)}, the alpha value of the layer's paint is superseded
+ * by this view's alpha value.</p>
+ *
+ * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE},
+ * {@link #LAYER_TYPE_SOFTWARE} and {@link #LAYER_TYPE_HARDWARE}
+ * for more information on when and how to use layers.</p>
+ *
+ * @param layerType The type of layer to use with this view, must be one of
+ * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+ * {@link #LAYER_TYPE_HARDWARE}
+ * @param paint The paint used to compose the layer. This argument is optional
+ * and can be null. It is ignored when the layer type is
+ * {@link #LAYER_TYPE_NONE}
+ *
+ * @see #getLayerType()
+ * @see #LAYER_TYPE_NONE
+ * @see #LAYER_TYPE_SOFTWARE
+ * @see #LAYER_TYPE_HARDWARE
+ * @see #setAlpha(float)
+ *
+ * @attr ref android.R.styleable#View_layerType
+ */
+ public void setLayerType(int layerType, @Nullable Paint paint) {
+ if (layerType < LAYER_TYPE_NONE || layerType > LAYER_TYPE_HARDWARE) {
+ throw new IllegalArgumentException("Layer type can only be one of: LAYER_TYPE_NONE, "
+ + "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE");
+ }
+
+ boolean typeChanged = mRenderNode.setLayerType(layerType);
+
+ if (!typeChanged) {
+ setLayerPaint(paint);
+ return;
+ }
+
+ if (layerType != LAYER_TYPE_SOFTWARE) {
+ // Destroy any previous software drawing cache if present
+ // NOTE: even if previous layer type is HW, we do this to ensure we've cleaned up
+ // drawing cache created in View#draw when drawing to a SW canvas.
+ destroyDrawingCache();
+ }
+
+ mLayerType = layerType;
+ mLayerPaint = mLayerType == LAYER_TYPE_NONE ? null : paint;
+ mRenderNode.setLayerPaint(mLayerPaint);
+
+ // draw() behaves differently if we are on a layer, so we need to
+ // invalidate() here
+ invalidateParentCaches();
+ invalidate(true);
+ }
+
+ /**
+ * Updates the {@link Paint} object used with the current layer (used only if the current
+ * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint
+ * provided to {@link #setLayerType(int, android.graphics.Paint)} will be used the next time
+ * the View is redrawn, but {@link #setLayerPaint(android.graphics.Paint)} must be called to
+ * ensure that the view gets redrawn immediately.
+ *
+ * <p>A layer is associated with an optional {@link android.graphics.Paint}
+ * instance that controls how the layer is composed on screen. The following
+ * properties of the paint are taken into account when composing the layer:</p>
+ * <ul>
+ * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
+ * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
+ * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
+ * </ul>
+ *
+ * <p>If this view has an alpha value set to < 1.0 by calling {@link #setAlpha(float)}, the
+ * alpha value of the layer's paint is superseded by this view's alpha value.</p>
+ *
+ * @param paint The paint used to compose the layer. This argument is optional
+ * and can be null. It is ignored when the layer type is
+ * {@link #LAYER_TYPE_NONE}
+ *
+ * @see #setLayerType(int, android.graphics.Paint)
+ */
+ public void setLayerPaint(@Nullable Paint paint) {
+ int layerType = getLayerType();
+ if (layerType != LAYER_TYPE_NONE) {
+ mLayerPaint = paint;
+ if (layerType == LAYER_TYPE_HARDWARE) {
+ if (mRenderNode.setLayerPaint(paint)) {
+ invalidateViewProperty(false, false);
+ }
+ } else {
+ invalidate();
+ }
+ }
+ }
+
+ /**
+ * Indicates what type of layer is currently associated with this view. By default
+ * a view does not have a layer, and the layer type is {@link #LAYER_TYPE_NONE}.
+ * Refer to the documentation of {@link #setLayerType(int, android.graphics.Paint)}
+ * for more information on the different types of layers.
+ *
+ * @return {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+ * {@link #LAYER_TYPE_HARDWARE}
+ *
+ * @see #setLayerType(int, android.graphics.Paint)
+ * @see #buildLayer()
+ * @see #LAYER_TYPE_NONE
+ * @see #LAYER_TYPE_SOFTWARE
+ * @see #LAYER_TYPE_HARDWARE
+ */
+ public int getLayerType() {
+ return mLayerType;
+ }
+
+ /**
+ * Forces this view's layer to be created and this view to be rendered
+ * into its layer. If this view's layer type is set to {@link #LAYER_TYPE_NONE},
+ * invoking this method will have no effect.
+ *
+ * This method can for instance be used to render a view into its layer before
+ * starting an animation. If this view is complex, rendering into the layer
+ * before starting the animation will avoid skipping frames.
+ *
+ * @throws IllegalStateException If this view is not attached to a window
+ *
+ * @see #setLayerType(int, android.graphics.Paint)
+ */
+ public void buildLayer() {
+ if (mLayerType == LAYER_TYPE_NONE) return;
+
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo == null) {
+ throw new IllegalStateException("This view must be attached to a window first");
+ }
+
+ if (getWidth() == 0 || getHeight() == 0) {
+ return;
+ }
+
+ switch (mLayerType) {
+ case LAYER_TYPE_HARDWARE:
+ updateDisplayListIfDirty();
+ if (attachInfo.mThreadedRenderer != null && mRenderNode.isValid()) {
+ attachInfo.mThreadedRenderer.buildLayer(mRenderNode);
+ }
+ break;
+ case LAYER_TYPE_SOFTWARE:
+ buildDrawingCache(true);
+ break;
+ }
+ }
+
+ /**
+ * Destroys all hardware rendering resources. This method is invoked
+ * when the system needs to reclaim resources. Upon execution of this
+ * method, you should free any OpenGL resources created by the view.
+ *
+ * Note: you <strong>must</strong> call
+ * <code>super.destroyHardwareResources()</code> when overriding
+ * this method.
+ *
+ * @hide
+ */
+ @CallSuper
+ protected void destroyHardwareResources() {
+ if (mOverlay != null) {
+ mOverlay.getOverlayView().destroyHardwareResources();
+ }
+ if (mGhostView != null) {
+ mGhostView.destroyHardwareResources();
+ }
+ }
+
+ /**
+ * <p>Enables or disables the drawing cache. When the drawing cache is enabled, the next call
+ * to {@link #getDrawingCache()} or {@link #buildDrawingCache()} will draw the view in a
+ * bitmap. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when
+ * the cache is enabled. To benefit from the cache, you must request the drawing cache by
+ * calling {@link #getDrawingCache()} and draw it on screen if the returned bitmap is not
+ * null.</p>
+ *
+ * <p>Enabling the drawing cache is similar to
+ * {@link #setLayerType(int, android.graphics.Paint) setting a layer} when hardware
+ * acceleration is turned off. When hardware acceleration is turned on, enabling the
+ * drawing cache has no effect on rendering because the system uses a different mechanism
+ * for acceleration which ignores the flag. If you want to use a Bitmap for the view, even
+ * when hardware acceleration is enabled, see {@link #setLayerType(int, android.graphics.Paint)}
+ * for information on how to enable software and hardware layers.</p>
+ *
+ * <p>This API can be used to manually generate
+ * a bitmap copy of this view, by setting the flag to <code>true</code> and calling
+ * {@link #getDrawingCache()}.</p>
+ *
+ * @param enabled true to enable the drawing cache, false otherwise
+ *
+ * @see #isDrawingCacheEnabled()
+ * @see #getDrawingCache()
+ * @see #buildDrawingCache()
+ * @see #setLayerType(int, android.graphics.Paint)
+ */
+ public void setDrawingCacheEnabled(boolean enabled) {
+ mCachingFailed = false;
+ setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
+ }
+
+ /**
+ * <p>Indicates whether the drawing cache is enabled for this view.</p>
+ *
+ * @return true if the drawing cache is enabled
+ *
+ * @see #setDrawingCacheEnabled(boolean)
+ * @see #getDrawingCache()
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean isDrawingCacheEnabled() {
+ return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
+ }
+
+ /**
+ * Debugging utility which recursively outputs the dirty state of a view and its
+ * descendants.
+ *
+ * @hide
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
+ Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) +
+ ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" +
+ (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) +
+ ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
+ if (clear) {
+ mPrivateFlags &= clearMask;
+ }
+ if (this instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) this;
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ child.outputDirtyFlags(indent + " ", clear, clearMask);
+ }
+ }
+ }
+
+ /**
+ * This method is used by ViewGroup to cause its children to restore or recreate their
+ * display lists. It is called by getDisplayList() when the parent ViewGroup does not need
+ * to recreate its own display list, which would happen if it went through the normal
+ * draw/dispatchDraw mechanisms.
+ *
+ * @hide
+ */
+ protected void dispatchGetDisplayList() {}
+
+ /**
+ * A view that is not attached or hardware accelerated cannot create a display list.
+ * This method checks these conditions and returns the appropriate result.
+ *
+ * @return true if view has the ability to create a display list, false otherwise.
+ *
+ * @hide
+ */
+ public boolean canHaveDisplayList() {
+ return !(mAttachInfo == null || mAttachInfo.mThreadedRenderer == null);
+ }
+
+ /**
+ * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
+ * @hide
+ */
+ @NonNull
+ public RenderNode updateDisplayListIfDirty() {
+ final RenderNode renderNode = mRenderNode;
+ if (!canHaveDisplayList()) {
+ // can't populate RenderNode, don't try
+ return renderNode;
+ }
+
+ if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
+ || !renderNode.isValid()
+ || (mRecreateDisplayList)) {
+ // Don't need to recreate the display list, just need to tell our
+ // children to restore/recreate theirs
+ if (renderNode.isValid()
+ && !mRecreateDisplayList) {
+ mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+ dispatchGetDisplayList();
+
+ return renderNode; // no work needed
+ }
+
+ // If we got here, we're recreating it. Mark it as such to ensure that
+ // we copy in child display lists into ours in drawChild()
+ mRecreateDisplayList = true;
+
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+ int layerType = getLayerType();
+
+ final DisplayListCanvas canvas = renderNode.start(width, height);
+
+ try {
+ if (layerType == LAYER_TYPE_SOFTWARE) {
+ buildDrawingCache(true);
+ Bitmap cache = getDrawingCache(true);
+ if (cache != null) {
+ canvas.drawBitmap(cache, 0, 0, mLayerPaint);
+ }
+ } else {
+ computeScroll();
+
+ canvas.translate(-mScrollX, -mScrollY);
+ mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+
+ // Fast path for layouts with no backgrounds
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().draw(canvas);
+ }
+ if (debugDraw()) {
+ debugDrawFocus(canvas);
+ }
+ } else {
+ draw(canvas);
+ }
+ }
+ } finally {
+ renderNode.end(canvas);
+ setDisplayListProperties(renderNode);
+ }
+ } else {
+ mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+ }
+ return renderNode;
+ }
+
+ private void resetDisplayList() {
+ mRenderNode.discardDisplayList();
+ if (mBackgroundRenderNode != null) {
+ mBackgroundRenderNode.discardDisplayList();
+ }
+ }
+
+ /**
+ * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p>
+ *
+ * @return A non-scaled bitmap representing this view or null if cache is disabled.
+ *
+ * @see #getDrawingCache(boolean)
+ */
+ public Bitmap getDrawingCache() {
+ return getDrawingCache(false);
+ }
+
+ /**
+ * <p>Returns the bitmap in which this view drawing is cached. The returned bitmap
+ * is null when caching is disabled. If caching is enabled and the cache is not ready,
+ * this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not
+ * draw from the cache when the cache is enabled. To benefit from the cache, you must
+ * request the drawing cache by calling this method and draw it on screen if the
+ * returned bitmap is not null.</p>
+ *
+ * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
+ * this method will create a bitmap of the same size as this view. Because this bitmap
+ * will be drawn scaled by the parent ViewGroup, the result on screen might show
+ * scaling artifacts. To avoid such artifacts, you should call this method by setting
+ * the auto scaling to true. Doing so, however, will generate a bitmap of a different
+ * size than the view. This implies that your application must be able to handle this
+ * size.</p>
+ *
+ * @param autoScale Indicates whether the generated bitmap should be scaled based on
+ * the current density of the screen when the application is in compatibility
+ * mode.
+ *
+ * @return A bitmap representing this view or null if cache is disabled.
+ *
+ * @see #setDrawingCacheEnabled(boolean)
+ * @see #isDrawingCacheEnabled()
+ * @see #buildDrawingCache(boolean)
+ * @see #destroyDrawingCache()
+ */
+ public Bitmap getDrawingCache(boolean autoScale) {
+ if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
+ return null;
+ }
+ if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
+ buildDrawingCache(autoScale);
+ }
+ return autoScale ? mDrawingCache : mUnscaledDrawingCache;
+ }
+
+ /**
+ * <p>Frees the resources used by the drawing cache. If you call
+ * {@link #buildDrawingCache()} manually without calling
+ * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
+ * should cleanup the cache with this method afterwards.</p>
+ *
+ * @see #setDrawingCacheEnabled(boolean)
+ * @see #buildDrawingCache()
+ * @see #getDrawingCache()
+ */
+ public void destroyDrawingCache() {
+ if (mDrawingCache != null) {
+ mDrawingCache.recycle();
+ mDrawingCache = null;
+ }
+ if (mUnscaledDrawingCache != null) {
+ mUnscaledDrawingCache.recycle();
+ mUnscaledDrawingCache = null;
+ }
+ }
+
+ /**
+ * Setting a solid background color for the drawing cache's bitmaps will improve
+ * performance and memory usage. Note, though that this should only be used if this
+ * view will always be drawn on top of a solid color.
+ *
+ * @param color The background color to use for the drawing cache's bitmap
+ *
+ * @see #setDrawingCacheEnabled(boolean)
+ * @see #buildDrawingCache()
+ * @see #getDrawingCache()
+ */
+ public void setDrawingCacheBackgroundColor(@ColorInt int color) {
+ if (color != mDrawingCacheBackgroundColor) {
+ mDrawingCacheBackgroundColor = color;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ }
+ }
+
+ /**
+ * @see #setDrawingCacheBackgroundColor(int)
+ *
+ * @return The background color to used for the drawing cache's bitmap
+ */
+ @ColorInt
+ public int getDrawingCacheBackgroundColor() {
+ return mDrawingCacheBackgroundColor;
+ }
+
+ /**
+ * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
+ *
+ * @see #buildDrawingCache(boolean)
+ */
+ public void buildDrawingCache() {
+ buildDrawingCache(false);
+ }
+
+ /**
+ * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>
+ *
+ * <p>If you call {@link #buildDrawingCache()} manually without calling
+ * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
+ * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p>
+ *
+ * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
+ * this method will create a bitmap of the same size as this view. Because this bitmap
+ * will be drawn scaled by the parent ViewGroup, the result on screen might show
+ * scaling artifacts. To avoid such artifacts, you should call this method by setting
+ * the auto scaling to true. Doing so, however, will generate a bitmap of a different
+ * size than the view. This implies that your application must be able to handle this
+ * size.</p>
+ *
+ * <p>You should avoid calling this method when hardware acceleration is enabled. If
+ * you do not need the drawing cache bitmap, calling this method will increase memory
+ * usage and cause the view to be rendered in software once, thus negatively impacting
+ * performance.</p>
+ *
+ * @see #getDrawingCache()
+ * @see #destroyDrawingCache()
+ */
+ public void buildDrawingCache(boolean autoScale) {
+ if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
+ mDrawingCache == null : mUnscaledDrawingCache == null)) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "buildDrawingCache/SW Layer for " + getClass().getSimpleName());
+ }
+ try {
+ buildDrawingCacheImpl(autoScale);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+ }
+
+ /**
+ * private, internal implementation of buildDrawingCache, used to enable tracing
+ */
+ private void buildDrawingCacheImpl(boolean autoScale) {
+ mCachingFailed = false;
+
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+
+ final AttachInfo attachInfo = mAttachInfo;
+ final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
+
+ if (autoScale && scalingRequired) {
+ width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
+ height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
+ }
+
+ final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
+ final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
+ final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
+
+ final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
+ final long drawingCacheSize =
+ ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
+ if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
+ if (width > 0 && height > 0) {
+ Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
+ + " too large to fit into a software layer (or drawing cache), needs "
+ + projectedBitmapSize + " bytes, only "
+ + drawingCacheSize + " available");
+ }
+ destroyDrawingCache();
+ mCachingFailed = true;
+ return;
+ }
+
+ boolean clear = true;
+ Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
+
+ if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
+ Bitmap.Config quality;
+ if (!opaque) {
+ // Never pick ARGB_4444 because it looks awful
+ // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
+ switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
+ case DRAWING_CACHE_QUALITY_AUTO:
+ case DRAWING_CACHE_QUALITY_LOW:
+ case DRAWING_CACHE_QUALITY_HIGH:
+ default:
+ quality = Bitmap.Config.ARGB_8888;
+ break;
+ }
+ } else {
+ // Optimization for translucent windows
+ // If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
+ quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+ }
+
+ // Try to cleanup memory
+ if (bitmap != null) bitmap.recycle();
+
+ try {
+ bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
+ width, height, quality);
+ bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
+ if (autoScale) {
+ mDrawingCache = bitmap;
+ } else {
+ mUnscaledDrawingCache = bitmap;
+ }
+ if (opaque && use32BitCache) bitmap.setHasAlpha(false);
+ } catch (OutOfMemoryError e) {
+ // If there is not enough memory to create the bitmap cache, just
+ // ignore the issue as bitmap caches are not required to draw the
+ // view hierarchy
+ if (autoScale) {
+ mDrawingCache = null;
+ } else {
+ mUnscaledDrawingCache = null;
+ }
+ mCachingFailed = true;
+ return;
+ }
+
+ clear = drawingCacheBackgroundColor != 0;
+ }
+
+ Canvas canvas;
+ if (attachInfo != null) {
+ canvas = attachInfo.mCanvas;
+ if (canvas == null) {
+ canvas = new Canvas();
+ }
+ canvas.setBitmap(bitmap);
+ // Temporarily clobber the cached Canvas in case one of our children
+ // is also using a drawing cache. Without this, the children would
+ // steal the canvas by attaching their own bitmap to it and bad, bad
+ // thing would happen (invisible views, corrupted drawings, etc.)
+ attachInfo.mCanvas = null;
+ } else {
+ // This case should hopefully never or seldom happen
+ canvas = new Canvas(bitmap);
+ }
+
+ if (clear) {
+ bitmap.eraseColor(drawingCacheBackgroundColor);
+ }
+
+ computeScroll();
+ final int restoreCount = canvas.save();
+
+ if (autoScale && scalingRequired) {
+ final float scale = attachInfo.mApplicationScale;
+ canvas.scale(scale, scale);
+ }
+
+ canvas.translate(-mScrollX, -mScrollY);
+
+ mPrivateFlags |= PFLAG_DRAWN;
+ if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
+ mLayerType != LAYER_TYPE_NONE) {
+ mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
+ }
+
+ // Fast path for layouts with no backgrounds
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+ dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().draw(canvas);
+ }
+ } else {
+ draw(canvas);
+ }
+
+ canvas.restoreToCount(restoreCount);
+ canvas.setBitmap(null);
+
+ if (attachInfo != null) {
+ // Restore the cached Canvas for our siblings
+ attachInfo.mCanvas = canvas;
+ }
+ }
+
+ /**
+ * Create a snapshot of the view into a bitmap. We should probably make
+ * some form of this public, but should think about the API.
+ *
+ * @hide
+ */
+ public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+
+ final AttachInfo attachInfo = mAttachInfo;
+ final float scale = attachInfo != null ? attachInfo.mApplicationScale : 1.0f;
+ width = (int) ((width * scale) + 0.5f);
+ height = (int) ((height * scale) + 0.5f);
+
+ Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
+ width > 0 ? width : 1, height > 0 ? height : 1, quality);
+ if (bitmap == null) {
+ throw new OutOfMemoryError();
+ }
+
+ Resources resources = getResources();
+ if (resources != null) {
+ bitmap.setDensity(resources.getDisplayMetrics().densityDpi);
+ }
+
+ Canvas canvas;
+ if (attachInfo != null) {
+ canvas = attachInfo.mCanvas;
+ if (canvas == null) {
+ canvas = new Canvas();
+ }
+ canvas.setBitmap(bitmap);
+ // Temporarily clobber the cached Canvas in case one of our children
+ // is also using a drawing cache. Without this, the children would
+ // steal the canvas by attaching their own bitmap to it and bad, bad
+ // things would happen (invisible views, corrupted drawings, etc.)
+ attachInfo.mCanvas = null;
+ } else {
+ // This case should hopefully never or seldom happen
+ canvas = new Canvas(bitmap);
+ }
+ boolean enabledHwBitmapsInSwMode = canvas.isHwBitmapsInSwModeEnabled();
+ canvas.setHwBitmapsInSwModeEnabled(true);
+ if ((backgroundColor & 0xff000000) != 0) {
+ bitmap.eraseColor(backgroundColor);
+ }
+
+ computeScroll();
+ final int restoreCount = canvas.save();
+ canvas.scale(scale, scale);
+ canvas.translate(-mScrollX, -mScrollY);
+
+ // Temporarily remove the dirty mask
+ int flags = mPrivateFlags;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+
+ // Fast path for layouts with no backgrounds
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().draw(canvas);
+ }
+ } else {
+ draw(canvas);
+ }
+
+ mPrivateFlags = flags;
+
+ canvas.restoreToCount(restoreCount);
+ canvas.setBitmap(null);
+ canvas.setHwBitmapsInSwModeEnabled(enabledHwBitmapsInSwMode);
+
+ if (attachInfo != null) {
+ // Restore the cached Canvas for our siblings
+ attachInfo.mCanvas = canvas;
+ }
+
+ return bitmap;
+ }
+
+ /**
+ * Indicates whether this View is currently in edit mode. A View is usually
+ * in edit mode when displayed within a developer tool. For instance, if
+ * this View is being drawn by a visual user interface builder, this method
+ * should return true.
+ *
+ * Subclasses should check the return value of this method to provide
+ * different behaviors if their normal behavior might interfere with the
+ * host environment. For instance: the class spawns a thread in its
+ * constructor, the drawing code relies on device-specific features, etc.
+ *
+ * This method is usually checked in the drawing code of custom widgets.
+ *
+ * @return True if this View is in edit mode, false otherwise.
+ */
+ public boolean isInEditMode() {
+ return false;
+ }
+
+ /**
+ * If the View draws content inside its padding and enables fading edges,
+ * it needs to support padding offsets. Padding offsets are added to the
+ * fading edges to extend the length of the fade so that it covers pixels
+ * drawn inside the padding.
+ *
+ * Subclasses of this class should override this method if they need
+ * to draw content inside the padding.
+ *
+ * @return True if padding offset must be applied, false otherwise.
+ *
+ * @see #getLeftPaddingOffset()
+ * @see #getRightPaddingOffset()
+ * @see #getTopPaddingOffset()
+ * @see #getBottomPaddingOffset()
+ *
+ * @since CURRENT
+ */
+ protected boolean isPaddingOffsetRequired() {
+ return false;
+ }
+
+ /**
+ * Amount by which to extend the left fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The left padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getLeftPaddingOffset() {
+ return 0;
+ }
+
+ /**
+ * Amount by which to extend the right fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The right padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getRightPaddingOffset() {
+ return 0;
+ }
+
+ /**
+ * Amount by which to extend the top fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The top padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getTopPaddingOffset() {
+ return 0;
+ }
+
+ /**
+ * Amount by which to extend the bottom fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The bottom padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getBottomPaddingOffset() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ * @param offsetRequired
+ */
+ protected int getFadeTop(boolean offsetRequired) {
+ int top = mPaddingTop;
+ if (offsetRequired) top += getTopPaddingOffset();
+ return top;
+ }
+
+ /**
+ * @hide
+ * @param offsetRequired
+ */
+ protected int getFadeHeight(boolean offsetRequired) {
+ int padding = mPaddingTop;
+ if (offsetRequired) padding += getTopPaddingOffset();
+ return mBottom - mTop - mPaddingBottom - padding;
+ }
+
+ /**
+ * <p>Indicates whether this view is attached to a hardware accelerated
+ * window or not.</p>
+ *
+ * <p>Even if this method returns true, it does not mean that every call
+ * to {@link #draw(android.graphics.Canvas)} will be made with an hardware
+ * accelerated {@link android.graphics.Canvas}. For instance, if this view
+ * is drawn onto an offscreen {@link android.graphics.Bitmap} and its
+ * window is hardware accelerated,
+ * {@link android.graphics.Canvas#isHardwareAccelerated()} will likely
+ * return false, and this method will return true.</p>
+ *
+ * @return True if the view is attached to a window and the window is
+ * hardware accelerated; false in any other case.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean isHardwareAccelerated() {
+ return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
+ }
+
+ /**
+ * Sets a rectangular area on this view to which the view will be clipped
+ * when it is drawn. Setting the value to null will remove the clip bounds
+ * and the view will draw normally, using its full bounds.
+ *
+ * @param clipBounds The rectangular area, in the local coordinates of
+ * this view, to which future drawing operations will be clipped.
+ */
+ public void setClipBounds(Rect clipBounds) {
+ if (clipBounds == mClipBounds
+ || (clipBounds != null && clipBounds.equals(mClipBounds))) {
+ return;
+ }
+ if (clipBounds != null) {
+ if (mClipBounds == null) {
+ mClipBounds = new Rect(clipBounds);
+ } else {
+ mClipBounds.set(clipBounds);
+ }
+ } else {
+ mClipBounds = null;
+ }
+ mRenderNode.setClipBounds(mClipBounds);
+ invalidateViewProperty(false, false);
+ }
+
+ /**
+ * Returns a copy of the current {@link #setClipBounds(Rect) clipBounds}.
+ *
+ * @return A copy of the current clip bounds if clip bounds are set,
+ * otherwise null.
+ */
+ public Rect getClipBounds() {
+ return (mClipBounds != null) ? new Rect(mClipBounds) : null;
+ }
+
+
+ /**
+ * Populates an output rectangle with the clip bounds of the view,
+ * returning {@code true} if successful or {@code false} if the view's
+ * clip bounds are {@code null}.
+ *
+ * @param outRect rectangle in which to place the clip bounds of the view
+ * @return {@code true} if successful or {@code false} if the view's
+ * clip bounds are {@code null}
+ */
+ public boolean getClipBounds(Rect outRect) {
+ if (mClipBounds != null) {
+ outRect.set(mClipBounds);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
+ * case of an active Animation being run on the view.
+ */
+ private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
+ Animation a, boolean scalingRequired) {
+ Transformation invalidationTransform;
+ final int flags = parent.mGroupFlags;
+ final boolean initialized = a.isInitialized();
+ if (!initialized) {
+ a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
+ a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
+ if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
+ onAnimationStart();
+ }
+
+ final Transformation t = parent.getChildTransformation();
+ boolean more = a.getTransformation(drawingTime, t, 1f);
+ if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
+ if (parent.mInvalidationTransformation == null) {
+ parent.mInvalidationTransformation = new Transformation();
+ }
+ invalidationTransform = parent.mInvalidationTransformation;
+ a.getTransformation(drawingTime, invalidationTransform, 1f);
+ } else {
+ invalidationTransform = t;
+ }
+
+ if (more) {
+ if (!a.willChangeBounds()) {
+ if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
+ ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
+ parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
+ } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
+ // The child need to draw an animation, potentially offscreen, so
+ // make sure we do not cancel invalidate requests
+ parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
+ parent.invalidate(mLeft, mTop, mRight, mBottom);
+ }
+ } else {
+ if (parent.mInvalidateRegion == null) {
+ parent.mInvalidateRegion = new RectF();
+ }
+ final RectF region = parent.mInvalidateRegion;
+ a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
+ invalidationTransform);
+
+ // The child need to draw an animation, potentially offscreen, so
+ // make sure we do not cancel invalidate requests
+ parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
+
+ final int left = mLeft + (int) region.left;
+ final int top = mTop + (int) region.top;
+ parent.invalidate(left, top, left + (int) (region.width() + .5f),
+ top + (int) (region.height() + .5f));
+ }
+ }
+ return more;
+ }
+
+ /**
+ * This method is called by getDisplayList() when a display list is recorded for a View.
+ * It pushes any properties to the RenderNode that aren't managed by the RenderNode.
+ */
+ void setDisplayListProperties(RenderNode renderNode) {
+ if (renderNode != null) {
+ renderNode.setHasOverlappingRendering(getHasOverlappingRendering());
+ renderNode.setClipToBounds(mParent instanceof ViewGroup
+ && ((ViewGroup) mParent).getClipChildren());
+
+ float alpha = 1;
+ if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags &
+ ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+ ViewGroup parentVG = (ViewGroup) mParent;
+ final Transformation t = parentVG.getChildTransformation();
+ if (parentVG.getChildStaticTransformation(this, t)) {
+ final int transformType = t.getTransformationType();
+ if (transformType != Transformation.TYPE_IDENTITY) {
+ if ((transformType & Transformation.TYPE_ALPHA) != 0) {
+ alpha = t.getAlpha();
+ }
+ if ((transformType & Transformation.TYPE_MATRIX) != 0) {
+ renderNode.setStaticMatrix(t.getMatrix());
+ }
+ }
+ }
+ }
+ if (mTransformationInfo != null) {
+ alpha *= getFinalAlpha();
+ if (alpha < 1) {
+ final int multipliedAlpha = (int) (255 * alpha);
+ if (onSetAlpha(multipliedAlpha)) {
+ alpha = 1;
+ }
+ }
+ renderNode.setAlpha(alpha);
+ } else if (alpha < 1) {
+ renderNode.setAlpha(alpha);
+ }
+ }
+ }
+
+ /**
+ * This method is called by ViewGroup.drawChild() to have each child view draw itself.
+ *
+ * This is where the View specializes rendering behavior based on layer type,
+ * and hardware acceleration.
+ */
+ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+ final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
+ /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
+ *
+ * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
+ * HW accelerated, it can't handle drawing RenderNodes.
+ */
+ boolean drawingWithRenderNode = mAttachInfo != null
+ && mAttachInfo.mHardwareAccelerated
+ && hardwareAcceleratedCanvas;
+
+ boolean more = false;
+ final boolean childHasIdentityMatrix = hasIdentityMatrix();
+ final int parentFlags = parent.mGroupFlags;
+
+ if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
+ parent.getChildTransformation().clear();
+ parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+ }
+
+ Transformation transformToApply = null;
+ boolean concatMatrix = false;
+ final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
+ final Animation a = getAnimation();
+ if (a != null) {
+ more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
+ concatMatrix = a.willChangeTransformationMatrix();
+ if (concatMatrix) {
+ mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
+ }
+ transformToApply = parent.getChildTransformation();
+ } else {
+ if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
+ // No longer animating: clear out old animation matrix
+ mRenderNode.setAnimationMatrix(null);
+ mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
+ }
+ if (!drawingWithRenderNode
+ && (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+ final Transformation t = parent.getChildTransformation();
+ final boolean hasTransform = parent.getChildStaticTransformation(this, t);
+ if (hasTransform) {
+ final int transformType = t.getTransformationType();
+ transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
+ concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+ }
+ }
+ }
+
+ concatMatrix |= !childHasIdentityMatrix;
+
+ // Sets the flag as early as possible to allow draw() implementations
+ // to call invalidate() successfully when doing animations
+ mPrivateFlags |= PFLAG_DRAWN;
+
+ if (!concatMatrix &&
+ (parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
+ ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
+ canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
+ (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
+ mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
+ return more;
+ }
+ mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED;
+
+ if (hardwareAcceleratedCanvas) {
+ // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
+ // retain the flag's value temporarily in the mRecreateDisplayList flag
+ mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
+ mPrivateFlags &= ~PFLAG_INVALIDATED;
+ }
+
+ RenderNode renderNode = null;
+ Bitmap cache = null;
+ int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local
+ if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
+ if (layerType != LAYER_TYPE_NONE) {
+ // If not drawing with RenderNode, treat HW layers as SW
+ layerType = LAYER_TYPE_SOFTWARE;
+ buildDrawingCache(true);
+ }
+ cache = getDrawingCache(true);
+ }
+
+ if (drawingWithRenderNode) {
+ // Delay getting the display list until animation-driven alpha values are
+ // set up and possibly passed on to the view
+ renderNode = updateDisplayListIfDirty();
+ if (!renderNode.isValid()) {
+ // Uncommon, but possible. If a view is removed from the hierarchy during the call
+ // to getDisplayList(), the display list will be marked invalid and we should not
+ // try to use it again.
+ renderNode = null;
+ drawingWithRenderNode = false;
+ }
+ }
+
+ int sx = 0;
+ int sy = 0;
+ if (!drawingWithRenderNode) {
+ computeScroll();
+ sx = mScrollX;
+ sy = mScrollY;
+ }
+
+ final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
+ final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
+
+ int restoreTo = -1;
+ if (!drawingWithRenderNode || transformToApply != null) {
+ restoreTo = canvas.save();
+ }
+ if (offsetForScroll) {
+ canvas.translate(mLeft - sx, mTop - sy);
+ } else {
+ if (!drawingWithRenderNode) {
+ canvas.translate(mLeft, mTop);
+ }
+ if (scalingRequired) {
+ if (drawingWithRenderNode) {
+ // TODO: Might not need this if we put everything inside the DL
+ restoreTo = canvas.save();
+ }
+ // mAttachInfo cannot be null, otherwise scalingRequired == false
+ final float scale = 1.0f / mAttachInfo.mApplicationScale;
+ canvas.scale(scale, scale);
+ }
+ }
+
+ float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
+ if (transformToApply != null
+ || alpha < 1
+ || !hasIdentityMatrix()
+ || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
+ if (transformToApply != null || !childHasIdentityMatrix) {
+ int transX = 0;
+ int transY = 0;
+
+ if (offsetForScroll) {
+ transX = -sx;
+ transY = -sy;
+ }
+
+ if (transformToApply != null) {
+ if (concatMatrix) {
+ if (drawingWithRenderNode) {
+ renderNode.setAnimationMatrix(transformToApply.getMatrix());
+ } else {
+ // Undo the scroll translation, apply the transformation matrix,
+ // then redo the scroll translate to get the correct result.
+ canvas.translate(-transX, -transY);
+ canvas.concat(transformToApply.getMatrix());
+ canvas.translate(transX, transY);
+ }
+ parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+ }
+
+ float transformAlpha = transformToApply.getAlpha();
+ if (transformAlpha < 1) {
+ alpha *= transformAlpha;
+ parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+ }
+ }
+
+ if (!childHasIdentityMatrix && !drawingWithRenderNode) {
+ canvas.translate(-transX, -transY);
+ canvas.concat(getMatrix());
+ canvas.translate(transX, transY);
+ }
+ }
+
+ // Deal with alpha if it is or used to be <1
+ if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
+ if (alpha < 1) {
+ mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
+ }
+ parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
+ if (!drawingWithDrawingCache) {
+ final int multipliedAlpha = (int) (255 * alpha);
+ if (!onSetAlpha(multipliedAlpha)) {
+ if (drawingWithRenderNode) {
+ renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
+ } else if (layerType == LAYER_TYPE_NONE) {
+ canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
+ multipliedAlpha);
+ }
+ } else {
+ // Alpha is handled by the child directly, clobber the layer's alpha
+ mPrivateFlags |= PFLAG_ALPHA_SET;
+ }
+ }
+ }
+ } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
+ onSetAlpha(255);
+ mPrivateFlags &= ~PFLAG_ALPHA_SET;
+ }
+
+ if (!drawingWithRenderNode) {
+ // apply clips directly, since RenderNode won't do it for this draw
+ if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
+ if (offsetForScroll) {
+ canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
+ } else {
+ if (!scalingRequired || cache == null) {
+ canvas.clipRect(0, 0, getWidth(), getHeight());
+ } else {
+ canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
+ }
+ }
+ }
+
+ if (mClipBounds != null) {
+ // clip bounds ignore scroll
+ canvas.clipRect(mClipBounds);
+ }
+ }
+
+ if (!drawingWithDrawingCache) {
+ if (drawingWithRenderNode) {
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+ ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
+ } else {
+ // Fast path for layouts with no backgrounds
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+ dispatchDraw(canvas);
+ } else {
+ draw(canvas);
+ }
+ }
+ } else if (cache != null) {
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
+ if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
+ // no layer paint, use temporary paint to draw bitmap
+ Paint cachePaint = parent.mCachePaint;
+ if (cachePaint == null) {
+ cachePaint = new Paint();
+ cachePaint.setDither(false);
+ parent.mCachePaint = cachePaint;
+ }
+ cachePaint.setAlpha((int) (alpha * 255));
+ canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
+ } else {
+ // use layer paint to draw the bitmap, merging the two alphas, but also restore
+ int layerPaintAlpha = mLayerPaint.getAlpha();
+ if (alpha < 1) {
+ mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
+ }
+ canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
+ if (alpha < 1) {
+ mLayerPaint.setAlpha(layerPaintAlpha);
+ }
+ }
+ }
+
+ if (restoreTo >= 0) {
+ canvas.restoreToCount(restoreTo);
+ }
+
+ if (a != null && !more) {
+ if (!hardwareAcceleratedCanvas && !a.getFillAfter()) {
+ onSetAlpha(255);
+ }
+ parent.finishAnimatingView(this, a);
+ }
+
+ if (more && hardwareAcceleratedCanvas) {
+ if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
+ // alpha animations should cause the child to recreate its display list
+ invalidate(true);
+ }
+ }
+
+ mRecreateDisplayList = false;
+
+ return more;
+ }
+
+ static Paint getDebugPaint() {
+ if (sDebugPaint == null) {
+ sDebugPaint = new Paint();
+ sDebugPaint.setAntiAlias(false);
+ }
+ return sDebugPaint;
+ }
+
+ final int dipsToPixels(int dips) {
+ float scale = getContext().getResources().getDisplayMetrics().density;
+ return (int) (dips * scale + 0.5f);
+ }
+
+ final private void debugDrawFocus(Canvas canvas) {
+ if (isFocused()) {
+ final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
+ final int l = mScrollX;
+ final int r = l + mRight - mLeft;
+ final int t = mScrollY;
+ final int b = t + mBottom - mTop;
+
+ final Paint paint = getDebugPaint();
+ paint.setColor(DEBUG_CORNERS_COLOR);
+
+ // Draw squares in corners.
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(l, t, l + cornerSquareSize, t + cornerSquareSize, paint);
+ canvas.drawRect(r - cornerSquareSize, t, r, t + cornerSquareSize, paint);
+ canvas.drawRect(l, b - cornerSquareSize, l + cornerSquareSize, b, paint);
+ canvas.drawRect(r - cornerSquareSize, b - cornerSquareSize, r, b, paint);
+
+ // Draw big X across the view.
+ paint.setStyle(Paint.Style.STROKE);
+ canvas.drawLine(l, t, r, b, paint);
+ canvas.drawLine(l, b, r, t, paint);
+ }
+ }
+
+ /**
+ * Manually render this view (and all of its children) to the given Canvas.
+ * The view must have already done a full layout before this function is
+ * called. When implementing a view, implement
+ * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
+ * If you do need to override this method, call the superclass version.
+ *
+ * @param canvas The Canvas to which the View is rendered.
+ */
+ @CallSuper
+ public void draw(Canvas canvas) {
+ final int privateFlags = mPrivateFlags;
+ final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
+ (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
+ mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
+
+ /*
+ * Draw traversal performs several drawing steps which must be executed
+ * in the appropriate order:
+ *
+ * 1. Draw the background
+ * 2. If necessary, save the canvas' layers to prepare for fading
+ * 3. Draw view's content
+ * 4. Draw children
+ * 5. If necessary, draw the fading edges and restore layers
+ * 6. Draw decorations (scrollbars for instance)
+ */
+
+ // Step 1, draw the background, if needed
+ int saveCount;
+
+ if (!dirtyOpaque) {
+ drawBackground(canvas);
+ }
+
+ // skip step 2 & 5 if possible (common case)
+ final int viewFlags = mViewFlags;
+ boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
+ boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
+ if (!verticalEdges && !horizontalEdges) {
+ // Step 3, draw the content
+ if (!dirtyOpaque) onDraw(canvas);
+
+ // Step 4, draw the children
+ dispatchDraw(canvas);
+
+ drawAutofilledHighlight(canvas);
+
+ // Overlay is part of the content and draws beneath Foreground
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().dispatchDraw(canvas);
+ }
+
+ // Step 6, draw decorations (foreground, scrollbars)
+ onDrawForeground(canvas);
+
+ // Step 7, draw the default focus highlight
+ drawDefaultFocusHighlight(canvas);
+
+ if (debugDraw()) {
+ debugDrawFocus(canvas);
+ }
+
+ // we're done...
+ return;
+ }
+
+ /*
+ * Here we do the full fledged routine...
+ * (this is an uncommon case where speed matters less,
+ * this is why we repeat some of the tests that have been
+ * done above)
+ */
+
+ boolean drawTop = false;
+ boolean drawBottom = false;
+ boolean drawLeft = false;
+ boolean drawRight = false;
+
+ float topFadeStrength = 0.0f;
+ float bottomFadeStrength = 0.0f;
+ float leftFadeStrength = 0.0f;
+ float rightFadeStrength = 0.0f;
+
+ // Step 2, save the canvas' layers
+ int paddingLeft = mPaddingLeft;
+
+ final boolean offsetRequired = isPaddingOffsetRequired();
+ if (offsetRequired) {
+ paddingLeft += getLeftPaddingOffset();
+ }
+
+ int left = mScrollX + paddingLeft;
+ int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
+ int top = mScrollY + getFadeTop(offsetRequired);
+ int bottom = top + getFadeHeight(offsetRequired);
+
+ if (offsetRequired) {
+ right += getRightPaddingOffset();
+ bottom += getBottomPaddingOffset();
+ }
+
+ final ScrollabilityCache scrollabilityCache = mScrollCache;
+ final float fadeHeight = scrollabilityCache.fadingEdgeLength;
+ int length = (int) fadeHeight;
+
+ // clip the fade length if top and bottom fades overlap
+ // overlapping fades produce odd-looking artifacts
+ if (verticalEdges && (top + length > bottom - length)) {
+ length = (bottom - top) / 2;
+ }
+
+ // also clip horizontal fades if necessary
+ if (horizontalEdges && (left + length > right - length)) {
+ length = (right - left) / 2;
+ }
+
+ if (verticalEdges) {
+ topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
+ drawTop = topFadeStrength * fadeHeight > 1.0f;
+ bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
+ drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
+ }
+
+ if (horizontalEdges) {
+ leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
+ drawLeft = leftFadeStrength * fadeHeight > 1.0f;
+ rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
+ drawRight = rightFadeStrength * fadeHeight > 1.0f;
+ }
+
+ saveCount = canvas.getSaveCount();
+
+ int solidColor = getSolidColor();
+ if (solidColor == 0) {
+ final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
+
+ if (drawTop) {
+ canvas.saveLayer(left, top, right, top + length, null, flags);
+ }
+
+ if (drawBottom) {
+ canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
+ }
+
+ if (drawLeft) {
+ canvas.saveLayer(left, top, left + length, bottom, null, flags);
+ }
+
+ if (drawRight) {
+ canvas.saveLayer(right - length, top, right, bottom, null, flags);
+ }
+ } else {
+ scrollabilityCache.setFadeColor(solidColor);
+ }
+
+ // Step 3, draw the content
+ if (!dirtyOpaque) onDraw(canvas);
+
+ // Step 4, draw the children
+ dispatchDraw(canvas);
+
+ // Step 5, draw the fade effect and restore layers
+ final Paint p = scrollabilityCache.paint;
+ final Matrix matrix = scrollabilityCache.matrix;
+ final Shader fade = scrollabilityCache.shader;
+
+ if (drawTop) {
+ matrix.setScale(1, fadeHeight * topFadeStrength);
+ matrix.postTranslate(left, top);
+ fade.setLocalMatrix(matrix);
+ p.setShader(fade);
+ canvas.drawRect(left, top, right, top + length, p);
+ }
+
+ if (drawBottom) {
+ matrix.setScale(1, fadeHeight * bottomFadeStrength);
+ matrix.postRotate(180);
+ matrix.postTranslate(left, bottom);
+ fade.setLocalMatrix(matrix);
+ p.setShader(fade);
+ canvas.drawRect(left, bottom - length, right, bottom, p);
+ }
+
+ if (drawLeft) {
+ matrix.setScale(1, fadeHeight * leftFadeStrength);
+ matrix.postRotate(-90);
+ matrix.postTranslate(left, top);
+ fade.setLocalMatrix(matrix);
+ p.setShader(fade);
+ canvas.drawRect(left, top, left + length, bottom, p);
+ }
+
+ if (drawRight) {
+ matrix.setScale(1, fadeHeight * rightFadeStrength);
+ matrix.postRotate(90);
+ matrix.postTranslate(right, top);
+ fade.setLocalMatrix(matrix);
+ p.setShader(fade);
+ canvas.drawRect(right - length, top, right, bottom, p);
+ }
+
+ canvas.restoreToCount(saveCount);
+
+ drawAutofilledHighlight(canvas);
+
+ // Overlay is part of the content and draws beneath Foreground
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().dispatchDraw(canvas);
+ }
+
+ // Step 6, draw decorations (foreground, scrollbars)
+ onDrawForeground(canvas);
+
+ if (debugDraw()) {
+ debugDrawFocus(canvas);
+ }
+ }
+
+ /**
+ * Draws the background onto the specified canvas.
+ *
+ * @param canvas Canvas on which to draw the background
+ */
+ private void drawBackground(Canvas canvas) {
+ final Drawable background = mBackground;
+ if (background == null) {
+ return;
+ }
+
+ setBackgroundBounds();
+
+ // Attempt to use a display list if requested.
+ if (canvas.isHardwareAccelerated() && mAttachInfo != null
+ && mAttachInfo.mThreadedRenderer != null) {
+ mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
+
+ final RenderNode renderNode = mBackgroundRenderNode;
+ if (renderNode != null && renderNode.isValid()) {
+ setBackgroundRenderNodeProperties(renderNode);
+ ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
+ return;
+ }
+ }
+
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ if ((scrollX | scrollY) == 0) {
+ background.draw(canvas);
+ } else {
+ canvas.translate(scrollX, scrollY);
+ background.draw(canvas);
+ canvas.translate(-scrollX, -scrollY);
+ }
+ }
+
+ /**
+ * Sets the correct background bounds and rebuilds the outline, if needed.
+ * <p/>
+ * This is called by LayoutLib.
+ */
+ void setBackgroundBounds() {
+ if (mBackgroundSizeChanged && mBackground != null) {
+ mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
+ mBackgroundSizeChanged = false;
+ rebuildOutline();
+ }
+ }
+
+ private void setBackgroundRenderNodeProperties(RenderNode renderNode) {
+ renderNode.setTranslationX(mScrollX);
+ renderNode.setTranslationY(mScrollY);
+ }
+
+ /**
+ * Creates a new display list or updates the existing display list for the
+ * specified Drawable.
+ *
+ * @param drawable Drawable for which to create a display list
+ * @param renderNode Existing RenderNode, or {@code null}
+ * @return A valid display list for the specified drawable
+ */
+ private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
+ if (renderNode == null) {
+ renderNode = RenderNode.create(drawable.getClass().getName(), this);
+ }
+
+ final Rect bounds = drawable.getBounds();
+ final int width = bounds.width();
+ final int height = bounds.height();
+ final DisplayListCanvas canvas = renderNode.start(width, height);
+
+ // Reverse left/top translation done by drawable canvas, which will
+ // instead be applied by rendernode's LTRB bounds below. This way, the
+ // drawable's bounds match with its rendernode bounds and its content
+ // will lie within those bounds in the rendernode tree.
+ canvas.translate(-bounds.left, -bounds.top);
+
+ try {
+ drawable.draw(canvas);
+ } finally {
+ renderNode.end(canvas);
+ }
+
+ // Set up drawable properties that are view-independent.
+ renderNode.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ renderNode.setProjectBackwards(drawable.isProjected());
+ renderNode.setProjectionReceiver(true);
+ renderNode.setClipToBounds(false);
+ return renderNode;
+ }
+
+ /**
+ * Returns the overlay for this view, creating it if it does not yet exist.
+ * Adding drawables to the overlay will cause them to be displayed whenever
+ * the view itself is redrawn. Objects in the overlay should be actively
+ * managed: remove them when they should not be displayed anymore. The
+ * overlay will always have the same size as its host view.
+ *
+ * <p>Note: Overlays do not currently work correctly with {@link
+ * SurfaceView} or {@link TextureView}; contents in overlays for these
+ * types of views may not display correctly.</p>
+ *
+ * @return The ViewOverlay object for this view.
+ * @see ViewOverlay
+ */
+ public ViewOverlay getOverlay() {
+ if (mOverlay == null) {
+ mOverlay = new ViewOverlay(mContext, this);
+ }
+ return mOverlay;
+ }
+
+ /**
+ * Override this if your view is known to always be drawn on top of a solid color background,
+ * and needs to draw fading edges. Returning a non-zero color enables the view system to
+ * optimize the drawing of the fading edges. If you do return a non-zero color, the alpha
+ * should be set to 0xFF.
+ *
+ * @see #setVerticalFadingEdgeEnabled(boolean)
+ * @see #setHorizontalFadingEdgeEnabled(boolean)
+ *
+ * @return The known solid color background for this view, or 0 if the color may vary
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ @ColorInt
+ public int getSolidColor() {
+ return 0;
+ }
+
+ /**
+ * Build a human readable string representation of the specified view flags.
+ *
+ * @param flags the view flags to convert to a string
+ * @return a String representing the supplied flags
+ */
+ private static String printFlags(int flags) {
+ String output = "";
+ int numFlags = 0;
+ if ((flags & FOCUSABLE) == FOCUSABLE) {
+ output += "TAKES_FOCUS";
+ numFlags++;
+ }
+
+ switch (flags & VISIBILITY_MASK) {
+ case INVISIBLE:
+ if (numFlags > 0) {
+ output += " ";
+ }
+ output += "INVISIBLE";
+ // USELESS HERE numFlags++;
+ break;
+ case GONE:
+ if (numFlags > 0) {
+ output += " ";
+ }
+ output += "GONE";
+ // USELESS HERE numFlags++;
+ break;
+ default:
+ break;
+ }
+ return output;
+ }
+
+ /**
+ * Build a human readable string representation of the specified private
+ * view flags.
+ *
+ * @param privateFlags the private view flags to convert to a string
+ * @return a String representing the supplied flags
+ */
+ private static String printPrivateFlags(int privateFlags) {
+ String output = "";
+ int numFlags = 0;
+
+ if ((privateFlags & PFLAG_WANTS_FOCUS) == PFLAG_WANTS_FOCUS) {
+ output += "WANTS_FOCUS";
+ numFlags++;
+ }
+
+ if ((privateFlags & PFLAG_FOCUSED) == PFLAG_FOCUSED) {
+ if (numFlags > 0) {
+ output += " ";
+ }
+ output += "FOCUSED";
+ numFlags++;
+ }
+
+ if ((privateFlags & PFLAG_SELECTED) == PFLAG_SELECTED) {
+ if (numFlags > 0) {
+ output += " ";
+ }
+ output += "SELECTED";
+ numFlags++;
+ }
+
+ if ((privateFlags & PFLAG_IS_ROOT_NAMESPACE) == PFLAG_IS_ROOT_NAMESPACE) {
+ if (numFlags > 0) {
+ output += " ";
+ }
+ output += "IS_ROOT_NAMESPACE";
+ numFlags++;
+ }
+
+ if ((privateFlags & PFLAG_HAS_BOUNDS) == PFLAG_HAS_BOUNDS) {
+ if (numFlags > 0) {
+ output += " ";
+ }
+ output += "HAS_BOUNDS";
+ numFlags++;
+ }
+
+ if ((privateFlags & PFLAG_DRAWN) == PFLAG_DRAWN) {
+ if (numFlags > 0) {
+ output += " ";
+ }
+ output += "DRAWN";
+ // USELESS HERE numFlags++;
+ }
+ return output;
+ }
+
+ /**
+ * <p>Indicates whether or not this view's layout will be requested during
+ * the next hierarchy layout pass.</p>
+ *
+ * @return true if the layout will be forced during next layout pass
+ */
+ public boolean isLayoutRequested() {
+ return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
+ }
+
+ /**
+ * Return true if o is a ViewGroup that is laying out using optical bounds.
+ * @hide
+ */
+ public static boolean isLayoutModeOptical(Object o) {
+ return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
+ }
+
+ private boolean setOpticalFrame(int left, int top, int right, int bottom) {
+ Insets parentInsets = mParent instanceof View ?
+ ((View) mParent).getOpticalInsets() : Insets.NONE;
+ Insets childInsets = getOpticalInsets();
+ return setFrame(
+ left + parentInsets.left - childInsets.left,
+ top + parentInsets.top - childInsets.top,
+ right + parentInsets.left + childInsets.right,
+ bottom + parentInsets.top + childInsets.bottom);
+ }
+
+ /**
+ * Assign a size and position to a view and all of its
+ * descendants
+ *
+ * <p>This is the second phase of the layout mechanism.
+ * (The first is measuring). In this phase, each parent calls
+ * layout on all of its children to position them.
+ * This is typically done using the child measurements
+ * that were stored in the measure pass().</p>
+ *
+ * <p>Derived classes should not override this method.
+ * Derived classes with children should override
+ * onLayout. In that method, they should
+ * call layout on each of their children.</p>
+ *
+ * @param l Left position, relative to parent
+ * @param t Top position, relative to parent
+ * @param r Right position, relative to parent
+ * @param b Bottom position, relative to parent
+ */
+ @SuppressWarnings({"unchecked"})
+ public void layout(int l, int t, int r, int b) {
+ if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
+ onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
+ mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
+ }
+
+ int oldL = mLeft;
+ int oldT = mTop;
+ int oldB = mBottom;
+ int oldR = mRight;
+
+ boolean changed = isLayoutModeOptical(mParent) ?
+ setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
+
+ if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
+ onLayout(changed, l, t, r, b);
+
+ if (shouldDrawRoundScrollbar()) {
+ if(mRoundScrollbarRenderer == null) {
+ mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
+ }
+ } else {
+ mRoundScrollbarRenderer = null;
+ }
+
+ mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
+
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnLayoutChangeListeners != null) {
+ ArrayList<OnLayoutChangeListener> listenersCopy =
+ (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
+ int numListeners = listenersCopy.size();
+ for (int i = 0; i < numListeners; ++i) {
+ listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
+ }
+ }
+ }
+
+ mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
+ mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
+
+ if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
+ mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
+ notifyEnterOrExitForAutoFillIfNeeded(true);
+ }
+ }
+
+ /**
+ * Called from layout when this view should
+ * assign a size and position to each of its children.
+ *
+ * Derived classes with children should override
+ * this method and call layout on each of
+ * their children.
+ * @param changed This is a new size or position for this view
+ * @param left Left position, relative to parent
+ * @param top Top position, relative to parent
+ * @param right Right position, relative to parent
+ * @param bottom Bottom position, relative to parent
+ */
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ }
+
+ /**
+ * Assign a size and position to this view.
+ *
+ * This is called from layout.
+ *
+ * @param left Left position, relative to parent
+ * @param top Top position, relative to parent
+ * @param right Right position, relative to parent
+ * @param bottom Bottom position, relative to parent
+ * @return true if the new size and position are different than the
+ * previous ones
+ * {@hide}
+ */
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean changed = false;
+
+ if (DBG) {
+ Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ + right + "," + bottom + ")");
+ }
+
+ if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
+ changed = true;
+
+ // Remember our drawn bit
+ int drawn = mPrivateFlags & PFLAG_DRAWN;
+
+ int oldWidth = mRight - mLeft;
+ int oldHeight = mBottom - mTop;
+ int newWidth = right - left;
+ int newHeight = bottom - top;
+ boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
+
+ // Invalidate our old position
+ invalidate(sizeChanged);
+
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+
+ mPrivateFlags |= PFLAG_HAS_BOUNDS;
+
+
+ if (sizeChanged) {
+ sizeChange(newWidth, newHeight, oldWidth, oldHeight);
+ }
+
+ if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
+ // If we are visible, force the DRAWN bit to on so that
+ // this invalidate will go through (at least to our parent).
+ // This is because someone may have invalidated this view
+ // before this call to setFrame came in, thereby clearing
+ // the DRAWN bit.
+ mPrivateFlags |= PFLAG_DRAWN;
+ invalidate(sizeChanged);
+ // parent display list may need to be recreated based on a change in the bounds
+ // of any child
+ invalidateParentCaches();
+ }
+
+ // Reset drawn bit to original value (invalidate turns it off)
+ mPrivateFlags |= drawn;
+
+ mBackgroundSizeChanged = true;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (mForegroundInfo != null) {
+ mForegroundInfo.mBoundsChanged = true;
+ }
+
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+ return changed;
+ }
+
+ /**
+ * Same as setFrame, but public and hidden. For use in {@link android.transition.ChangeBounds}.
+ * @hide
+ */
+ public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ setFrame(left, top, right, bottom);
+ }
+
+ private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+ onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+ if (mOverlay != null) {
+ mOverlay.getOverlayView().setRight(newWidth);
+ mOverlay.getOverlayView().setBottom(newHeight);
+ }
+ rebuildOutline();
+ }
+
+ /**
+ * Finalize inflating a view from XML. This is called as the last phase
+ * of inflation, after all child views have been added.
+ *
+ * <p>Even if the subclass overrides onFinishInflate, they should always be
+ * sure to call the super method, so that we get called.
+ */
+ @CallSuper
+ protected void onFinishInflate() {
+ }
+
+ /**
+ * Returns the resources associated with this view.
+ *
+ * @return Resources object.
+ */
+ public Resources getResources() {
+ return mResources;
+ }
+
+ /**
+ * Invalidates the specified Drawable.
+ *
+ * @param drawable the drawable to invalidate
+ */
+ @Override
+ public void invalidateDrawable(@NonNull Drawable drawable) {
+ if (verifyDrawable(drawable)) {
+ final Rect dirty = drawable.getDirtyBounds();
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+
+ invalidate(dirty.left + scrollX, dirty.top + scrollY,
+ dirty.right + scrollX, dirty.bottom + scrollY);
+ rebuildOutline();
+ }
+ }
+
+ /**
+ * Schedules an action on a drawable to occur at a specified time.
+ *
+ * @param who the recipient of the action
+ * @param what the action to run on the drawable
+ * @param when the time at which the action must occur. Uses the
+ * {@link SystemClock#uptimeMillis} timebase.
+ */
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+ if (verifyDrawable(who) && what != null) {
+ final long delay = when - SystemClock.uptimeMillis();
+ if (mAttachInfo != null) {
+ mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
+ Choreographer.CALLBACK_ANIMATION, what, who,
+ Choreographer.subtractFrameDelay(delay));
+ } else {
+ // Postpone the runnable until we know
+ // on which thread it needs to run.
+ getRunQueue().postDelayed(what, delay);
+ }
+ }
+ }
+
+ /**
+ * Cancels a scheduled action on a drawable.
+ *
+ * @param who the recipient of the action
+ * @param what the action to cancel
+ */
+ @Override
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ if (verifyDrawable(who) && what != null) {
+ if (mAttachInfo != null) {
+ mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
+ Choreographer.CALLBACK_ANIMATION, what, who);
+ }
+ getRunQueue().removeCallbacks(what);
+ }
+ }
+
+ /**
+ * Unschedule any events associated with the given Drawable. This can be
+ * used when selecting a new Drawable into a view, so that the previous
+ * one is completely unscheduled.
+ *
+ * @param who The Drawable to unschedule.
+ *
+ * @see #drawableStateChanged
+ */
+ public void unscheduleDrawable(Drawable who) {
+ if (mAttachInfo != null && who != null) {
+ mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
+ Choreographer.CALLBACK_ANIMATION, null, who);
+ }
+ }
+
+ /**
+ * Resolve the Drawables depending on the layout direction. This is implicitly supposing
+ * that the View directionality can and will be resolved before its Drawables.
+ *
+ * Will call {@link View#onResolveDrawables} when resolution is done.
+ *
+ * @hide
+ */
+ protected void resolveDrawables() {
+ // Drawables resolution may need to happen before resolving the layout direction (which is
+ // done only during the measure() call).
+ // If the layout direction is not resolved yet, we cannot resolve the Drawables except in
+ // one case: when the raw layout direction has not been defined as LAYOUT_DIRECTION_INHERIT.
+ // So, if the raw layout direction is LAYOUT_DIRECTION_LTR or LAYOUT_DIRECTION_RTL or
+ // LAYOUT_DIRECTION_LOCALE, we can "cheat" and we don't need to wait for the layout
+ // direction to be resolved as its resolved value will be the same as its raw value.
+ if (!isLayoutDirectionResolved() &&
+ getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT) {
+ return;
+ }
+
+ final int layoutDirection = isLayoutDirectionResolved() ?
+ getLayoutDirection() : getRawLayoutDirection();
+
+ if (mBackground != null) {
+ mBackground.setLayoutDirection(layoutDirection);
+ }
+ if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
+ mForegroundInfo.mDrawable.setLayoutDirection(layoutDirection);
+ }
+ if (mDefaultFocusHighlight != null) {
+ mDefaultFocusHighlight.setLayoutDirection(layoutDirection);
+ }
+ mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED;
+ onResolveDrawables(layoutDirection);
+ }
+
+ boolean areDrawablesResolved() {
+ return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED;
+ }
+
+ /**
+ * Called when layout direction has been resolved.
+ *
+ * The default implementation does nothing.
+ *
+ * @param layoutDirection The resolved layout direction.
+ *
+ * @see #LAYOUT_DIRECTION_LTR
+ * @see #LAYOUT_DIRECTION_RTL
+ *
+ * @hide
+ */
+ public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) {
+ }
+
+ /**
+ * @hide
+ */
+ protected void resetResolvedDrawables() {
+ resetResolvedDrawablesInternal();
+ }
+
+ void resetResolvedDrawablesInternal() {
+ mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED;
+ }
+
+ /**
+ * If your view subclass is displaying its own Drawable objects, it should
+ * override this function and return true for any Drawable it is
+ * displaying. This allows animations for those drawables to be
+ * scheduled.
+ *
+ * <p>Be sure to call through to the super class when overriding this
+ * function.
+ *
+ * @param who The Drawable to verify. Return true if it is one you are
+ * displaying, else return the result of calling through to the
+ * super class.
+ *
+ * @return boolean If true than the Drawable is being displayed in the
+ * view; else false and it is not allowed to animate.
+ *
+ * @see #unscheduleDrawable(android.graphics.drawable.Drawable)
+ * @see #drawableStateChanged()
+ */
+ @CallSuper
+ protected boolean verifyDrawable(@NonNull Drawable who) {
+ // Avoid verifying the scroll bar drawable so that we don't end up in
+ // an invalidation loop. This effectively prevents the scroll bar
+ // drawable from triggering invalidations and scheduling runnables.
+ return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who)
+ || (mDefaultFocusHighlight == who);
+ }
+
+ /**
+ * This function is called whenever the state of the view changes in such
+ * a way that it impacts the state of drawables being shown.
+ * <p>
+ * If the View has a StateListAnimator, it will also be called to run necessary state
+ * change animations.
+ * <p>
+ * Be sure to call through to the superclass when overriding this function.
+ *
+ * @see Drawable#setState(int[])
+ */
+ @CallSuper
+ protected void drawableStateChanged() {
+ final int[] state = getDrawableState();
+ boolean changed = false;
+
+ final Drawable bg = mBackground;
+ if (bg != null && bg.isStateful()) {
+ changed |= bg.setState(state);
+ }
+
+ final Drawable hl = mDefaultFocusHighlight;
+ if (hl != null && hl.isStateful()) {
+ changed |= hl.setState(state);
+ }
+
+ final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+ if (fg != null && fg.isStateful()) {
+ changed |= fg.setState(state);
+ }
+
+ if (mScrollCache != null) {
+ final Drawable scrollBar = mScrollCache.scrollBar;
+ if (scrollBar != null && scrollBar.isStateful()) {
+ changed |= scrollBar.setState(state)
+ && mScrollCache.state != ScrollabilityCache.OFF;
+ }
+ }
+
+ if (mStateListAnimator != null) {
+ mStateListAnimator.setState(state);
+ }
+
+ if (changed) {
+ invalidate();
+ }
+ }
+
+ /**
+ * This function is called whenever the view hotspot changes and needs to
+ * be propagated to drawables or child views managed by the view.
+ * <p>
+ * Dispatching to child views is handled by
+ * {@link #dispatchDrawableHotspotChanged(float, float)}.
+ * <p>
+ * Be sure to call through to the superclass when overriding this function.
+ *
+ * @param x hotspot x coordinate
+ * @param y hotspot y coordinate
+ */
+ @CallSuper
+ public void drawableHotspotChanged(float x, float y) {
+ if (mBackground != null) {
+ mBackground.setHotspot(x, y);
+ }
+ if (mDefaultFocusHighlight != null) {
+ mDefaultFocusHighlight.setHotspot(x, y);
+ }
+ if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
+ mForegroundInfo.mDrawable.setHotspot(x, y);
+ }
+
+ dispatchDrawableHotspotChanged(x, y);
+ }
+
+ /**
+ * Dispatches drawableHotspotChanged to all of this View's children.
+ *
+ * @param x hotspot x coordinate
+ * @param y hotspot y coordinate
+ * @see #drawableHotspotChanged(float, float)
+ */
+ public void dispatchDrawableHotspotChanged(float x, float y) {
+ }
+
+ /**
+ * Call this to force a view to update its drawable state. This will cause
+ * drawableStateChanged to be called on this view. Views that are interested
+ * in the new state should call getDrawableState.
+ *
+ * @see #drawableStateChanged
+ * @see #getDrawableState
+ */
+ public void refreshDrawableState() {
+ mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
+ drawableStateChanged();
+
+ ViewParent parent = mParent;
+ if (parent != null) {
+ parent.childDrawableStateChanged(this);
+ }
+ }
+
+ /**
+ * Create a default focus highlight if it doesn't exist.
+ * @return a default focus highlight.
+ */
+ private Drawable getDefaultFocusHighlightDrawable() {
+ if (mDefaultFocusHighlightCache == null) {
+ if (mContext != null) {
+ final int[] attrs = new int[] { android.R.attr.selectableItemBackground };
+ final TypedArray ta = mContext.obtainStyledAttributes(attrs);
+ mDefaultFocusHighlightCache = ta.getDrawable(0);
+ ta.recycle();
+ }
+ }
+ return mDefaultFocusHighlightCache;
+ }
+
+ /**
+ * Set the current default focus highlight.
+ * @param highlight the highlight drawable, or {@code null} if it's no longer needed.
+ */
+ private void setDefaultFocusHighlight(Drawable highlight) {
+ mDefaultFocusHighlight = highlight;
+ mDefaultFocusHighlightSizeChanged = true;
+ if (highlight != null) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ }
+ highlight.setLayoutDirection(getLayoutDirection());
+ if (highlight.isStateful()) {
+ highlight.setState(getDrawableState());
+ }
+ if (isAttachedToWindow()) {
+ highlight.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+ }
+ // Set callback last, since the view may still be initializing.
+ highlight.setCallback(this);
+ } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null
+ && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
+ mPrivateFlags |= PFLAG_SKIP_DRAW;
+ }
+ invalidate();
+ }
+
+ /**
+ * Check whether we need to draw a default focus highlight when this view gets focused,
+ * which requires:
+ * <ul>
+ * <li>In both background and foreground, {@link android.R.attr#state_focused}
+ * is not defined.</li>
+ * <li>This view is not in touch mode.</li>
+ * <li>This view doesn't opt out for a default focus highlight, via
+ * {@link #setDefaultFocusHighlightEnabled(boolean)}.</li>
+ * <li>This view is attached to window.</li>
+ * </ul>
+ * @return {@code true} if a default focus highlight is needed.
+ * @hide
+ */
+ @TestApi
+ public boolean isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground) {
+ final boolean lackFocusState = (background == null || !background.isStateful()
+ || !background.hasFocusStateSpecified())
+ && (foreground == null || !foreground.isStateful()
+ || !foreground.hasFocusStateSpecified());
+ return !isInTouchMode() && getDefaultFocusHighlightEnabled() && lackFocusState
+ && isAttachedToWindow() && sUseDefaultFocusHighlight;
+ }
+
+ /**
+ * When this view is focused, switches on/off the default focused highlight.
+ * <p>
+ * This always happens when this view is focused, and only at this moment the default focus
+ * highlight can be visible.
+ */
+ private void switchDefaultFocusHighlight() {
+ if (isFocused()) {
+ final boolean needed = isDefaultFocusHighlightNeeded(mBackground,
+ mForegroundInfo == null ? null : mForegroundInfo.mDrawable);
+ final boolean active = mDefaultFocusHighlight != null;
+ if (needed && !active) {
+ setDefaultFocusHighlight(getDefaultFocusHighlightDrawable());
+ } else if (!needed && active) {
+ // The highlight is no longer needed, so tear it down.
+ setDefaultFocusHighlight(null);
+ }
+ }
+ }
+
+ /**
+ * Draw the default focus highlight onto the canvas.
+ * @param canvas the canvas where we're drawing the highlight.
+ */
+ private void drawDefaultFocusHighlight(Canvas canvas) {
+ if (mDefaultFocusHighlight != null) {
+ if (mDefaultFocusHighlightSizeChanged) {
+ mDefaultFocusHighlightSizeChanged = false;
+ final int l = mScrollX;
+ final int r = l + mRight - mLeft;
+ final int t = mScrollY;
+ final int b = t + mBottom - mTop;
+ mDefaultFocusHighlight.setBounds(l, t, r, b);
+ }
+ mDefaultFocusHighlight.draw(canvas);
+ }
+ }
+
+ /**
+ * Return an array of resource IDs of the drawable states representing the
+ * current state of the view.
+ *
+ * @return The current drawable state
+ *
+ * @see Drawable#setState(int[])
+ * @see #drawableStateChanged()
+ * @see #onCreateDrawableState(int)
+ */
+ public final int[] getDrawableState() {
+ if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
+ return mDrawableState;
+ } else {
+ mDrawableState = onCreateDrawableState(0);
+ mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
+ return mDrawableState;
+ }
+ }
+
+ /**
+ * Generate the new {@link android.graphics.drawable.Drawable} state for
+ * this view. This is called by the view
+ * system when the cached Drawable state is determined to be invalid. To
+ * retrieve the current state, you should use {@link #getDrawableState}.
+ *
+ * @param extraSpace if non-zero, this is the number of extra entries you
+ * would like in the returned array in which you can place your own
+ * states.
+ *
+ * @return Returns an array holding the current {@link Drawable} state of
+ * the view.
+ *
+ * @see #mergeDrawableStates(int[], int[])
+ */
+ protected int[] onCreateDrawableState(int extraSpace) {
+ if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
+ mParent instanceof View) {
+ return ((View) mParent).onCreateDrawableState(extraSpace);
+ }
+
+ int[] drawableState;
+
+ int privateFlags = mPrivateFlags;
+
+ int viewStateIndex = 0;
+ if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
+ if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
+ if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
+ if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
+ if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
+ if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
+ if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
+ ThreadedRenderer.isAvailable()) {
+ // This is set if HW acceleration is requested, even if the current
+ // process doesn't allow it. This is just to allow app preview
+ // windows to better match their app.
+ viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
+ }
+ if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;
+
+ final int privateFlags2 = mPrivateFlags2;
+ if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
+ viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
+ }
+ if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
+ viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
+ }
+
+ drawableState = StateSet.get(viewStateIndex);
+
+ //noinspection ConstantIfStatement
+ if (false) {
+ Log.i("View", "drawableStateIndex=" + viewStateIndex);
+ Log.i("View", toString()
+ + " pressed=" + ((privateFlags & PFLAG_PRESSED) != 0)
+ + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+ + " fo=" + hasFocus()
+ + " sl=" + ((privateFlags & PFLAG_SELECTED) != 0)
+ + " wf=" + hasWindowFocus()
+ + ": " + Arrays.toString(drawableState));
+ }
+
+ if (extraSpace == 0) {
+ return drawableState;
+ }
+
+ final int[] fullState;
+ if (drawableState != null) {
+ fullState = new int[drawableState.length + extraSpace];
+ System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
+ } else {
+ fullState = new int[extraSpace];
+ }
+
+ return fullState;
+ }
+
+ /**
+ * Merge your own state values in <var>additionalState</var> into the base
+ * state values <var>baseState</var> that were returned by
+ * {@link #onCreateDrawableState(int)}.
+ *
+ * @param baseState The base state values returned by
+ * {@link #onCreateDrawableState(int)}, which will be modified to also hold your
+ * own additional state values.
+ *
+ * @param additionalState The additional state values you would like
+ * added to <var>baseState</var>; this array is not modified.
+ *
+ * @return As a convenience, the <var>baseState</var> array you originally
+ * passed into the function is returned.
+ *
+ * @see #onCreateDrawableState(int)
+ */
+ protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) {
+ final int N = baseState.length;
+ int i = N - 1;
+ while (i >= 0 && baseState[i] == 0) {
+ i--;
+ }
+ System.arraycopy(additionalState, 0, baseState, i + 1, additionalState.length);
+ return baseState;
+ }
+
+ /**
+ * Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}
+ * on all Drawable objects associated with this view.
+ * <p>
+ * Also calls {@link StateListAnimator#jumpToCurrentState()} if there is a StateListAnimator
+ * attached to this view.
+ */
+ @CallSuper
+ public void jumpDrawablesToCurrentState() {
+ if (mBackground != null) {
+ mBackground.jumpToCurrentState();
+ }
+ if (mStateListAnimator != null) {
+ mStateListAnimator.jumpToCurrentState();
+ }
+ if (mDefaultFocusHighlight != null) {
+ mDefaultFocusHighlight.jumpToCurrentState();
+ }
+ if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
+ mForegroundInfo.mDrawable.jumpToCurrentState();
+ }
+ }
+
+ /**
+ * Sets the background color for this view.
+ * @param color the color of the background
+ */
+ @RemotableViewMethod
+ public void setBackgroundColor(@ColorInt int color) {
+ if (mBackground instanceof ColorDrawable) {
+ ((ColorDrawable) mBackground.mutate()).setColor(color);
+ computeOpaqueFlags();
+ mBackgroundResource = 0;
+ } else {
+ setBackground(new ColorDrawable(color));
+ }
+ }
+
+ /**
+ * Set the background to a given resource. The resource should refer to
+ * a Drawable object or 0 to remove the background.
+ * @param resid The identifier of the resource.
+ *
+ * @attr ref android.R.styleable#View_background
+ */
+ @RemotableViewMethod
+ public void setBackgroundResource(@DrawableRes int resid) {
+ if (resid != 0 && resid == mBackgroundResource) {
+ return;
+ }
+
+ Drawable d = null;
+ if (resid != 0) {
+ d = mContext.getDrawable(resid);
+ }
+ setBackground(d);
+
+ mBackgroundResource = resid;
+ }
+
+ /**
+ * Set the background to a given Drawable, or remove the background. If the
+ * background has padding, this View's padding is set to the background's
+ * padding. However, when a background is removed, this View's padding isn't
+ * touched. If setting the padding is desired, please use
+ * {@link #setPadding(int, int, int, int)}.
+ *
+ * @param background The Drawable to use as the background, or null to remove the
+ * background
+ */
+ public void setBackground(Drawable background) {
+ //noinspection deprecation
+ setBackgroundDrawable(background);
+ }
+
+ /**
+ * @deprecated use {@link #setBackground(Drawable)} instead
+ */
+ @Deprecated
+ public void setBackgroundDrawable(Drawable background) {
+ computeOpaqueFlags();
+
+ if (background == mBackground) {
+ return;
+ }
+
+ boolean requestLayout = false;
+
+ mBackgroundResource = 0;
+
+ /*
+ * Regardless of whether we're setting a new background or not, we want
+ * to clear the previous drawable. setVisible first while we still have the callback set.
+ */
+ if (mBackground != null) {
+ if (isAttachedToWindow()) {
+ mBackground.setVisible(false, false);
+ }
+ mBackground.setCallback(null);
+ unscheduleDrawable(mBackground);
+ }
+
+ if (background != null) {
+ Rect padding = sThreadLocal.get();
+ if (padding == null) {
+ padding = new Rect();
+ sThreadLocal.set(padding);
+ }
+ resetResolvedDrawablesInternal();
+ background.setLayoutDirection(getLayoutDirection());
+ if (background.getPadding(padding)) {
+ resetResolvedPaddingInternal();
+ switch (background.getLayoutDirection()) {
+ case LAYOUT_DIRECTION_RTL:
+ mUserPaddingLeftInitial = padding.right;
+ mUserPaddingRightInitial = padding.left;
+ internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
+ break;
+ case LAYOUT_DIRECTION_LTR:
+ default:
+ mUserPaddingLeftInitial = padding.left;
+ mUserPaddingRightInitial = padding.right;
+ internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
+ }
+ mLeftPaddingDefined = false;
+ mRightPaddingDefined = false;
+ }
+
+ // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or
+ // if it has a different minimum size, we should layout again
+ if (mBackground == null
+ || mBackground.getMinimumHeight() != background.getMinimumHeight()
+ || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
+ requestLayout = true;
+ }
+
+ // Set mBackground before we set this as the callback and start making other
+ // background drawable state change calls. In particular, the setVisible call below
+ // can result in drawables attempting to start animations or otherwise invalidate,
+ // which requires the view set as the callback (us) to recognize the drawable as
+ // belonging to it as per verifyDrawable.
+ mBackground = background;
+ if (background.isStateful()) {
+ background.setState(getDrawableState());
+ }
+ if (isAttachedToWindow()) {
+ background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+ }
+
+ applyBackgroundTint();
+
+ // Set callback last, since the view may still be initializing.
+ background.setCallback(this);
+
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ requestLayout = true;
+ }
+ } else {
+ /* Remove the background */
+ mBackground = null;
+ if ((mViewFlags & WILL_NOT_DRAW) != 0
+ && (mDefaultFocusHighlight == null)
+ && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
+ mPrivateFlags |= PFLAG_SKIP_DRAW;
+ }
+
+ /*
+ * When the background is set, we try to apply its padding to this
+ * View. When the background is removed, we don't touch this View's
+ * padding. This is noted in the Javadocs. Hence, we don't need to
+ * requestLayout(), the invalidate() below is sufficient.
+ */
+
+ // The old background's minimum size could have affected this
+ // View's layout, so let's requestLayout
+ requestLayout = true;
+ }
+
+ computeOpaqueFlags();
+
+ if (requestLayout) {
+ requestLayout();
+ }
+
+ mBackgroundSizeChanged = true;
+ invalidate(true);
+ invalidateOutline();
+ }
+
+ /**
+ * Gets the background drawable
+ *
+ * @return The drawable used as the background for this view, if any.
+ *
+ * @see #setBackground(Drawable)
+ *
+ * @attr ref android.R.styleable#View_background
+ */
+ public Drawable getBackground() {
+ return mBackground;
+ }
+
+ /**
+ * Applies a tint to the background drawable. Does not modify the current tint
+ * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setBackground(Drawable)} will automatically
+ * mutate the drawable and apply the specified tint and tint mode using
+ * {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#View_backgroundTint
+ * @see #getBackgroundTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setBackgroundTintList(@Nullable ColorStateList tint) {
+ if (mBackgroundTint == null) {
+ mBackgroundTint = new TintInfo();
+ }
+ mBackgroundTint.mTintList = tint;
+ mBackgroundTint.mHasTintList = true;
+
+ applyBackgroundTint();
+ }
+
+ /**
+ * Return the tint applied to the background drawable, if specified.
+ *
+ * @return the tint applied to the background drawable
+ * @attr ref android.R.styleable#View_backgroundTint
+ * @see #setBackgroundTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getBackgroundTintList() {
+ return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setBackgroundTintList(ColorStateList)}} to the background
+ * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#View_backgroundTintMode
+ * @see #getBackgroundTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+ if (mBackgroundTint == null) {
+ mBackgroundTint = new TintInfo();
+ }
+ mBackgroundTint.mTintMode = tintMode;
+ mBackgroundTint.mHasTintMode = true;
+
+ applyBackgroundTint();
+ }
+
+ /**
+ * Return the blending mode used to apply the tint to the background
+ * drawable, if specified.
+ *
+ * @return the blending mode used to apply the tint to the background
+ * drawable
+ * @attr ref android.R.styleable#View_backgroundTintMode
+ * @see #setBackgroundTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getBackgroundTintMode() {
+ return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
+ }
+
+ private void applyBackgroundTint() {
+ if (mBackground != null && mBackgroundTint != null) {
+ final TintInfo tintInfo = mBackgroundTint;
+ if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
+ mBackground = mBackground.mutate();
+
+ if (tintInfo.mHasTintList) {
+ mBackground.setTintList(tintInfo.mTintList);
+ }
+
+ if (tintInfo.mHasTintMode) {
+ mBackground.setTintMode(tintInfo.mTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mBackground.isStateful()) {
+ mBackground.setState(getDrawableState());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the drawable used as the foreground of this View. The
+ * foreground drawable, if non-null, is always drawn on top of the view's content.
+ *
+ * @return a Drawable or null if no foreground was set
+ *
+ * @see #onDrawForeground(Canvas)
+ */
+ public Drawable getForeground() {
+ return mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+ }
+
+ /**
+ * Supply a Drawable that is to be rendered on top of all of the content in the view.
+ *
+ * @param foreground the Drawable to be drawn on top of the children
+ *
+ * @attr ref android.R.styleable#View_foreground
+ */
+ public void setForeground(Drawable foreground) {
+ if (mForegroundInfo == null) {
+ if (foreground == null) {
+ // Nothing to do.
+ return;
+ }
+ mForegroundInfo = new ForegroundInfo();
+ }
+
+ if (foreground == mForegroundInfo.mDrawable) {
+ // Nothing to do
+ return;
+ }
+
+ if (mForegroundInfo.mDrawable != null) {
+ if (isAttachedToWindow()) {
+ mForegroundInfo.mDrawable.setVisible(false, false);
+ }
+ mForegroundInfo.mDrawable.setCallback(null);
+ unscheduleDrawable(mForegroundInfo.mDrawable);
+ }
+
+ mForegroundInfo.mDrawable = foreground;
+ mForegroundInfo.mBoundsChanged = true;
+ if (foreground != null) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ }
+ foreground.setLayoutDirection(getLayoutDirection());
+ if (foreground.isStateful()) {
+ foreground.setState(getDrawableState());
+ }
+ applyForegroundTint();
+ if (isAttachedToWindow()) {
+ foreground.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+ }
+ // Set callback last, since the view may still be initializing.
+ foreground.setCallback(this);
+ } else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null
+ && (mDefaultFocusHighlight == null)) {
+ mPrivateFlags |= PFLAG_SKIP_DRAW;
+ }
+ requestLayout();
+ invalidate();
+ }
+
+ /**
+ * Magic bit used to support features of framework-internal window decor implementation details.
+ * This used to live exclusively in FrameLayout.
+ *
+ * @return true if the foreground should draw inside the padding region or false
+ * if it should draw inset by the view's padding
+ * @hide internal use only; only used by FrameLayout and internal screen layouts.
+ */
+ public boolean isForegroundInsidePadding() {
+ return mForegroundInfo != null ? mForegroundInfo.mInsidePadding : true;
+ }
+
+ /**
+ * Describes how the foreground is positioned.
+ *
+ * @return foreground gravity.
+ *
+ * @see #setForegroundGravity(int)
+ *
+ * @attr ref android.R.styleable#View_foregroundGravity
+ */
+ public int getForegroundGravity() {
+ return mForegroundInfo != null ? mForegroundInfo.mGravity
+ : Gravity.START | Gravity.TOP;
+ }
+
+ /**
+ * Describes how the foreground is positioned. Defaults to START and TOP.
+ *
+ * @param gravity see {@link android.view.Gravity}
+ *
+ * @see #getForegroundGravity()
+ *
+ * @attr ref android.R.styleable#View_foregroundGravity
+ */
+ public void setForegroundGravity(int gravity) {
+ if (mForegroundInfo == null) {
+ mForegroundInfo = new ForegroundInfo();
+ }
+
+ if (mForegroundInfo.mGravity != gravity) {
+ if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.START;
+ }
+
+ if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.TOP;
+ }
+
+ mForegroundInfo.mGravity = gravity;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Applies a tint to the foreground drawable. Does not modify the current tint
+ * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setForeground(Drawable)} will automatically
+ * mutate the drawable and apply the specified tint and tint mode using
+ * {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#View_foregroundTint
+ * @see #getForegroundTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setForegroundTintList(@Nullable ColorStateList tint) {
+ if (mForegroundInfo == null) {
+ mForegroundInfo = new ForegroundInfo();
+ }
+ if (mForegroundInfo.mTintInfo == null) {
+ mForegroundInfo.mTintInfo = new TintInfo();
+ }
+ mForegroundInfo.mTintInfo.mTintList = tint;
+ mForegroundInfo.mTintInfo.mHasTintList = true;
+
+ applyForegroundTint();
+ }
+
+ /**
+ * Return the tint applied to the foreground drawable, if specified.
+ *
+ * @return the tint applied to the foreground drawable
+ * @attr ref android.R.styleable#View_foregroundTint
+ * @see #setForegroundTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getForegroundTintList() {
+ return mForegroundInfo != null && mForegroundInfo.mTintInfo != null
+ ? mForegroundInfo.mTintInfo.mTintList : null;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setForegroundTintList(ColorStateList)}} to the background
+ * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#View_foregroundTintMode
+ * @see #getForegroundTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setForegroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+ if (mForegroundInfo == null) {
+ mForegroundInfo = new ForegroundInfo();
+ }
+ if (mForegroundInfo.mTintInfo == null) {
+ mForegroundInfo.mTintInfo = new TintInfo();
+ }
+ mForegroundInfo.mTintInfo.mTintMode = tintMode;
+ mForegroundInfo.mTintInfo.mHasTintMode = true;
+
+ applyForegroundTint();
+ }
+
+ /**
+ * Return the blending mode used to apply the tint to the foreground
+ * drawable, if specified.
+ *
+ * @return the blending mode used to apply the tint to the foreground
+ * drawable
+ * @attr ref android.R.styleable#View_foregroundTintMode
+ * @see #setForegroundTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getForegroundTintMode() {
+ return mForegroundInfo != null && mForegroundInfo.mTintInfo != null
+ ? mForegroundInfo.mTintInfo.mTintMode : null;
+ }
+
+ private void applyForegroundTint() {
+ if (mForegroundInfo != null && mForegroundInfo.mDrawable != null
+ && mForegroundInfo.mTintInfo != null) {
+ final TintInfo tintInfo = mForegroundInfo.mTintInfo;
+ if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
+ mForegroundInfo.mDrawable = mForegroundInfo.mDrawable.mutate();
+
+ if (tintInfo.mHasTintList) {
+ mForegroundInfo.mDrawable.setTintList(tintInfo.mTintList);
+ }
+
+ if (tintInfo.mHasTintMode) {
+ mForegroundInfo.mDrawable.setTintMode(tintInfo.mTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mForegroundInfo.mDrawable.isStateful()) {
+ mForegroundInfo.mDrawable.setState(getDrawableState());
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the drawable to be overlayed when a view is autofilled
+ *
+ * @return The drawable
+ *
+ * @throws IllegalStateException if the drawable could not be found.
+ */
+ @Nullable private Drawable getAutofilledDrawable() {
+ if (mAttachInfo == null) {
+ return null;
+ }
+ // Lazily load the isAutofilled drawable.
+ if (mAttachInfo.mAutofilledDrawable == null) {
+ Context rootContext = getRootView().getContext();
+ TypedArray a = rootContext.getTheme().obtainStyledAttributes(AUTOFILL_HIGHLIGHT_ATTR);
+ int attributeResourceId = a.getResourceId(0, 0);
+ mAttachInfo.mAutofilledDrawable = rootContext.getDrawable(attributeResourceId);
+ a.recycle();
+ }
+
+ return mAttachInfo.mAutofilledDrawable;
+ }
+
+ /**
+ * Draw {@link View#isAutofilled()} highlight over view if the view is autofilled.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawAutofilledHighlight(@NonNull Canvas canvas) {
+ if (isAutofilled()) {
+ Drawable autofilledHighlight = getAutofilledDrawable();
+
+ if (autofilledHighlight != null) {
+ autofilledHighlight.setBounds(0, 0, getWidth(), getHeight());
+ autofilledHighlight.draw(canvas);
+ }
+ }
+ }
+
+ /**
+ * Draw any foreground content for this view.
+ *
+ * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}
+ * drawable or other view-specific decorations. The foreground is drawn on top of the
+ * primary view content.</p>
+ *
+ * @param canvas canvas to draw into
+ */
+ public void onDrawForeground(Canvas canvas) {
+ onDrawScrollIndicators(canvas);
+ onDrawScrollBars(canvas);
+
+ final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
+ if (foreground != null) {
+ if (mForegroundInfo.mBoundsChanged) {
+ mForegroundInfo.mBoundsChanged = false;
+ final Rect selfBounds = mForegroundInfo.mSelfBounds;
+ final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
+
+ if (mForegroundInfo.mInsidePadding) {
+ selfBounds.set(0, 0, getWidth(), getHeight());
+ } else {
+ selfBounds.set(getPaddingLeft(), getPaddingTop(),
+ getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
+ }
+
+ final int ld = getLayoutDirection();
+ Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
+ foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
+ foreground.setBounds(overlayBounds);
+ }
+
+ foreground.draw(canvas);
+ }
+ }
+
+ /**
+ * Sets the padding. The view may add on the space required to display
+ * the scrollbars, depending on the style and visibility of the scrollbars.
+ * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop},
+ * {@link #getPaddingRight} and {@link #getPaddingBottom} may be different
+ * from the values set in this call.
+ *
+ * @attr ref android.R.styleable#View_padding
+ * @attr ref android.R.styleable#View_paddingBottom
+ * @attr ref android.R.styleable#View_paddingLeft
+ * @attr ref android.R.styleable#View_paddingRight
+ * @attr ref android.R.styleable#View_paddingTop
+ * @param left the left padding in pixels
+ * @param top the top padding in pixels
+ * @param right the right padding in pixels
+ * @param bottom the bottom padding in pixels
+ */
+ public void setPadding(int left, int top, int right, int bottom) {
+ resetResolvedPaddingInternal();
+
+ mUserPaddingStart = UNDEFINED_PADDING;
+ mUserPaddingEnd = UNDEFINED_PADDING;
+
+ mUserPaddingLeftInitial = left;
+ mUserPaddingRightInitial = right;
+
+ mLeftPaddingDefined = true;
+ mRightPaddingDefined = true;
+
+ internalSetPadding(left, top, right, bottom);
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetPadding(int left, int top, int right, int bottom) {
+ mUserPaddingLeft = left;
+ mUserPaddingRight = right;
+ mUserPaddingBottom = bottom;
+
+ final int viewFlags = mViewFlags;
+ boolean changed = false;
+
+ // Common case is there are no scroll bars.
+ if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) {
+ if ((viewFlags & SCROLLBARS_VERTICAL) != 0) {
+ final int offset = (viewFlags & SCROLLBARS_INSET_MASK) == 0
+ ? 0 : getVerticalScrollbarWidth();
+ switch (mVerticalScrollbarPosition) {
+ case SCROLLBAR_POSITION_DEFAULT:
+ if (isLayoutRtl()) {
+ left += offset;
+ } else {
+ right += offset;
+ }
+ break;
+ case SCROLLBAR_POSITION_RIGHT:
+ right += offset;
+ break;
+ case SCROLLBAR_POSITION_LEFT:
+ left += offset;
+ break;
+ }
+ }
+ if ((viewFlags & SCROLLBARS_HORIZONTAL) != 0) {
+ bottom += (viewFlags & SCROLLBARS_INSET_MASK) == 0
+ ? 0 : getHorizontalScrollbarHeight();
+ }
+ }
+
+ if (mPaddingLeft != left) {
+ changed = true;
+ mPaddingLeft = left;
+ }
+ if (mPaddingTop != top) {
+ changed = true;
+ mPaddingTop = top;
+ }
+ if (mPaddingRight != right) {
+ changed = true;
+ mPaddingRight = right;
+ }
+ if (mPaddingBottom != bottom) {
+ changed = true;
+ mPaddingBottom = bottom;
+ }
+
+ if (changed) {
+ requestLayout();
+ invalidateOutline();
+ }
+ }
+
+ /**
+ * Sets the relative padding. The view may add on the space required to display
+ * the scrollbars, depending on the style and visibility of the scrollbars.
+ * So the values returned from {@link #getPaddingStart}, {@link #getPaddingTop},
+ * {@link #getPaddingEnd} and {@link #getPaddingBottom} may be different
+ * from the values set in this call.
+ *
+ * @attr ref android.R.styleable#View_padding
+ * @attr ref android.R.styleable#View_paddingBottom
+ * @attr ref android.R.styleable#View_paddingStart
+ * @attr ref android.R.styleable#View_paddingEnd
+ * @attr ref android.R.styleable#View_paddingTop
+ * @param start the start padding in pixels
+ * @param top the top padding in pixels
+ * @param end the end padding in pixels
+ * @param bottom the bottom padding in pixels
+ */
+ public void setPaddingRelative(int start, int top, int end, int bottom) {
+ resetResolvedPaddingInternal();
+
+ mUserPaddingStart = start;
+ mUserPaddingEnd = end;
+ mLeftPaddingDefined = true;
+ mRightPaddingDefined = true;
+
+ switch(getLayoutDirection()) {
+ case LAYOUT_DIRECTION_RTL:
+ mUserPaddingLeftInitial = end;
+ mUserPaddingRightInitial = start;
+ internalSetPadding(end, top, start, bottom);
+ break;
+ case LAYOUT_DIRECTION_LTR:
+ default:
+ mUserPaddingLeftInitial = start;
+ mUserPaddingRightInitial = end;
+ internalSetPadding(start, top, end, bottom);
+ }
+ }
+
+ /**
+ * Returns the top padding of this view.
+ *
+ * @return the top padding in pixels
+ */
+ public int getPaddingTop() {
+ return mPaddingTop;
+ }
+
+ /**
+ * Returns the bottom padding of this view. If there are inset and enabled
+ * scrollbars, this value may include the space required to display the
+ * scrollbars as well.
+ *
+ * @return the bottom padding in pixels
+ */
+ public int getPaddingBottom() {
+ return mPaddingBottom;
+ }
+
+ /**
+ * Returns the left padding of this view. If there are inset and enabled
+ * scrollbars, this value may include the space required to display the
+ * scrollbars as well.
+ *
+ * @return the left padding in pixels
+ */
+ public int getPaddingLeft() {
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ return mPaddingLeft;
+ }
+
+ /**
+ * Returns the start padding of this view depending on its resolved layout direction.
+ * If there are inset and enabled scrollbars, this value may include the space
+ * required to display the scrollbars as well.
+ *
+ * @return the start padding in pixels
+ */
+ public int getPaddingStart() {
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ mPaddingRight : mPaddingLeft;
+ }
+
+ /**
+ * Returns the right padding of this view. If there are inset and enabled
+ * scrollbars, this value may include the space required to display the
+ * scrollbars as well.
+ *
+ * @return the right padding in pixels
+ */
+ public int getPaddingRight() {
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ return mPaddingRight;
+ }
+
+ /**
+ * Returns the end padding of this view depending on its resolved layout direction.
+ * If there are inset and enabled scrollbars, this value may include the space
+ * required to display the scrollbars as well.
+ *
+ * @return the end padding in pixels
+ */
+ public int getPaddingEnd() {
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ mPaddingLeft : mPaddingRight;
+ }
+
+ /**
+ * Return if the padding has been set through relative values
+ * {@link #setPaddingRelative(int, int, int, int)} or through
+ * @attr ref android.R.styleable#View_paddingStart or
+ * @attr ref android.R.styleable#View_paddingEnd
+ *
+ * @return true if the padding is relative or false if it is not.
+ */
+ public boolean isPaddingRelative() {
+ return (mUserPaddingStart != UNDEFINED_PADDING || mUserPaddingEnd != UNDEFINED_PADDING);
+ }
+
+ Insets computeOpticalInsets() {
+ return (mBackground == null) ? Insets.NONE : mBackground.getOpticalInsets();
+ }
+
+ /**
+ * @hide
+ */
+ public void resetPaddingToInitialValues() {
+ if (isRtlCompatibilityMode()) {
+ mPaddingLeft = mUserPaddingLeftInitial;
+ mPaddingRight = mUserPaddingRightInitial;
+ return;
+ }
+ if (isLayoutRtl()) {
+ mPaddingLeft = (mUserPaddingEnd >= 0) ? mUserPaddingEnd : mUserPaddingLeftInitial;
+ mPaddingRight = (mUserPaddingStart >= 0) ? mUserPaddingStart : mUserPaddingRightInitial;
+ } else {
+ mPaddingLeft = (mUserPaddingStart >= 0) ? mUserPaddingStart : mUserPaddingLeftInitial;
+ mPaddingRight = (mUserPaddingEnd >= 0) ? mUserPaddingEnd : mUserPaddingRightInitial;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public Insets getOpticalInsets() {
+ if (mLayoutInsets == null) {
+ mLayoutInsets = computeOpticalInsets();
+ }
+ return mLayoutInsets;
+ }
+
+ /**
+ * Set this view's optical insets.
+ *
+ * <p>This method should be treated similarly to setMeasuredDimension and not as a general
+ * property. Views that compute their own optical insets should call it as part of measurement.
+ * This method does not request layout. If you are setting optical insets outside of
+ * measure/layout itself you will want to call requestLayout() yourself.
+ * </p>
+ * @hide
+ */
+ public void setOpticalInsets(Insets insets) {
+ mLayoutInsets = insets;
+ }
+
+ /**
+ * Changes the selection state of this view. A view can be selected or not.
+ * Note that selection is not the same as focus. Views are typically
+ * selected in the context of an AdapterView like ListView or GridView;
+ * the selected view is the view that is highlighted.
+ *
+ * @param selected true if the view must be selected, false otherwise
+ */
+ public void setSelected(boolean selected) {
+ //noinspection DoubleNegation
+ if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) {
+ mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0);
+ if (!selected) resetPressedState();
+ invalidate(true);
+ refreshDrawableState();
+ dispatchSetSelected(selected);
+ if (selected) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ } else {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+ }
+
+ /**
+ * Dispatch setSelected to all of this View's children.
+ *
+ * @see #setSelected(boolean)
+ *
+ * @param selected The new selected state
+ */
+ protected void dispatchSetSelected(boolean selected) {
+ }
+
+ /**
+ * Indicates the selection state of this view.
+ *
+ * @return true if the view is selected, false otherwise
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isSelected() {
+ return (mPrivateFlags & PFLAG_SELECTED) != 0;
+ }
+
+ /**
+ * Changes the activated state of this view. A view can be activated or not.
+ * Note that activation is not the same as selection. Selection is
+ * a transient property, representing the view (hierarchy) the user is
+ * currently interacting with. Activation is a longer-term state that the
+ * user can move views in and out of. For example, in a list view with
+ * single or multiple selection enabled, the views in the current selection
+ * set are activated. (Um, yeah, we are deeply sorry about the terminology
+ * here.) The activated state is propagated down to children of the view it
+ * is set on.
+ *
+ * @param activated true if the view must be activated, false otherwise
+ */
+ public void setActivated(boolean activated) {
+ //noinspection DoubleNegation
+ if (((mPrivateFlags & PFLAG_ACTIVATED) != 0) != activated) {
+ mPrivateFlags = (mPrivateFlags & ~PFLAG_ACTIVATED) | (activated ? PFLAG_ACTIVATED : 0);
+ invalidate(true);
+ refreshDrawableState();
+ dispatchSetActivated(activated);
+ }
+ }
+
+ /**
+ * Dispatch setActivated to all of this View's children.
+ *
+ * @see #setActivated(boolean)
+ *
+ * @param activated The new activated state
+ */
+ protected void dispatchSetActivated(boolean activated) {
+ }
+
+ /**
+ * Indicates the activation state of this view.
+ *
+ * @return true if the view is activated, false otherwise
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isActivated() {
+ return (mPrivateFlags & PFLAG_ACTIVATED) != 0;
+ }
+
+ /**
+ * Returns the ViewTreeObserver for this view's hierarchy. The view tree
+ * observer can be used to get notifications when global events, like
+ * layout, happen.
+ *
+ * The returned ViewTreeObserver observer is not guaranteed to remain
+ * valid for the lifetime of this View. If the caller of this method keeps
+ * a long-lived reference to ViewTreeObserver, it should always check for
+ * the return value of {@link ViewTreeObserver#isAlive()}.
+ *
+ * @return The ViewTreeObserver for this view's hierarchy.
+ */
+ public ViewTreeObserver getViewTreeObserver() {
+ if (mAttachInfo != null) {
+ return mAttachInfo.mTreeObserver;
+ }
+ if (mFloatingTreeObserver == null) {
+ mFloatingTreeObserver = new ViewTreeObserver(mContext);
+ }
+ return mFloatingTreeObserver;
+ }
+
+ /**
+ * <p>Finds the topmost view in the current view hierarchy.</p>
+ *
+ * @return the topmost view containing this view
+ */
+ public View getRootView() {
+ if (mAttachInfo != null) {
+ final View v = mAttachInfo.mRootView;
+ if (v != null) {
+ return v;
+ }
+ }
+
+ View parent = this;
+
+ while (parent.mParent != null && parent.mParent instanceof View) {
+ parent = (View) parent.mParent;
+ }
+
+ return parent;
+ }
+
+ /**
+ * Transforms a motion event from view-local coordinates to on-screen
+ * coordinates.
+ *
+ * @param ev the view-local motion event
+ * @return false if the transformation could not be applied
+ * @hide
+ */
+ public boolean toGlobalMotionEvent(MotionEvent ev) {
+ final AttachInfo info = mAttachInfo;
+ if (info == null) {
+ return false;
+ }
+
+ final Matrix m = info.mTmpMatrix;
+ m.set(Matrix.IDENTITY_MATRIX);
+ transformMatrixToGlobal(m);
+ ev.transform(m);
+ return true;
+ }
+
+ /**
+ * Transforms a motion event from on-screen coordinates to view-local
+ * coordinates.
+ *
+ * @param ev the on-screen motion event
+ * @return false if the transformation could not be applied
+ * @hide
+ */
+ public boolean toLocalMotionEvent(MotionEvent ev) {
+ final AttachInfo info = mAttachInfo;
+ if (info == null) {
+ return false;
+ }
+
+ final Matrix m = info.mTmpMatrix;
+ m.set(Matrix.IDENTITY_MATRIX);
+ transformMatrixToLocal(m);
+ ev.transform(m);
+ return true;
+ }
+
+ /**
+ * Modifies the input matrix such that it maps view-local coordinates to
+ * on-screen coordinates.
+ *
+ * @param m input matrix to modify
+ * @hide
+ */
+ public void transformMatrixToGlobal(Matrix m) {
+ final ViewParent parent = mParent;
+ if (parent instanceof View) {
+ final View vp = (View) parent;
+ vp.transformMatrixToGlobal(m);
+ m.preTranslate(-vp.mScrollX, -vp.mScrollY);
+ } else if (parent instanceof ViewRootImpl) {
+ final ViewRootImpl vr = (ViewRootImpl) parent;
+ vr.transformMatrixToGlobal(m);
+ m.preTranslate(0, -vr.mCurScrollY);
+ }
+
+ m.preTranslate(mLeft, mTop);
+
+ if (!hasIdentityMatrix()) {
+ m.preConcat(getMatrix());
+ }
+ }
+
+ /**
+ * Modifies the input matrix such that it maps on-screen coordinates to
+ * view-local coordinates.
+ *
+ * @param m input matrix to modify
+ * @hide
+ */
+ public void transformMatrixToLocal(Matrix m) {
+ final ViewParent parent = mParent;
+ if (parent instanceof View) {
+ final View vp = (View) parent;
+ vp.transformMatrixToLocal(m);
+ m.postTranslate(vp.mScrollX, vp.mScrollY);
+ } else if (parent instanceof ViewRootImpl) {
+ final ViewRootImpl vr = (ViewRootImpl) parent;
+ vr.transformMatrixToLocal(m);
+ m.postTranslate(0, vr.mCurScrollY);
+ }
+
+ m.postTranslate(-mLeft, -mTop);
+
+ if (!hasIdentityMatrix()) {
+ m.postConcat(getInverseMatrix());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "layout", indexMapping = {
+ @ViewDebug.IntToString(from = 0, to = "x"),
+ @ViewDebug.IntToString(from = 1, to = "y")
+ })
+ public int[] getLocationOnScreen() {
+ int[] location = new int[2];
+ getLocationOnScreen(location);
+ return location;
+ }
+
+ /**
+ * <p>Computes the coordinates of this view on the screen. The argument
+ * must be an array of two integers. After the method returns, the array
+ * contains the x and y location in that order.</p>
+ *
+ * @param outLocation an array of two integers in which to hold the coordinates
+ */
+ public void getLocationOnScreen(@Size(2) int[] outLocation) {
+ getLocationInWindow(outLocation);
+
+ final AttachInfo info = mAttachInfo;
+ if (info != null) {
+ outLocation[0] += info.mWindowLeft;
+ outLocation[1] += info.mWindowTop;
+ }
+ }
+
+ /**
+ * <p>Computes the coordinates of this view in its window. The argument
+ * must be an array of two integers. After the method returns, the array
+ * contains the x and y location in that order.</p>
+ *
+ * @param outLocation an array of two integers in which to hold the coordinates
+ */
+ public void getLocationInWindow(@Size(2) int[] outLocation) {
+ if (outLocation == null || outLocation.length < 2) {
+ throw new IllegalArgumentException("outLocation must be an array of two integers");
+ }
+
+ outLocation[0] = 0;
+ outLocation[1] = 0;
+
+ transformFromViewToWindowSpace(outLocation);
+ }
+
+ /** @hide */
+ public void transformFromViewToWindowSpace(@Size(2) int[] inOutLocation) {
+ if (inOutLocation == null || inOutLocation.length < 2) {
+ throw new IllegalArgumentException("inOutLocation must be an array of two integers");
+ }
+
+ if (mAttachInfo == null) {
+ // When the view is not attached to a window, this method does not make sense
+ inOutLocation[0] = inOutLocation[1] = 0;
+ return;
+ }
+
+ float position[] = mAttachInfo.mTmpTransformLocation;
+ position[0] = inOutLocation[0];
+ position[1] = inOutLocation[1];
+
+ if (!hasIdentityMatrix()) {
+ getMatrix().mapPoints(position);
+ }
+
+ position[0] += mLeft;
+ position[1] += mTop;
+
+ ViewParent viewParent = mParent;
+ while (viewParent instanceof View) {
+ final View view = (View) viewParent;
+
+ position[0] -= view.mScrollX;
+ position[1] -= view.mScrollY;
+
+ if (!view.hasIdentityMatrix()) {
+ view.getMatrix().mapPoints(position);
+ }
+
+ position[0] += view.mLeft;
+ position[1] += view.mTop;
+
+ viewParent = view.mParent;
+ }
+
+ if (viewParent instanceof ViewRootImpl) {
+ // *cough*
+ final ViewRootImpl vr = (ViewRootImpl) viewParent;
+ position[1] -= vr.mCurScrollY;
+ }
+
+ inOutLocation[0] = Math.round(position[0]);
+ inOutLocation[1] = Math.round(position[1]);
+ }
+
+ /**
+ * @param id the id of the view to be found
+ * @return the view of the specified id, null if cannot be found
+ * @hide
+ */
+ protected <T extends View> T findViewTraversal(@IdRes int id) {
+ if (id == mID) {
+ return (T) this;
+ }
+ return null;
+ }
+
+ /**
+ * @param tag the tag of the view to be found
+ * @return the view of specified tag, null if cannot be found
+ * @hide
+ */
+ protected <T extends View> T findViewWithTagTraversal(Object tag) {
+ if (tag != null && tag.equals(mTag)) {
+ return (T) this;
+ }
+ return null;
+ }
+
+ /**
+ * @param predicate The predicate to evaluate.
+ * @param childToSkip If not null, ignores this child during the recursive traversal.
+ * @return The first view that matches the predicate or null.
+ * @hide
+ */
+ protected <T extends View> T findViewByPredicateTraversal(Predicate<View> predicate,
+ View childToSkip) {
+ if (predicate.test(this)) {
+ return (T) this;
+ }
+ return null;
+ }
+
+ /**
+ * Finds the first descendant view with the given ID, the view itself if
+ * the ID matches {@link #getId()}, or {@code null} if the ID is invalid
+ * (< 0) or there is no matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
+ */
+ @Nullable
+ public final <T extends View> T findViewById(@IdRes int id) {
+ if (id == NO_ID) {
+ return null;
+ }
+ return findViewTraversal(id);
+ }
+
+ /**
+ * Finds a view by its unuque and stable accessibility id.
+ *
+ * @param accessibilityId The searched accessibility id.
+ * @return The found view.
+ */
+ final <T extends View> T findViewByAccessibilityId(int accessibilityId) {
+ if (accessibilityId < 0) {
+ return null;
+ }
+ T view = findViewByAccessibilityIdTraversal(accessibilityId);
+ if (view != null) {
+ return view.includeForAccessibility() ? view : null;
+ }
+ return null;
+ }
+
+ /**
+ * Performs the traversal to find a view by its unique and stable accessibility id.
+ *
+ * <strong>Note:</strong>This method does not stop at the root namespace
+ * boundary since the user can touch the screen at an arbitrary location
+ * potentially crossing the root namespace boundary which will send an
+ * accessibility event to accessibility services and they should be able
+ * to obtain the event source. Also accessibility ids are guaranteed to be
+ * unique in the window.
+ *
+ * @param accessibilityId The accessibility id.
+ * @return The found view.
+ * @hide
+ */
+ public <T extends View> T findViewByAccessibilityIdTraversal(int accessibilityId) {
+ if (getAccessibilityViewId() == accessibilityId) {
+ return (T) this;
+ }
+ return null;
+ }
+
+ /**
+ * Performs the traversal to find a view by its autofill id.
+ *
+ * <strong>Note:</strong>This method does not stop at the root namespace
+ * boundary.
+ *
+ * @param autofillId The autofill id.
+ * @return The found view.
+ * @hide
+ */
+ public <T extends View> T findViewByAutofillIdTraversal(int autofillId) {
+ if (getAutofillViewId() == autofillId) {
+ return (T) this;
+ }
+ return null;
+ }
+
+ /**
+ * Look for a child view with the given tag. If this view has the given
+ * tag, return this view.
+ *
+ * @param tag The tag to search for, using "tag.equals(getTag())".
+ * @return The View that has the given tag in the hierarchy or null
+ */
+ public final <T extends View> T findViewWithTag(Object tag) {
+ if (tag == null) {
+ return null;
+ }
+ return findViewWithTagTraversal(tag);
+ }
+
+ /**
+ * Look for a child view that matches the specified predicate.
+ * If this view matches the predicate, return this view.
+ *
+ * @param predicate The predicate to evaluate.
+ * @return The first view that matches the predicate or null.
+ * @hide
+ */
+ public final <T extends View> T findViewByPredicate(Predicate<View> predicate) {
+ return findViewByPredicateTraversal(predicate, null);
+ }
+
+ /**
+ * Look for a child view that matches the specified predicate,
+ * starting with the specified view and its descendents and then
+ * recusively searching the ancestors and siblings of that view
+ * until this view is reached.
+ *
+ * This method is useful in cases where the predicate does not match
+ * a single unique view (perhaps multiple views use the same id)
+ * and we are trying to find the view that is "closest" in scope to the
+ * starting view.
+ *
+ * @param start The view to start from.
+ * @param predicate The predicate to evaluate.
+ * @return The first view that matches the predicate or null.
+ * @hide
+ */
+ public final <T extends View> T findViewByPredicateInsideOut(
+ View start, Predicate<View> predicate) {
+ View childToSkip = null;
+ for (;;) {
+ T view = start.findViewByPredicateTraversal(predicate, childToSkip);
+ if (view != null || start == this) {
+ return view;
+ }
+
+ ViewParent parent = start.getParent();
+ if (parent == null || !(parent instanceof View)) {
+ return null;
+ }
+
+ childToSkip = start;
+ start = (View) parent;
+ }
+ }
+
+ /**
+ * Sets the identifier for this view. The identifier does not have to be
+ * unique in this view's hierarchy. The identifier should be a positive
+ * number.
+ *
+ * @see #NO_ID
+ * @see #getId()
+ * @see #findViewById(int)
+ *
+ * @param id a number used to identify the view
+ *
+ * @attr ref android.R.styleable#View_id
+ */
+ public void setId(@IdRes int id) {
+ mID = id;
+ if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
+ mID = generateViewId();
+ }
+ }
+
+ /**
+ * {@hide}
+ *
+ * @param isRoot true if the view belongs to the root namespace, false
+ * otherwise
+ */
+ public void setIsRootNamespace(boolean isRoot) {
+ if (isRoot) {
+ mPrivateFlags |= PFLAG_IS_ROOT_NAMESPACE;
+ } else {
+ mPrivateFlags &= ~PFLAG_IS_ROOT_NAMESPACE;
+ }
+ }
+
+ /**
+ * {@hide}
+ *
+ * @return true if the view belongs to the root namespace, false otherwise
+ */
+ public boolean isRootNamespace() {
+ return (mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0;
+ }
+
+ /**
+ * Returns this view's identifier.
+ *
+ * @return a positive integer used to identify the view or {@link #NO_ID}
+ * if the view has no ID
+ *
+ * @see #setId(int)
+ * @see #findViewById(int)
+ * @attr ref android.R.styleable#View_id
+ */
+ @IdRes
+ @ViewDebug.CapturedViewProperty
+ public int getId() {
+ return mID;
+ }
+
+ /**
+ * Returns this view's tag.
+ *
+ * @return the Object stored in this view as a tag, or {@code null} if not
+ * set
+ *
+ * @see #setTag(Object)
+ * @see #getTag(int)
+ */
+ @ViewDebug.ExportedProperty
+ public Object getTag() {
+ return mTag;
+ }
+
+ /**
+ * Sets the tag associated with this view. A tag can be used to mark
+ * a view in its hierarchy and does not have to be unique within the
+ * hierarchy. Tags can also be used to store data within a view without
+ * resorting to another data structure.
+ *
+ * @param tag an Object to tag the view with
+ *
+ * @see #getTag()
+ * @see #setTag(int, Object)
+ */
+ public void setTag(final Object tag) {
+ mTag = tag;
+ }
+
+ /**
+ * Returns the tag associated with this view and the specified key.
+ *
+ * @param key The key identifying the tag
+ *
+ * @return the Object stored in this view as a tag, or {@code null} if not
+ * set
+ *
+ * @see #setTag(int, Object)
+ * @see #getTag()
+ */
+ public Object getTag(int key) {
+ if (mKeyedTags != null) return mKeyedTags.get(key);
+ return null;
+ }
+
+ /**
+ * Sets a tag associated with this view and a key. A tag can be used
+ * to mark a view in its hierarchy and does not have to be unique within
+ * the hierarchy. Tags can also be used to store data within a view
+ * without resorting to another data structure.
+ *
+ * The specified key should be an id declared in the resources of the
+ * application to ensure it is unique (see the <a
+ * href="{@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>).
+ * Keys identified as belonging to
+ * the Android framework or not associated with any package will cause
+ * an {@link IllegalArgumentException} to be thrown.
+ *
+ * @param key The key identifying the tag
+ * @param tag An Object to tag the view with
+ *
+ * @throws IllegalArgumentException If they specified key is not valid
+ *
+ * @see #setTag(Object)
+ * @see #getTag(int)
+ */
+ public void setTag(int key, final Object tag) {
+ // If the package id is 0x00 or 0x01, it's either an undefined package
+ // or a framework id
+ if ((key >>> 24) < 2) {
+ throw new IllegalArgumentException("The key must be an application-specific "
+ + "resource id.");
+ }
+
+ setKeyedTag(key, tag);
+ }
+
+ /**
+ * Variation of {@link #setTag(int, Object)} that enforces the key to be a
+ * framework id.
+ *
+ * @hide
+ */
+ public void setTagInternal(int key, Object tag) {
+ if ((key >>> 24) != 0x1) {
+ throw new IllegalArgumentException("The key must be a framework-specific "
+ + "resource id.");
+ }
+
+ setKeyedTag(key, tag);
+ }
+
+ private void setKeyedTag(int key, Object tag) {
+ if (mKeyedTags == null) {
+ mKeyedTags = new SparseArray<Object>(2);
+ }
+
+ mKeyedTags.put(key, tag);
+ }
+
+ /**
+ * Prints information about this view in the log output, with the tag
+ * {@link #VIEW_LOG_TAG}.
+ *
+ * @hide
+ */
+ public void debug() {
+ debug(0);
+ }
+
+ /**
+ * Prints information about this view in the log output, with the tag
+ * {@link #VIEW_LOG_TAG}. Each line in the output is preceded with an
+ * indentation defined by the <code>depth</code>.
+ *
+ * @param depth the indentation level
+ *
+ * @hide
+ */
+ protected void debug(int depth) {
+ String output = debugIndent(depth - 1);
+
+ output += "+ " + this;
+ int id = getId();
+ if (id != -1) {
+ output += " (id=" + id + ")";
+ }
+ Object tag = getTag();
+ if (tag != null) {
+ output += " (tag=" + tag + ")";
+ }
+ Log.d(VIEW_LOG_TAG, output);
+
+ if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+ output = debugIndent(depth) + " FOCUSED";
+ Log.d(VIEW_LOG_TAG, output);
+ }
+
+ output = debugIndent(depth);
+ output += "frame={" + mLeft + ", " + mTop + ", " + mRight
+ + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
+ + "} ";
+ Log.d(VIEW_LOG_TAG, output);
+
+ if (mPaddingLeft != 0 || mPaddingTop != 0 || mPaddingRight != 0
+ || mPaddingBottom != 0) {
+ output = debugIndent(depth);
+ output += "padding={" + mPaddingLeft + ", " + mPaddingTop
+ + ", " + mPaddingRight + ", " + mPaddingBottom + "}";
+ Log.d(VIEW_LOG_TAG, output);
+ }
+
+ output = debugIndent(depth);
+ output += "mMeasureWidth=" + mMeasuredWidth +
+ " mMeasureHeight=" + mMeasuredHeight;
+ Log.d(VIEW_LOG_TAG, output);
+
+ output = debugIndent(depth);
+ if (mLayoutParams == null) {
+ output += "BAD! no layout params";
+ } else {
+ output = mLayoutParams.debug(output);
+ }
+ Log.d(VIEW_LOG_TAG, output);
+
+ output = debugIndent(depth);
+ output += "flags={";
+ output += View.printFlags(mViewFlags);
+ output += "}";
+ Log.d(VIEW_LOG_TAG, output);
+
+ output = debugIndent(depth);
+ output += "privateFlags={";
+ output += View.printPrivateFlags(mPrivateFlags);
+ output += "}";
+ Log.d(VIEW_LOG_TAG, output);
+ }
+
+ /**
+ * Creates a string of whitespaces used for indentation.
+ *
+ * @param depth the indentation level
+ * @return a String containing (depth * 2 + 3) * 2 white spaces
+ *
+ * @hide
+ */
+ protected static String debugIndent(int depth) {
+ StringBuilder spaces = new StringBuilder((depth * 2 + 3) * 2);
+ for (int i = 0; i < (depth * 2) + 3; i++) {
+ spaces.append(' ').append(' ');
+ }
+ return spaces.toString();
+ }
+
+ /**
+ * <p>Return the offset of the widget's text baseline from the widget's top
+ * boundary. If this widget does not support baseline alignment, this
+ * method returns -1. </p>
+ *
+ * @return the offset of the baseline within the widget's bounds or -1
+ * if baseline alignment is not supported
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int getBaseline() {
+ return -1;
+ }
+
+ /**
+ * Returns whether the view hierarchy is currently undergoing a layout pass. This
+ * information is useful to avoid situations such as calling {@link #requestLayout()} during
+ * a layout pass.
+ *
+ * @return whether the view hierarchy is currently undergoing a layout pass
+ */
+ public boolean isInLayout() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ return (viewRoot != null && viewRoot.isInLayout());
+ }
+
+ /**
+ * Call this when something has changed which has invalidated the
+ * layout of this view. This will schedule a layout pass of the view
+ * tree. This should not be called while the view hierarchy is currently in a layout
+ * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
+ * end of the current layout pass (and then layout will run again) or after the current
+ * frame is drawn and the next layout occurs.
+ *
+ * <p>Subclasses which override this method should call the superclass method to
+ * handle possible request-during-layout errors correctly.</p>
+ */
+ @CallSuper
+ public void requestLayout() {
+ if (mMeasureCache != null) mMeasureCache.clear();
+
+ if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
+ // Only trigger request-during-layout logic if this is the view requesting it,
+ // not the views in its parent hierarchy
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null && viewRoot.isInLayout()) {
+ if (!viewRoot.requestLayoutDuringLayout(this)) {
+ return;
+ }
+ }
+ mAttachInfo.mViewRequestingLayout = this;
+ }
+
+ mPrivateFlags |= PFLAG_FORCE_LAYOUT;
+ mPrivateFlags |= PFLAG_INVALIDATED;
+
+ if (mParent != null && !mParent.isLayoutRequested()) {
+ mParent.requestLayout();
+ }
+ if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
+ mAttachInfo.mViewRequestingLayout = null;
+ }
+ }
+
+ /**
+ * Forces this view to be laid out during the next layout pass.
+ * This method does not call requestLayout() or forceLayout()
+ * on the parent.
+ */
+ public void forceLayout() {
+ if (mMeasureCache != null) mMeasureCache.clear();
+
+ mPrivateFlags |= PFLAG_FORCE_LAYOUT;
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ }
+
+ /**
+ * <p>
+ * This is called to find out how big a view should be. The parent
+ * supplies constraint information in the width and height parameters.
+ * </p>
+ *
+ * <p>
+ * The actual measurement work of a view is performed in
+ * {@link #onMeasure(int, int)}, called by this method. Therefore, only
+ * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
+ * </p>
+ *
+ *
+ * @param widthMeasureSpec Horizontal space requirements as imposed by the
+ * parent
+ * @param heightMeasureSpec Vertical space requirements as imposed by the
+ * parent
+ *
+ * @see #onMeasure(int, int)
+ */
+ public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
+ boolean optical = isLayoutModeOptical(this);
+ if (optical != isLayoutModeOptical(mParent)) {
+ Insets insets = getOpticalInsets();
+ int oWidth = insets.left + insets.right;
+ int oHeight = insets.top + insets.bottom;
+ widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
+ heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
+ }
+
+ // Suppress sign extension for the low bytes
+ long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
+ if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
+
+ final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
+
+ // Optimize layout by avoiding an extra EXACTLY pass when the view is
+ // already measured as the correct size. In API 23 and below, this
+ // extra pass is required to make LinearLayout re-distribute weight.
+ final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
+ || heightMeasureSpec != mOldHeightMeasureSpec;
+ final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
+ && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
+ final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
+ && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
+ final boolean needsLayout = specChanged
+ && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
+
+ if (forceLayout || needsLayout) {
+ // first clears the measured dimension flag
+ mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
+
+ resolveRtlPropertiesIfNeeded();
+
+ int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
+ if (cacheIndex < 0 || sIgnoreMeasureCache) {
+ // measure ourselves, this should set the measured dimension flag back
+ onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
+ } else {
+ long value = mMeasureCache.valueAt(cacheIndex);
+ // Casting a long to int drops the high 32 bits, no mask needed
+ setMeasuredDimensionRaw((int) (value >> 32), (int) value);
+ mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
+ }
+
+ // flag not set, setMeasuredDimension() was not invoked, we raise
+ // an exception to warn the developer
+ if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
+ throw new IllegalStateException("View with id " + getId() + ": "
+ + getClass().getName() + "#onMeasure() did not set the"
+ + " measured dimension by calling"
+ + " setMeasuredDimension()");
+ }
+
+ mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
+ }
+
+ mOldWidthMeasureSpec = widthMeasureSpec;
+ mOldHeightMeasureSpec = heightMeasureSpec;
+
+ mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
+ (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
+ }
+
+ /**
+ * <p>
+ * Measure the view and its content to determine the measured width and the
+ * measured height. This method is invoked by {@link #measure(int, int)} and
+ * should be overridden by subclasses to provide accurate and efficient
+ * measurement of their contents.
+ * </p>
+ *
+ * <p>
+ * <strong>CONTRACT:</strong> When overriding this method, you
+ * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
+ * measured width and height of this view. Failure to do so will trigger an
+ * <code>IllegalStateException</code>, thrown by
+ * {@link #measure(int, int)}. Calling the superclass'
+ * {@link #onMeasure(int, int)} is a valid use.
+ * </p>
+ *
+ * <p>
+ * The base class implementation of measure defaults to the background size,
+ * unless a larger size is allowed by the MeasureSpec. Subclasses should
+ * override {@link #onMeasure(int, int)} to provide better measurements of
+ * their content.
+ * </p>
+ *
+ * <p>
+ * If this method is overridden, it is the subclass's responsibility to make
+ * sure the measured height and width are at least the view's minimum height
+ * and width ({@link #getSuggestedMinimumHeight()} and
+ * {@link #getSuggestedMinimumWidth()}).
+ * </p>
+ *
+ * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
+ * The requirements are encoded with
+ * {@link android.view.View.MeasureSpec}.
+ * @param heightMeasureSpec vertical space requirements as imposed by the parent.
+ * The requirements are encoded with
+ * {@link android.view.View.MeasureSpec}.
+ *
+ * @see #getMeasuredWidth()
+ * @see #getMeasuredHeight()
+ * @see #setMeasuredDimension(int, int)
+ * @see #getSuggestedMinimumHeight()
+ * @see #getSuggestedMinimumWidth()
+ * @see android.view.View.MeasureSpec#getMode(int)
+ * @see android.view.View.MeasureSpec#getSize(int)
+ */
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+ getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
+ }
+
+ /**
+ * <p>This method must be called by {@link #onMeasure(int, int)} to store the
+ * measured width and measured height. Failing to do so will trigger an
+ * exception at measurement time.</p>
+ *
+ * @param measuredWidth The measured width of this view. May be a complex
+ * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
+ * @param measuredHeight The measured height of this view. May be a complex
+ * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
+ */
+ protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+ boolean optical = isLayoutModeOptical(this);
+ if (optical != isLayoutModeOptical(mParent)) {
+ Insets insets = getOpticalInsets();
+ int opticalWidth = insets.left + insets.right;
+ int opticalHeight = insets.top + insets.bottom;
+
+ measuredWidth += optical ? opticalWidth : -opticalWidth;
+ measuredHeight += optical ? opticalHeight : -opticalHeight;
+ }
+ setMeasuredDimensionRaw(measuredWidth, measuredHeight);
+ }
+
+ /**
+ * Sets the measured dimension without extra processing for things like optical bounds.
+ * Useful for reapplying consistent values that have already been cooked with adjustments
+ * for optical bounds, etc. such as those from the measurement cache.
+ *
+ * @param measuredWidth The measured width of this view. May be a complex
+ * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
+ * @param measuredHeight The measured height of this view. May be a complex
+ * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
+ */
+ private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
+ mMeasuredWidth = measuredWidth;
+ mMeasuredHeight = measuredHeight;
+
+ mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
+ }
+
+ /**
+ * Merge two states as returned by {@link #getMeasuredState()}.
+ * @param curState The current state as returned from a view or the result
+ * of combining multiple views.
+ * @param newState The new view state to combine.
+ * @return Returns a new integer reflecting the combination of the two
+ * states.
+ */
+ public static int combineMeasuredStates(int curState, int newState) {
+ return curState | newState;
+ }
+
+ /**
+ * Version of {@link #resolveSizeAndState(int, int, int)}
+ * returning only the {@link #MEASURED_SIZE_MASK} bits of the result.
+ */
+ public static int resolveSize(int size, int measureSpec) {
+ return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
+ }
+
+ /**
+ * Utility to reconcile a desired size and state, with constraints imposed
+ * by a MeasureSpec. Will take the desired size, unless a different size
+ * is imposed by the constraints. The returned value is a compound integer,
+ * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
+ * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
+ * resulting size is smaller than the size the view wants to be.
+ *
+ * @param size How big the view wants to be.
+ * @param measureSpec Constraints imposed by the parent.
+ * @param childMeasuredState Size information bit mask for the view's
+ * children.
+ * @return Size information bit mask as defined by
+ * {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
+ */
+ public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
+ final int specMode = MeasureSpec.getMode(measureSpec);
+ final int specSize = MeasureSpec.getSize(measureSpec);
+ final int result;
+ switch (specMode) {
+ case MeasureSpec.AT_MOST:
+ if (specSize < size) {
+ result = specSize | MEASURED_STATE_TOO_SMALL;
+ } else {
+ result = size;
+ }
+ break;
+ case MeasureSpec.EXACTLY:
+ result = specSize;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ result = size;
+ }
+ return result | (childMeasuredState & MEASURED_STATE_MASK);
+ }
+
+ /**
+ * Utility to return a default size. Uses the supplied size if the
+ * MeasureSpec imposed no constraints. Will get larger if allowed
+ * by the MeasureSpec.
+ *
+ * @param size Default size for this view
+ * @param measureSpec Constraints imposed by the parent
+ * @return The size this view should be.
+ */
+ public static int getDefaultSize(int size, int measureSpec) {
+ int result = size;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ switch (specMode) {
+ case MeasureSpec.UNSPECIFIED:
+ result = size;
+ break;
+ case MeasureSpec.AT_MOST:
+ case MeasureSpec.EXACTLY:
+ result = specSize;
+ break;
+ }
+ return result;
+ }
+
+ /**
+ * Returns the suggested minimum height that the view should use. This
+ * returns the maximum of the view's minimum height
+ * and the background's minimum height
+ * ({@link android.graphics.drawable.Drawable#getMinimumHeight()}).
+ * <p>
+ * When being used in {@link #onMeasure(int, int)}, the caller should still
+ * ensure the returned height is within the requirements of the parent.
+ *
+ * @return The suggested minimum height of the view.
+ */
+ protected int getSuggestedMinimumHeight() {
+ return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
+
+ }
+
+ /**
+ * Returns the suggested minimum width that the view should use. This
+ * returns the maximum of the view's minimum width
+ * and the background's minimum width
+ * ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
+ * <p>
+ * When being used in {@link #onMeasure(int, int)}, the caller should still
+ * ensure the returned width is within the requirements of the parent.
+ *
+ * @return The suggested minimum width of the view.
+ */
+ protected int getSuggestedMinimumWidth() {
+ return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
+ }
+
+ /**
+ * Returns the minimum height of the view.
+ *
+ * @return the minimum height the view will try to be, in pixels
+ *
+ * @see #setMinimumHeight(int)
+ *
+ * @attr ref android.R.styleable#View_minHeight
+ */
+ public int getMinimumHeight() {
+ return mMinHeight;
+ }
+
+ /**
+ * Sets the minimum height of the view. It is not guaranteed the view will
+ * be able to achieve this minimum height (for example, if its parent layout
+ * constrains it with less available height).
+ *
+ * @param minHeight The minimum height the view will try to be, in pixels
+ *
+ * @see #getMinimumHeight()
+ *
+ * @attr ref android.R.styleable#View_minHeight
+ */
+ @RemotableViewMethod
+ public void setMinimumHeight(int minHeight) {
+ mMinHeight = minHeight;
+ requestLayout();
+ }
+
+ /**
+ * Returns the minimum width of the view.
+ *
+ * @return the minimum width the view will try to be, in pixels
+ *
+ * @see #setMinimumWidth(int)
+ *
+ * @attr ref android.R.styleable#View_minWidth
+ */
+ public int getMinimumWidth() {
+ return mMinWidth;
+ }
+
+ /**
+ * Sets the minimum width of the view. It is not guaranteed the view will
+ * be able to achieve this minimum width (for example, if its parent layout
+ * constrains it with less available width).
+ *
+ * @param minWidth The minimum width the view will try to be, in pixels
+ *
+ * @see #getMinimumWidth()
+ *
+ * @attr ref android.R.styleable#View_minWidth
+ */
+ public void setMinimumWidth(int minWidth) {
+ mMinWidth = minWidth;
+ requestLayout();
+
+ }
+
+ /**
+ * Get the animation currently associated with this view.
+ *
+ * @return The animation that is currently playing or
+ * scheduled to play for this view.
+ */
+ public Animation getAnimation() {
+ return mCurrentAnimation;
+ }
+
+ /**
+ * Start the specified animation now.
+ *
+ * @param animation the animation to start now
+ */
+ public void startAnimation(Animation animation) {
+ animation.setStartTime(Animation.START_ON_FIRST_FRAME);
+ setAnimation(animation);
+ invalidateParentCaches();
+ invalidate(true);
+ }
+
+ /**
+ * Cancels any animations for this view.
+ */
+ public void clearAnimation() {
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.detach();
+ }
+ mCurrentAnimation = null;
+ invalidateParentIfNeeded();
+ }
+
+ /**
+ * Sets the next animation to play for this view.
+ * If you want the animation to play immediately, use
+ * {@link #startAnimation(android.view.animation.Animation)} instead.
+ * This method provides allows fine-grained
+ * control over the start time and invalidation, but you
+ * must make sure that 1) the animation has a start time set, and
+ * 2) the view's parent (which controls animations on its children)
+ * will be invalidated when the animation is supposed to
+ * start.
+ *
+ * @param animation The next animation, or null.
+ */
+ public void setAnimation(Animation animation) {
+ mCurrentAnimation = animation;
+
+ if (animation != null) {
+ // If the screen is off assume the animation start time is now instead of
+ // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
+ // would cause the animation to start when the screen turns back on
+ if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
+ && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
+ animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
+ }
+ animation.reset();
+ }
+ }
+
+ /**
+ * Invoked by a parent ViewGroup to notify the start of the animation
+ * currently associated with this view. If you override this method,
+ * always call super.onAnimationStart();
+ *
+ * @see #setAnimation(android.view.animation.Animation)
+ * @see #getAnimation()
+ */
+ @CallSuper
+ protected void onAnimationStart() {
+ mPrivateFlags |= PFLAG_ANIMATION_STARTED;
+ }
+
+ /**
+ * Invoked by a parent ViewGroup to notify the end of the animation
+ * currently associated with this view. If you override this method,
+ * always call super.onAnimationEnd();
+ *
+ * @see #setAnimation(android.view.animation.Animation)
+ * @see #getAnimation()
+ */
+ @CallSuper
+ protected void onAnimationEnd() {
+ mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
+ }
+
+ /**
+ * Invoked if there is a Transform that involves alpha. Subclass that can
+ * draw themselves with the specified alpha should return true, and then
+ * respect that alpha when their onDraw() is called. If this returns false
+ * then the view may be redirected to draw into an offscreen buffer to
+ * fulfill the request, which will look fine, but may be slower than if the
+ * subclass handles it internally. The default implementation returns false.
+ *
+ * @param alpha The alpha (0..255) to apply to the view's drawing
+ * @return true if the view can draw with the specified alpha.
+ */
+ protected boolean onSetAlpha(int alpha) {
+ return false;
+ }
+
+ /**
+ * This is used by the RootView to perform an optimization when
+ * the view hierarchy contains one or several SurfaceView.
+ * SurfaceView is always considered transparent, but its children are not,
+ * therefore all View objects remove themselves from the global transparent
+ * region (passed as a parameter to this function).
+ *
+ * @param region The transparent region for this ViewAncestor (window).
+ *
+ * @return Returns true if the effective visibility of the view at this
+ * point is opaque, regardless of the transparent region; returns false
+ * if it is possible for underlying windows to be seen behind the view.
+ *
+ * {@hide}
+ */
+ public boolean gatherTransparentRegion(Region region) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (region != null && attachInfo != null) {
+ final int pflags = mPrivateFlags;
+ if ((pflags & PFLAG_SKIP_DRAW) == 0) {
+ // The SKIP_DRAW flag IS NOT set, so this view draws. We need to
+ // remove it from the transparent region.
+ final int[] location = attachInfo.mTransparentLocation;
+ getLocationInWindow(location);
+ // When a view has Z value, then it will be better to leave some area below the view
+ // for drawing shadow. The shadow outset is proportional to the Z value. Note that
+ // the bottom part needs more offset than the left, top and right parts due to the
+ // spot light effects.
+ int shadowOffset = getZ() > 0 ? (int) getZ() : 0;
+ region.op(location[0] - shadowOffset, location[1] - shadowOffset,
+ location[0] + mRight - mLeft + shadowOffset,
+ location[1] + mBottom - mTop + (shadowOffset * 3), Region.Op.DIFFERENCE);
+ } else {
+ if (mBackground != null && mBackground.getOpacity() != PixelFormat.TRANSPARENT) {
+ // The SKIP_DRAW flag IS set and the background drawable exists, we remove
+ // the background drawable's non-transparent parts from this transparent region.
+ applyDrawableToTransparentRegion(mBackground, region);
+ }
+ if (mForegroundInfo != null && mForegroundInfo.mDrawable != null
+ && mForegroundInfo.mDrawable.getOpacity() != PixelFormat.TRANSPARENT) {
+ // Similarly, we remove the foreground drawable's non-transparent parts.
+ applyDrawableToTransparentRegion(mForegroundInfo.mDrawable, region);
+ }
+ if (mDefaultFocusHighlight != null
+ && mDefaultFocusHighlight.getOpacity() != PixelFormat.TRANSPARENT) {
+ // Similarly, we remove the default focus highlight's non-transparent parts.
+ applyDrawableToTransparentRegion(mDefaultFocusHighlight, region);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Play a sound effect for this view.
+ *
+ * <p>The framework will play sound effects for some built in actions, such as
+ * clicking, but you may wish to play these effects in your widget,
+ * for instance, for internal navigation.
+ *
+ * <p>The sound effect will only be played if sound effects are enabled by the user, and
+ * {@link #isSoundEffectsEnabled()} is true.
+ *
+ * @param soundConstant One of the constants defined in {@link SoundEffectConstants}
+ */
+ public void playSoundEffect(int soundConstant) {
+ if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
+ return;
+ }
+ mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
+ }
+
+ /**
+ * BZZZTT!!1!
+ *
+ * <p>Provide haptic feedback to the user for this view.
+ *
+ * <p>The framework will provide haptic feedback for some built in actions,
+ * such as long presses, but you may wish to provide feedback for your
+ * own widget.
+ *
+ * <p>The feedback will only be performed if
+ * {@link #isHapticFeedbackEnabled()} is true.
+ *
+ * @param feedbackConstant One of the constants defined in
+ * {@link HapticFeedbackConstants}
+ */
+ public boolean performHapticFeedback(int feedbackConstant) {
+ return performHapticFeedback(feedbackConstant, 0);
+ }
+
+ /**
+ * BZZZTT!!1!
+ *
+ * <p>Like {@link #performHapticFeedback(int)}, with additional options.
+ *
+ * @param feedbackConstant One of the constants defined in
+ * {@link HapticFeedbackConstants}
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ */
+ public boolean performHapticFeedback(int feedbackConstant, int flags) {
+ if (mAttachInfo == null) {
+ return false;
+ }
+ //noinspection SimplifiableIfStatement
+ if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
+ && !isHapticFeedbackEnabled()) {
+ return false;
+ }
+ return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
+ (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+ }
+
+ /**
+ * Request that the visibility of the status bar or other screen/window
+ * decorations be changed.
+ *
+ * <p>This method is used to put the over device UI into temporary modes
+ * where the user's attention is focused more on the application content,
+ * by dimming or hiding surrounding system affordances. This is typically
+ * used in conjunction with {@link Window#FEATURE_ACTION_BAR_OVERLAY
+ * Window.FEATURE_ACTION_BAR_OVERLAY}, allowing the applications content
+ * to be placed behind the action bar (and with these flags other system
+ * affordances) so that smooth transitions between hiding and showing them
+ * can be done.
+ *
+ * <p>Two representative examples of the use of system UI visibility is
+ * implementing a content browsing application (like a magazine reader)
+ * and a video playing application.
+ *
+ * <p>The first code shows a typical implementation of a View in a content
+ * browsing application. In this implementation, the application goes
+ * into a content-oriented mode by hiding the status bar and action bar,
+ * and putting the navigation elements into lights out mode. The user can
+ * then interact with content while in this mode. Such an application should
+ * provide an easy way for the user to toggle out of the mode (such as to
+ * check information in the status bar or access notifications). In the
+ * implementation here, this is done simply by tapping on the content.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/ContentBrowserActivity.java
+ * content}
+ *
+ * <p>This second code sample shows a typical implementation of a View
+ * in a video playing application. In this situation, while the video is
+ * playing the application would like to go into a complete full-screen mode,
+ * to use as much of the display as possible for the video. When in this state
+ * the user can not interact with the application; the system intercepts
+ * touching on the screen to pop the UI out of full screen mode. See
+ * {@link #fitSystemWindows(Rect)} for a sample layout that goes with this code.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java
+ * content}
+ *
+ * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
+ * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}.
+ */
+ public void setSystemUiVisibility(int visibility) {
+ if (visibility != mSystemUiVisibility) {
+ mSystemUiVisibility = visibility;
+ if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+ mParent.recomputeViewAttributes(this);
+ }
+ }
+ }
+
+ /**
+ * Returns the last {@link #setSystemUiVisibility(int)} that this view has requested.
+ * @return Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
+ * and {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY}.
+ */
+ public int getSystemUiVisibility() {
+ return mSystemUiVisibility;
+ }
+
+ /**
+ * Returns the current system UI visibility that is currently set for
+ * the entire window. This is the combination of the
+ * {@link #setSystemUiVisibility(int)} values supplied by all of the
+ * views in the window.
+ */
+ public int getWindowSystemUiVisibility() {
+ return mAttachInfo != null ? mAttachInfo.mSystemUiVisibility : 0;
+ }
+
+ /**
+ * Override to find out when the window's requested system UI visibility
+ * has changed, that is the value returned by {@link #getWindowSystemUiVisibility()}.
+ * This is different from the callbacks received through
+ * {@link #setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener)}
+ * in that this is only telling you about the local request of the window,
+ * not the actual values applied by the system.
+ */
+ public void onWindowSystemUiVisibilityChanged(int visible) {
+ }
+
+ /**
+ * Dispatch callbacks to {@link #onWindowSystemUiVisibilityChanged(int)} down
+ * the view hierarchy.
+ */
+ public void dispatchWindowSystemUiVisiblityChanged(int visible) {
+ onWindowSystemUiVisibilityChanged(visible);
+ }
+
+ /**
+ * Set a listener to receive callbacks when the visibility of the system bar changes.
+ * @param l The {@link OnSystemUiVisibilityChangeListener} to receive callbacks.
+ */
+ public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener l) {
+ getListenerInfo().mOnSystemUiVisibilityChangeListener = l;
+ if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+ mParent.recomputeViewAttributes(this);
+ }
+ }
+
+ /**
+ * Dispatch callbacks to {@link #setOnSystemUiVisibilityChangeListener} down
+ * the view hierarchy.
+ */
+ public void dispatchSystemUiVisibilityChanged(int visibility) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
+ li.mOnSystemUiVisibilityChangeListener.onSystemUiVisibilityChange(
+ visibility & PUBLIC_STATUS_BAR_VISIBILITY_MASK);
+ }
+ }
+
+ boolean updateLocalSystemUiVisibility(int localValue, int localChanges) {
+ int val = (mSystemUiVisibility&~localChanges) | (localValue&localChanges);
+ if (val != mSystemUiVisibility) {
+ setSystemUiVisibility(val);
+ return true;
+ }
+ return false;
+ }
+
+ /** @hide */
+ public void setDisabledSystemUiVisibility(int flags) {
+ if (mAttachInfo != null) {
+ if (mAttachInfo.mDisabledSystemUiVisibility != flags) {
+ mAttachInfo.mDisabledSystemUiVisibility = flags;
+ if (mParent != null) {
+ mParent.recomputeViewAttributes(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates an image that the system displays during the drag and drop
+ * operation. This is called a &quot;drag shadow&quot;. The default implementation
+ * for a DragShadowBuilder based on a View returns an image that has exactly the same
+ * appearance as the given View. The default also positions the center of the drag shadow
+ * directly under the touch point. If no View is provided (the constructor with no parameters
+ * is used), and {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} and
+ * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overridden, then the
+ * default is an invisible drag shadow.
+ * <p>
+ * You are not required to use the View you provide to the constructor as the basis of the
+ * drag shadow. The {@link #onDrawShadow(Canvas) onDrawShadow()} method allows you to draw
+ * anything you want as the drag shadow.
+ * </p>
+ * <p>
+ * You pass a DragShadowBuilder object to the system when you start the drag. The system
+ * calls {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} to get the
+ * size and position of the drag shadow. It uses this data to construct a
+ * {@link android.graphics.Canvas} object, then it calls {@link #onDrawShadow(Canvas) onDrawShadow()}
+ * so that your application can draw the shadow image in the Canvas.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For a guide to implementing drag and drop features, read the
+ * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+ * </div>
+ */
+ public static class DragShadowBuilder {
+ private final WeakReference<View> mView;
+
+ /**
+ * Constructs a shadow image builder based on a View. By default, the resulting drag
+ * shadow will have the same appearance and dimensions as the View, with the touch point
+ * over the center of the View.
+ * @param view A View. Any View in scope can be used.
+ */
+ public DragShadowBuilder(View view) {
+ mView = new WeakReference<View>(view);
+ }
+
+ /**
+ * Construct a shadow builder object with no associated View. This
+ * constructor variant is only useful when the {@link #onProvideShadowMetrics(Point, Point)}
+ * and {@link #onDrawShadow(Canvas)} methods are also overridden in order
+ * to supply the drag shadow's dimensions and appearance without
+ * reference to any View object. If they are not overridden, then the result is an
+ * invisible drag shadow.
+ */
+ public DragShadowBuilder() {
+ mView = new WeakReference<View>(null);
+ }
+
+ /**
+ * Returns the View object that had been passed to the
+ * {@link #View.DragShadowBuilder(View)}
+ * constructor. If that View parameter was {@code null} or if the
+ * {@link #View.DragShadowBuilder()}
+ * constructor was used to instantiate the builder object, this method will return
+ * null.
+ *
+ * @return The View object associate with this builder object.
+ */
+ @SuppressWarnings({"JavadocReference"})
+ final public View getView() {
+ return mView.get();
+ }
+
+ /**
+ * Provides the metrics for the shadow image. These include the dimensions of
+ * the shadow image, and the point within that shadow that should
+ * be centered under the touch location while dragging.
+ * <p>
+ * The default implementation sets the dimensions of the shadow to be the
+ * same as the dimensions of the View itself and centers the shadow under
+ * the touch point.
+ * </p>
+ *
+ * @param outShadowSize A {@link android.graphics.Point} containing the width and height
+ * of the shadow image. Your application must set {@link android.graphics.Point#x} to the
+ * desired width and must set {@link android.graphics.Point#y} to the desired height of the
+ * image.
+ *
+ * @param outShadowTouchPoint A {@link android.graphics.Point} for the position within the
+ * shadow image that should be underneath the touch point during the drag and drop
+ * operation. Your application must set {@link android.graphics.Point#x} to the
+ * X coordinate and {@link android.graphics.Point#y} to the Y coordinate of this position.
+ */
+ public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+ final View view = mView.get();
+ if (view != null) {
+ outShadowSize.set(view.getWidth(), view.getHeight());
+ outShadowTouchPoint.set(outShadowSize.x / 2, outShadowSize.y / 2);
+ } else {
+ Log.e(View.VIEW_LOG_TAG, "Asked for drag thumb metrics but no view");
+ }
+ }
+
+ /**
+ * Draws the shadow image. The system creates the {@link android.graphics.Canvas} object
+ * based on the dimensions it received from the
+ * {@link #onProvideShadowMetrics(Point, Point)} callback.
+ *
+ * @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
+ */
+ public void onDrawShadow(Canvas canvas) {
+ final View view = mView.get();
+ if (view != null) {
+ view.draw(canvas);
+ } else {
+ Log.e(View.VIEW_LOG_TAG, "Asked to draw drag shadow but no view");
+ }
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)
+ * startDragAndDrop()} for newer platform versions.
+ */
+ @Deprecated
+ public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+ Object myLocalState, int flags) {
+ return startDragAndDrop(data, shadowBuilder, myLocalState, flags);
+ }
+
+ /**
+ * Starts a drag and drop operation. When your application calls this method, it passes a
+ * {@link android.view.View.DragShadowBuilder} object to the system. The
+ * system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)}
+ * to get metrics for the drag shadow, and then calls the object's
+ * {@link DragShadowBuilder#onDrawShadow(Canvas)} to draw the drag shadow itself.
+ * <p>
+ * Once the system has the drag shadow, it begins the drag and drop operation by sending
+ * drag events to all the View objects in your application that are currently visible. It does
+ * this either by calling the View object's drag listener (an implementation of
+ * {@link android.view.View.OnDragListener#onDrag(View,DragEvent) onDrag()} or by calling the
+ * View object's {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} method.
+ * Both are passed a {@link android.view.DragEvent} object that has a
+ * {@link android.view.DragEvent#getAction()} value of
+ * {@link android.view.DragEvent#ACTION_DRAG_STARTED}.
+ * </p>
+ * <p>
+ * Your application can invoke {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object,
+ * int) startDragAndDrop()} on any attached View object. The View object does not need to be
+ * the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to be related
+ * to the View the user selected for dragging.
+ * </p>
+ * @param data A {@link android.content.ClipData} object pointing to the data to be
+ * transferred by the drag and drop operation.
+ * @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the
+ * drag shadow.
+ * @param myLocalState An {@link java.lang.Object} containing local data about the drag and
+ * drop operation. When dispatching drag events to views in the same activity this object
+ * will be available through {@link android.view.DragEvent#getLocalState()}. Views in other
+ * activities will not have access to this data ({@link android.view.DragEvent#getLocalState()}
+ * will return null).
+ * <p>
+ * myLocalState is a lightweight mechanism for the sending information from the dragged View
+ * to the target Views. For example, it can contain flags that differentiate between a
+ * a copy operation and a move operation.
+ * </p>
+ * @param flags Flags that control the drag and drop operation. This can be set to 0 for no
+ * flags, or any combination of the following:
+ * <ul>
+ * <li>{@link #DRAG_FLAG_GLOBAL}</li>
+ * <li>{@link #DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION}</li>
+ * <li>{@link #DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION}</li>
+ * <li>{@link #DRAG_FLAG_GLOBAL_URI_READ}</li>
+ * <li>{@link #DRAG_FLAG_GLOBAL_URI_WRITE}</li>
+ * <li>{@link #DRAG_FLAG_OPAQUE}</li>
+ * </ul>
+ * @return {@code true} if the method completes successfully, or
+ * {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
+ * do a drag, and so no drag operation is in progress.
+ */
+ public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,
+ Object myLocalState, int flags) {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags);
+ }
+ if (mAttachInfo == null) {
+ Log.w(VIEW_LOG_TAG, "startDragAndDrop called on a detached view.");
+ return false;
+ }
+
+ if (data != null) {
+ data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+ }
+
+ boolean okay = false;
+
+ Point shadowSize = new Point();
+ Point shadowTouchPoint = new Point();
+ shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
+
+ if ((shadowSize.x < 0) || (shadowSize.y < 0) ||
+ (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
+ throw new IllegalStateException("Drag shadow dimensions must not be negative");
+ }
+
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y
+ + " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
+ }
+ if (mAttachInfo.mDragSurface != null) {
+ mAttachInfo.mDragSurface.release();
+ }
+ mAttachInfo.mDragSurface = new Surface();
+ try {
+ mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+ flags, shadowSize.x, shadowSize.y, mAttachInfo.mDragSurface);
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
+ + mAttachInfo.mDragToken + " surface=" + mAttachInfo.mDragSurface);
+ if (mAttachInfo.mDragToken != null) {
+ Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+ try {
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ shadowBuilder.onDrawShadow(canvas);
+ } finally {
+ mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
+ }
+
+ final ViewRootImpl root = getViewRootImpl();
+
+ // Cache the local state object for delivery with DragEvents
+ root.setLocalDragState(myLocalState);
+
+ // repurpose 'shadowSize' for the last touch point
+ root.getLastTouchPoint(shadowSize);
+
+ okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
+ root.getLastTouchSource(), shadowSize.x, shadowSize.y,
+ shadowTouchPoint.x, shadowTouchPoint.y, data);
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+ }
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
+ mAttachInfo.mDragSurface.destroy();
+ mAttachInfo.mDragSurface = null;
+ }
+
+ return okay;
+ }
+
+ /**
+ * Cancels an ongoing drag and drop operation.
+ * <p>
+ * A {@link android.view.DragEvent} object with
+ * {@link android.view.DragEvent#getAction()} value of
+ * {@link android.view.DragEvent#ACTION_DRAG_ENDED} and
+ * {@link android.view.DragEvent#getResult()} value of {@code false}
+ * will be sent to every
+ * View that received {@link android.view.DragEvent#ACTION_DRAG_STARTED}
+ * even if they are not currently visible.
+ * </p>
+ * <p>
+ * This method can be called on any View in the same window as the View on which
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int) startDragAndDrop}
+ * was called.
+ * </p>
+ */
+ public final void cancelDragAndDrop() {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "cancelDragAndDrop");
+ }
+ if (mAttachInfo == null) {
+ Log.w(VIEW_LOG_TAG, "cancelDragAndDrop called on a detached view.");
+ return;
+ }
+ if (mAttachInfo.mDragToken != null) {
+ try {
+ mAttachInfo.mSession.cancelDragAndDrop(mAttachInfo.mDragToken);
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
+ }
+ mAttachInfo.mDragToken = null;
+ } else {
+ Log.e(VIEW_LOG_TAG, "No active drag to cancel");
+ }
+ }
+
+ /**
+ * Updates the drag shadow for the ongoing drag and drop operation.
+ *
+ * @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the
+ * new drag shadow.
+ */
+ public final void updateDragShadow(DragShadowBuilder shadowBuilder) {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "updateDragShadow");
+ }
+ if (mAttachInfo == null) {
+ Log.w(VIEW_LOG_TAG, "updateDragShadow called on a detached view.");
+ return;
+ }
+ if (mAttachInfo.mDragToken != null) {
+ try {
+ Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+ try {
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ shadowBuilder.onDrawShadow(canvas);
+ } finally {
+ mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
+ }
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to update drag shadow", e);
+ }
+ } else {
+ Log.e(VIEW_LOG_TAG, "No active drag");
+ }
+ }
+
+ /**
+ * Starts a move from {startX, startY}, the amount of the movement will be the offset
+ * between {startX, startY} and the new cursor positon.
+ * @param startX horizontal coordinate where the move started.
+ * @param startY vertical coordinate where the move started.
+ * @return whether moving was started successfully.
+ * @hide
+ */
+ public final boolean startMovingTask(float startX, float startY) {
+ if (ViewDebug.DEBUG_POSITIONING) {
+ Log.d(VIEW_LOG_TAG, "startMovingTask: {" + startX + "," + startY + "}");
+ }
+ try {
+ return mAttachInfo.mSession.startMovingTask(mAttachInfo.mWindow, startX, startY);
+ } catch (RemoteException e) {
+ Log.e(VIEW_LOG_TAG, "Unable to start moving", e);
+ }
+ return false;
+ }
+
+ /**
+ * Handles drag events sent by the system following a call to
+ * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
+ * startDragAndDrop()}.
+ *<p>
+ * When the system calls this method, it passes a
+ * {@link android.view.DragEvent} object. A call to
+ * {@link android.view.DragEvent#getAction()} returns one of the action type constants defined
+ * in DragEvent. The method uses these to determine what is happening in the drag and drop
+ * operation.
+ * @param event The {@link android.view.DragEvent} sent by the system.
+ * The {@link android.view.DragEvent#getAction()} method returns an action type constant defined
+ * in DragEvent, indicating the type of drag event represented by this object.
+ * @return {@code true} if the method was successful, otherwise {@code false}.
+ * <p>
+ * The method should return {@code true} in response to an action type of
+ * {@link android.view.DragEvent#ACTION_DRAG_STARTED} to receive drag events for the current
+ * operation.
+ * </p>
+ * <p>
+ * The method should also return {@code true} in response to an action type of
+ * {@link android.view.DragEvent#ACTION_DROP} if it consumed the drop, or
+ * {@code false} if it didn't.
+ * </p>
+ * <p>
+ * For all other events, the return value is ignored.
+ * </p>
+ */
+ public boolean onDragEvent(DragEvent event) {
+ return false;
+ }
+
+ // Dispatches ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED events for pre-Nougat apps.
+ boolean dispatchDragEnterExitInPreN(DragEvent event) {
+ return callDragEventHandler(event);
+ }
+
+ /**
+ * Detects if this View is enabled and has a drag event listener.
+ * If both are true, then it calls the drag event listener with the
+ * {@link android.view.DragEvent} it received. If the drag event listener returns
+ * {@code true}, then dispatchDragEvent() returns {@code true}.
+ * <p>
+ * For all other cases, the method calls the
+ * {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} drag event handler
+ * method and returns its result.
+ * </p>
+ * <p>
+ * This ensures that a drag event is always consumed, even if the View does not have a drag
+ * event listener. However, if the View has a listener and the listener returns true, then
+ * onDragEvent() is not called.
+ * </p>
+ */
+ public boolean dispatchDragEvent(DragEvent event) {
+ event.mEventHandlerWasCalled = true;
+ if (event.mAction == DragEvent.ACTION_DRAG_LOCATION ||
+ event.mAction == DragEvent.ACTION_DROP) {
+ // About to deliver an event with coordinates to this view. Notify that now this view
+ // has drag focus. This will send exit/enter events as needed.
+ getViewRootImpl().setDragFocus(this, event);
+ }
+ return callDragEventHandler(event);
+ }
+
+ final boolean callDragEventHandler(DragEvent event) {
+ final boolean result;
+
+ ListenerInfo li = mListenerInfo;
+ //noinspection SimplifiableIfStatement
+ if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnDragListener.onDrag(this, event)) {
+ result = true;
+ } else {
+ result = onDragEvent(event);
+ }
+
+ switch (event.mAction) {
+ case DragEvent.ACTION_DRAG_ENTERED: {
+ mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
+ refreshDrawableState();
+ } break;
+ case DragEvent.ACTION_DRAG_EXITED: {
+ mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
+ refreshDrawableState();
+ } break;
+ case DragEvent.ACTION_DRAG_ENDED: {
+ mPrivateFlags2 &= ~View.DRAG_MASK;
+ refreshDrawableState();
+ } break;
+ }
+
+ return result;
+ }
+
+ boolean canAcceptDrag() {
+ return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0;
+ }
+
+ /**
+ * This needs to be a better API (NOT ON VIEW) before it is exposed. If
+ * it is ever exposed at all.
+ * @hide
+ */
+ public void onCloseSystemDialogs(String reason) {
+ }
+
+ /**
+ * Given a Drawable whose bounds have been set to draw into this view,
+ * update a Region being computed for
+ * {@link #gatherTransparentRegion(android.graphics.Region)} so
+ * that any non-transparent parts of the Drawable are removed from the
+ * given transparent region.
+ *
+ * @param dr The Drawable whose transparency is to be applied to the region.
+ * @param region A Region holding the current transparency information,
+ * where any parts of the region that are set are considered to be
+ * transparent. On return, this region will be modified to have the
+ * transparency information reduced by the corresponding parts of the
+ * Drawable that are not transparent.
+ * {@hide}
+ */
+ public void applyDrawableToTransparentRegion(Drawable dr, Region region) {
+ if (DBG) {
+ Log.i("View", "Getting transparent region for: " + this);
+ }
+ final Region r = dr.getTransparentRegion();
+ final Rect db = dr.getBounds();
+ final AttachInfo attachInfo = mAttachInfo;
+ if (r != null && attachInfo != null) {
+ final int w = getRight()-getLeft();
+ final int h = getBottom()-getTop();
+ if (db.left > 0) {
+ //Log.i("VIEW", "Drawable left " + db.left + " > view 0");
+ r.op(0, 0, db.left, h, Region.Op.UNION);
+ }
+ if (db.right < w) {
+ //Log.i("VIEW", "Drawable right " + db.right + " < view " + w);
+ r.op(db.right, 0, w, h, Region.Op.UNION);
+ }
+ if (db.top > 0) {
+ //Log.i("VIEW", "Drawable top " + db.top + " > view 0");
+ r.op(0, 0, w, db.top, Region.Op.UNION);
+ }
+ if (db.bottom < h) {
+ //Log.i("VIEW", "Drawable bottom " + db.bottom + " < view " + h);
+ r.op(0, db.bottom, w, h, Region.Op.UNION);
+ }
+ final int[] location = attachInfo.mTransparentLocation;
+ getLocationInWindow(location);
+ r.translate(location[0], location[1]);
+ region.op(r, Region.Op.INTERSECT);
+ } else {
+ region.op(db, Region.Op.DIFFERENCE);
+ }
+ }
+
+ private void checkForLongClick(int delayOffset, float x, float y) {
+ if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
+ mHasPerformedLongPress = false;
+
+ if (mPendingCheckForLongPress == null) {
+ mPendingCheckForLongPress = new CheckForLongPress();
+ }
+ mPendingCheckForLongPress.setAnchor(x, y);
+ mPendingCheckForLongPress.rememberWindowAttachCount();
+ mPendingCheckForLongPress.rememberPressedState();
+ postDelayed(mPendingCheckForLongPress,
+ ViewConfiguration.getLongPressTimeout() - delayOffset);
+ }
+ }
+
+ /**
+ * Inflate a view from an XML resource. This convenience method wraps the {@link
+ * LayoutInflater} class, which provides a full range of options for view inflation.
+ *
+ * @param context The Context object for your activity or application.
+ * @param resource The resource ID to inflate
+ * @param root A view group that will be the parent. Used to properly inflate the
+ * layout_* parameters.
+ * @see LayoutInflater
+ */
+ public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
+ LayoutInflater factory = LayoutInflater.from(context);
+ return factory.inflate(resource, root);
+ }
+
+ /**
+ * Scroll the view with standard behavior for scrolling beyond the normal
+ * content boundaries. Views that call this method should override
+ * {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
+ * results of an over-scroll operation.
+ *
+ * Views can use this method to handle any touch or fling-based scrolling.
+ *
+ * @param deltaX Change in X in pixels
+ * @param deltaY Change in Y in pixels
+ * @param scrollX Current X scroll value in pixels before applying deltaX
+ * @param scrollY Current Y scroll value in pixels before applying deltaY
+ * @param scrollRangeX Maximum content scroll range along the X axis
+ * @param scrollRangeY Maximum content scroll range along the Y axis
+ * @param maxOverScrollX Number of pixels to overscroll by in either direction
+ * along the X axis.
+ * @param maxOverScrollY Number of pixels to overscroll by in either direction
+ * along the Y axis.
+ * @param isTouchEvent true if this scroll operation is the result of a touch event.
+ * @return true if scrolling was clamped to an over-scroll boundary along either
+ * axis, false otherwise.
+ */
+ @SuppressWarnings({"UnusedParameters"})
+ protected boolean overScrollBy(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverScrollX, int maxOverScrollY,
+ boolean isTouchEvent) {
+ final int overScrollMode = mOverScrollMode;
+ final boolean canScrollHorizontal =
+ computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+ final boolean canScrollVertical =
+ computeVerticalScrollRange() > computeVerticalScrollExtent();
+ final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS ||
+ (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+ final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
+ (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+ int newScrollX = scrollX + deltaX;
+ if (!overScrollHorizontal) {
+ maxOverScrollX = 0;
+ }
+
+ int newScrollY = scrollY + deltaY;
+ if (!overScrollVertical) {
+ maxOverScrollY = 0;
+ }
+
+ // Clamp values if at the limits and record
+ final int left = -maxOverScrollX;
+ final int right = maxOverScrollX + scrollRangeX;
+ final int top = -maxOverScrollY;
+ final int bottom = maxOverScrollY + scrollRangeY;
+
+ boolean clampedX = false;
+ if (newScrollX > right) {
+ newScrollX = right;
+ clampedX = true;
+ } else if (newScrollX < left) {
+ newScrollX = left;
+ clampedX = true;
+ }
+
+ boolean clampedY = false;
+ if (newScrollY > bottom) {
+ newScrollY = bottom;
+ clampedY = true;
+ } else if (newScrollY < top) {
+ newScrollY = top;
+ clampedY = true;
+ }
+
+ onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+ return clampedX || clampedY;
+ }
+
+ /**
+ * Called by {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)} to
+ * respond to the results of an over-scroll operation.
+ *
+ * @param scrollX New X scroll value in pixels
+ * @param scrollY New Y scroll value in pixels
+ * @param clampedX True if scrollX was clamped to an over-scroll boundary
+ * @param clampedY True if scrollY was clamped to an over-scroll boundary
+ */
+ protected void onOverScrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ // Intentionally empty.
+ }
+
+ /**
+ * Returns the over-scroll mode for this view. The result will be
+ * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+ * (allow over-scrolling only if the view content is larger than the container),
+ * or {@link #OVER_SCROLL_NEVER}.
+ *
+ * @return This view's over-scroll mode.
+ */
+ public int getOverScrollMode() {
+ return mOverScrollMode;
+ }
+
+ /**
+ * Set the over-scroll mode for this view. Valid over-scroll modes are
+ * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+ * (allow over-scrolling only if the view content is larger than the container),
+ * or {@link #OVER_SCROLL_NEVER}.
+ *
+ * Setting the over-scroll mode of a view will have an effect only if the
+ * view is capable of scrolling.
+ *
+ * @param overScrollMode The new over-scroll mode for this view.
+ */
+ public void setOverScrollMode(int overScrollMode) {
+ if (overScrollMode != OVER_SCROLL_ALWAYS &&
+ overScrollMode != OVER_SCROLL_IF_CONTENT_SCROLLS &&
+ overScrollMode != OVER_SCROLL_NEVER) {
+ throw new IllegalArgumentException("Invalid overscroll mode " + overScrollMode);
+ }
+ mOverScrollMode = overScrollMode;
+ }
+
+ /**
+ * Enable or disable nested scrolling for this view.
+ *
+ * <p>If this property is set to true the view will be permitted to initiate nested
+ * scrolling operations with a compatible parent view in the current hierarchy. If this
+ * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+ * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+ * the nested scroll.</p>
+ *
+ * @param enabled true to enable nested scrolling, false to disable
+ *
+ * @see #isNestedScrollingEnabled()
+ */
+ public void setNestedScrollingEnabled(boolean enabled) {
+ if (enabled) {
+ mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED;
+ } else {
+ stopNestedScroll();
+ mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED;
+ }
+ }
+
+ /**
+ * Returns true if nested scrolling is enabled for this view.
+ *
+ * <p>If nested scrolling is enabled and this View class implementation supports it,
+ * this view will act as a nested scrolling child view when applicable, forwarding data
+ * about the scroll operation in progress to a compatible and cooperating nested scrolling
+ * parent.</p>
+ *
+ * @return true if nested scrolling is enabled
+ *
+ * @see #setNestedScrollingEnabled(boolean)
+ */
+ public boolean isNestedScrollingEnabled() {
+ return (mPrivateFlags3 & PFLAG3_NESTED_SCROLLING_ENABLED) ==
+ PFLAG3_NESTED_SCROLLING_ENABLED;
+ }
+
+ /**
+ * Begin a nestable scroll operation along the given axes.
+ *
+ * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+ *
+ * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+ * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+ * In the case of touch scrolling the nested scroll will be terminated automatically in
+ * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+ * In the event of programmatic scrolling the caller must explicitly call
+ * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p>
+ *
+ * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+ * If it returns false the caller may ignore the rest of this contract until the next scroll.
+ * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
+ *
+ * <p>At each incremental step of the scroll the caller should invoke
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
+ * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+ * parent at least partially consumed the scroll and the caller should adjust the amount it
+ * scrolls by.</p>
+ *
+ * <p>After applying the remainder of the scroll delta the caller should invoke
+ * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing
+ * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+ * these values differently. See {@link ViewParent#onNestedScroll(View, int, int, int, int)}.
+ * </p>
+ *
+ * @param axes Flags consisting of a combination of {@link #SCROLL_AXIS_HORIZONTAL} and/or
+ * {@link #SCROLL_AXIS_VERTICAL}.
+ * @return true if a cooperative parent was found and nested scrolling has been enabled for
+ * the current gesture.
+ *
+ * @see #stopNestedScroll()
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ public boolean startNestedScroll(int axes) {
+ if (hasNestedScrollingParent()) {
+ // Already in progress
+ return true;
+ }
+ if (isNestedScrollingEnabled()) {
+ ViewParent p = getParent();
+ View child = this;
+ while (p != null) {
+ try {
+ if (p.onStartNestedScroll(child, this, axes)) {
+ mNestedScrollingParent = p;
+ p.onNestedScrollAccepted(child, this, axes);
+ return true;
+ }
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, "ViewParent " + p + " does not implement interface " +
+ "method onStartNestedScroll", e);
+ // Allow the search upward to continue
+ }
+ if (p instanceof View) {
+ child = (View) p;
+ }
+ p = p.getParent();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stop a nested scroll in progress.
+ *
+ * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+ *
+ * @see #startNestedScroll(int)
+ */
+ public void stopNestedScroll() {
+ if (mNestedScrollingParent != null) {
+ mNestedScrollingParent.onStopNestedScroll(this);
+ mNestedScrollingParent = null;
+ }
+ }
+
+ /**
+ * Returns true if this view has a nested scrolling parent.
+ *
+ * <p>The presence of a nested scrolling parent indicates that this view has initiated
+ * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+ *
+ * @return whether this view has a nested scrolling parent
+ */
+ public boolean hasNestedScrollingParent() {
+ return mNestedScrollingParent != null;
+ }
+
+ /**
+ * Dispatch one step of a nested scroll in progress.
+ *
+ * <p>Implementations of views that support nested scrolling should call this to report
+ * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+ * is not currently in progress or nested scrolling is not
+ * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p>
+ *
+ * <p>Compatible View implementations should also call
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before
+ * consuming a component of the scroll event themselves.</p>
+ *
+ * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+ * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the event was dispatched, false if it could not be dispatched.
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ */
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, @Nullable @Size(2) int[] offsetInWindow) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
+
+ mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
+ dxUnconsumed, dyUnconsumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return true;
+ } else if (offsetInWindow != null) {
+ // No motion, no dispatch. Keep offsetInWindow up to date.
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+ *
+ * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+ * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+ * scrolling operation to consume some or all of the scroll operation before the child view
+ * consumes it.</p>
+ *
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+ * and consumed[1] the consumed dy.
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the parent consumed some or all of the scroll delta
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ public boolean dispatchNestedPreScroll(int dx, int dy,
+ @Nullable @Size(2) int[] consumed, @Nullable @Size(2) int[] offsetInWindow) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ if (dx != 0 || dy != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
+
+ if (consumed == null) {
+ if (mTempNestedScrollConsumed == null) {
+ mTempNestedScrollConsumed = new int[2];
+ }
+ consumed = mTempNestedScrollConsumed;
+ }
+ consumed[0] = 0;
+ consumed[1] = 0;
+ mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return consumed[0] != 0 || consumed[1] != 0;
+ } else if (offsetInWindow != null) {
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch a fling to a nested scrolling parent.
+ *
+ * <p>This method should be used to indicate that a nested scrolling child has detected
+ * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
+ */
+ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY, consumed);
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch a fling to a nested scrolling parent before it is processed by this view.
+ *
+ * <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
+ * and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
+ * offsets an opportunity for the parent view in a nested fling to fully consume the fling
+ * before the child view consumes it. If this method returns <code>true</code>, a nested
+ * parent view consumed the fling and this view should not scroll as a result.</p>
+ *
+ * <p>For a better user experience, only one view in a nested scrolling chain should consume
+ * the fling at a time. If a parent view consumed the fling this method will return false.
+ * Custom view implementations should account for this in two ways:</p>
+ *
+ * <ul>
+ * <li>If a custom view is paged and needs to settle to a fixed page-point, do not
+ * call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
+ * position regardless.</li>
+ * <li>If a nested parent does consume the fling, this view should not scroll at all,
+ * even to settle back to a valid idle position.</li>
+ * </ul>
+ *
+ * <p>Views should also not offer fling velocities to nested parent views along an axis
+ * where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
+ * should not offer a horizontal fling velocity to its parents since scrolling along that
+ * axis is not permitted and carrying velocity along that motion does not make sense.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @return true if a nested scrolling parent consumed the fling
+ */
+ public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ return mNestedScrollingParent.onNestedPreFling(this, velocityX, velocityY);
+ }
+ return false;
+ }
+
+ /**
+ * Gets a scale factor that determines the distance the view should scroll
+ * vertically in response to {@link MotionEvent#ACTION_SCROLL}.
+ * @return The vertical scroll scale factor.
+ * @hide
+ */
+ protected float getVerticalScrollFactor() {
+ if (mVerticalScrollFactor == 0) {
+ TypedValue outValue = new TypedValue();
+ if (!mContext.getTheme().resolveAttribute(
+ com.android.internal.R.attr.listPreferredItemHeight, outValue, true)) {
+ throw new IllegalStateException(
+ "Expected theme to define listPreferredItemHeight.");
+ }
+ mVerticalScrollFactor = outValue.getDimension(
+ mContext.getResources().getDisplayMetrics());
+ }
+ return mVerticalScrollFactor;
+ }
+
+ /**
+ * Gets a scale factor that determines the distance the view should scroll
+ * horizontally in response to {@link MotionEvent#ACTION_SCROLL}.
+ * @return The horizontal scroll scale factor.
+ * @hide
+ */
+ protected float getHorizontalScrollFactor() {
+ // TODO: Should use something else.
+ return getVerticalScrollFactor();
+ }
+
+ /**
+ * Return the value specifying the text direction or policy that was set with
+ * {@link #setTextDirection(int)}.
+ *
+ * @return the defined text direction. It can be one of:
+ *
+ * {@link #TEXT_DIRECTION_INHERIT},
+ * {@link #TEXT_DIRECTION_FIRST_STRONG},
+ * {@link #TEXT_DIRECTION_ANY_RTL},
+ * {@link #TEXT_DIRECTION_LTR},
+ * {@link #TEXT_DIRECTION_RTL},
+ * {@link #TEXT_DIRECTION_LOCALE},
+ * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR},
+ * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL}
+ *
+ * @attr ref android.R.styleable#View_textDirection
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "text", mapping = {
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL")
+ })
+ public int getRawTextDirection() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_MASK) >> PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
+ }
+
+ /**
+ * Set the text direction.
+ *
+ * @param textDirection the direction to set. Should be one of:
+ *
+ * {@link #TEXT_DIRECTION_INHERIT},
+ * {@link #TEXT_DIRECTION_FIRST_STRONG},
+ * {@link #TEXT_DIRECTION_ANY_RTL},
+ * {@link #TEXT_DIRECTION_LTR},
+ * {@link #TEXT_DIRECTION_RTL},
+ * {@link #TEXT_DIRECTION_LOCALE}
+ * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR},
+ * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL},
+ *
+ * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it will
+ * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}.
+ *
+ * @attr ref android.R.styleable#View_textDirection
+ */
+ public void setTextDirection(int textDirection) {
+ if (getRawTextDirection() != textDirection) {
+ // Reset the current text direction and the resolved one
+ mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
+ resetResolvedTextDirection();
+ // Set the new text direction
+ mPrivateFlags2 |= ((textDirection << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) & PFLAG2_TEXT_DIRECTION_MASK);
+ // Do resolution
+ resolveTextDirection();
+ // Notify change
+ onRtlPropertiesChanged(getLayoutDirection());
+ // Refresh
+ requestLayout();
+ invalidate(true);
+ }
+ }
+
+ /**
+ * Return the resolved text direction.
+ *
+ * @return the resolved text direction. Returns one of:
+ *
+ * {@link #TEXT_DIRECTION_FIRST_STRONG},
+ * {@link #TEXT_DIRECTION_ANY_RTL},
+ * {@link #TEXT_DIRECTION_LTR},
+ * {@link #TEXT_DIRECTION_RTL},
+ * {@link #TEXT_DIRECTION_LOCALE},
+ * {@link #TEXT_DIRECTION_FIRST_STRONG_LTR},
+ * {@link #TEXT_DIRECTION_FIRST_STRONG_RTL}
+ *
+ * @attr ref android.R.styleable#View_textDirection
+ */
+ @ViewDebug.ExportedProperty(category = "text", mapping = {
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_LTR, to = "FIRST_STRONG_LTR"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG_RTL, to = "FIRST_STRONG_RTL")
+ })
+ public int getTextDirection() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+ }
+
+ /**
+ * Resolve the text direction.
+ *
+ * @return true if resolution has been done, false otherwise.
+ *
+ * @hide
+ */
+ public boolean resolveTextDirection() {
+ // Reset any previous text direction resolution
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
+
+ if (hasRtlSupport()) {
+ // Set resolved text direction flag depending on text direction flag
+ final int textDirection = getRawTextDirection();
+ switch(textDirection) {
+ case TEXT_DIRECTION_INHERIT:
+ if (!canResolveTextDirection()) {
+ // We cannot do the resolution if there is no parent, so use the default one
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+
+ // Parent has not yet resolved, so we still return the default
+ try {
+ if (!mParent.isTextDirectionResolved()) {
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED |
+ PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ return true;
+ }
+
+ // Set current resolved direction to the same value as the parent's one
+ int parentResolvedDirection;
+ try {
+ parentResolvedDirection = mParent.getTextDirection();
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ parentResolvedDirection = TEXT_DIRECTION_LTR;
+ }
+ switch (parentResolvedDirection) {
+ case TEXT_DIRECTION_FIRST_STRONG:
+ case TEXT_DIRECTION_ANY_RTL:
+ case TEXT_DIRECTION_LTR:
+ case TEXT_DIRECTION_RTL:
+ case TEXT_DIRECTION_LOCALE:
+ case TEXT_DIRECTION_FIRST_STRONG_LTR:
+ case TEXT_DIRECTION_FIRST_STRONG_RTL:
+ mPrivateFlags2 |=
+ (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Default resolved direction is "first strong" heuristic
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ }
+ break;
+ case TEXT_DIRECTION_FIRST_STRONG:
+ case TEXT_DIRECTION_ANY_RTL:
+ case TEXT_DIRECTION_LTR:
+ case TEXT_DIRECTION_RTL:
+ case TEXT_DIRECTION_LOCALE:
+ case TEXT_DIRECTION_FIRST_STRONG_LTR:
+ case TEXT_DIRECTION_FIRST_STRONG_RTL:
+ // Resolved direction is the same as text direction
+ mPrivateFlags2 |= (textDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Default resolved direction is "first strong" heuristic
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ }
+ } else {
+ // Default resolved direction is "first strong" heuristic
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ // Set to resolved
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED;
+ return true;
+ }
+
+ /**
+ * Check if text direction resolution can be done.
+ *
+ * @return true if text direction resolution can be done otherwise return false.
+ */
+ public boolean canResolveTextDirection() {
+ switch (getRawTextDirection()) {
+ case TEXT_DIRECTION_INHERIT:
+ if (mParent != null) {
+ try {
+ return mParent.canResolveTextDirection();
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Reset resolved text direction. Text direction will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
+ *
+ * @hide
+ */
+ public void resetResolvedTextDirection() {
+ // Reset any previous text direction resolution
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
+ // Set to default value
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ /**
+ * @return true if text direction is inherited.
+ *
+ * @hide
+ */
+ public boolean isTextDirectionInherited() {
+ return (getRawTextDirection() == TEXT_DIRECTION_INHERIT);
+ }
+
+ /**
+ * @return true if text direction is resolved.
+ */
+ public boolean isTextDirectionResolved() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED;
+ }
+
+ /**
+ * Return the value specifying the text alignment or policy that was set with
+ * {@link #setTextAlignment(int)}.
+ *
+ * @return the defined text alignment. It can be one of:
+ *
+ * {@link #TEXT_ALIGNMENT_INHERIT},
+ * {@link #TEXT_ALIGNMENT_GRAVITY},
+ * {@link #TEXT_ALIGNMENT_CENTER},
+ * {@link #TEXT_ALIGNMENT_TEXT_START},
+ * {@link #TEXT_ALIGNMENT_TEXT_END},
+ * {@link #TEXT_ALIGNMENT_VIEW_START},
+ * {@link #TEXT_ALIGNMENT_VIEW_END}
+ *
+ * @attr ref android.R.styleable#View_textAlignment
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "text", mapping = {
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
+ })
+ @TextAlignment
+ public int getRawTextAlignment() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_MASK) >> PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
+ }
+
+ /**
+ * Set the text alignment.
+ *
+ * @param textAlignment The text alignment to set. Should be one of
+ *
+ * {@link #TEXT_ALIGNMENT_INHERIT},
+ * {@link #TEXT_ALIGNMENT_GRAVITY},
+ * {@link #TEXT_ALIGNMENT_CENTER},
+ * {@link #TEXT_ALIGNMENT_TEXT_START},
+ * {@link #TEXT_ALIGNMENT_TEXT_END},
+ * {@link #TEXT_ALIGNMENT_VIEW_START},
+ * {@link #TEXT_ALIGNMENT_VIEW_END}
+ *
+ * Resolution will be done if the value is set to TEXT_ALIGNMENT_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+ * will return the default {@link #TEXT_ALIGNMENT_GRAVITY}.
+ *
+ * @attr ref android.R.styleable#View_textAlignment
+ */
+ public void setTextAlignment(@TextAlignment int textAlignment) {
+ if (textAlignment != getRawTextAlignment()) {
+ // Reset the current and resolved text alignment
+ mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
+ resetResolvedTextAlignment();
+ // Set the new text alignment
+ mPrivateFlags2 |=
+ ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK);
+ // Do resolution
+ resolveTextAlignment();
+ // Notify change
+ onRtlPropertiesChanged(getLayoutDirection());
+ // Refresh
+ requestLayout();
+ invalidate(true);
+ }
+ }
+
+ /**
+ * Return the resolved text alignment.
+ *
+ * @return the resolved text alignment. Returns one of:
+ *
+ * {@link #TEXT_ALIGNMENT_GRAVITY},
+ * {@link #TEXT_ALIGNMENT_CENTER},
+ * {@link #TEXT_ALIGNMENT_TEXT_START},
+ * {@link #TEXT_ALIGNMENT_TEXT_END},
+ * {@link #TEXT_ALIGNMENT_VIEW_START},
+ * {@link #TEXT_ALIGNMENT_VIEW_END}
+ *
+ * @attr ref android.R.styleable#View_textAlignment
+ */
+ @ViewDebug.ExportedProperty(category = "text", mapping = {
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
+ @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
+ })
+ @TextAlignment
+ public int getTextAlignment() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >>
+ PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ }
+
+ /**
+ * Resolve the text alignment.
+ *
+ * @return true if resolution has been done, false otherwise.
+ *
+ * @hide
+ */
+ public boolean resolveTextAlignment() {
+ // Reset any previous text alignment resolution
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
+
+ if (hasRtlSupport()) {
+ // Set resolved text alignment flag depending on text alignment flag
+ final int textAlignment = getRawTextAlignment();
+ switch (textAlignment) {
+ case TEXT_ALIGNMENT_INHERIT:
+ // Check if we can resolve the text alignment
+ if (!canResolveTextAlignment()) {
+ // We cannot do the resolution if there is no parent so use the default
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+
+ // Parent has not yet resolved, so we still return the default
+ try {
+ if (!mParent.isTextAlignmentResolved()) {
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED |
+ PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ return true;
+ }
+
+ int parentResolvedTextAlignment;
+ try {
+ parentResolvedTextAlignment = mParent.getTextAlignment();
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ parentResolvedTextAlignment = TEXT_ALIGNMENT_GRAVITY;
+ }
+ switch (parentResolvedTextAlignment) {
+ case TEXT_ALIGNMENT_GRAVITY:
+ case TEXT_ALIGNMENT_TEXT_START:
+ case TEXT_ALIGNMENT_TEXT_END:
+ case TEXT_ALIGNMENT_CENTER:
+ case TEXT_ALIGNMENT_VIEW_START:
+ case TEXT_ALIGNMENT_VIEW_END:
+ // Resolved text alignment is the same as the parent resolved
+ // text alignment
+ mPrivateFlags2 |=
+ (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Use default resolved text alignment
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ }
+ break;
+ case TEXT_ALIGNMENT_GRAVITY:
+ case TEXT_ALIGNMENT_TEXT_START:
+ case TEXT_ALIGNMENT_TEXT_END:
+ case TEXT_ALIGNMENT_CENTER:
+ case TEXT_ALIGNMENT_VIEW_START:
+ case TEXT_ALIGNMENT_VIEW_END:
+ // Resolved text alignment is the same as text alignment
+ mPrivateFlags2 |= (textAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Use default resolved text alignment
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ }
+ } else {
+ // Use default resolved text alignment
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ }
+
+ // Set the resolved
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+ return true;
+ }
+
+ /**
+ * Check if text alignment resolution can be done.
+ *
+ * @return true if text alignment resolution can be done otherwise return false.
+ */
+ public boolean canResolveTextAlignment() {
+ switch (getRawTextAlignment()) {
+ case TEXT_DIRECTION_INHERIT:
+ if (mParent != null) {
+ try {
+ return mParent.canResolveTextAlignment();
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Reset resolved text alignment. Text alignment will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
+ *
+ * @hide
+ */
+ public void resetResolvedTextAlignment() {
+ // Reset any previous text alignment resolution
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
+ // Set to default
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ }
+
+ /**
+ * @return true if text alignment is inherited.
+ *
+ * @hide
+ */
+ public boolean isTextAlignmentInherited() {
+ return (getRawTextAlignment() == TEXT_ALIGNMENT_INHERIT);
+ }
+
+ /**
+ * @return true if text alignment is resolved.
+ */
+ public boolean isTextAlignmentResolved() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+ }
+
+ /**
+ * Generate a value suitable for use in {@link #setId(int)}.
+ * This value will not collide with ID values generated at build time by aapt for R.id.
+ *
+ * @return a generated ID value
+ */
+ public static int generateViewId() {
+ for (;;) {
+ final int result = sNextGeneratedId.get();
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ int newValue = result + 1;
+ if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
+ if (sNextGeneratedId.compareAndSet(result, newValue)) {
+ return result;
+ }
+ }
+ }
+
+ private static boolean isViewIdGenerated(int id) {
+ return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0;
+ }
+
+ /**
+ * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
+ * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
+ * a normal View or a ViewGroup with
+ * {@link android.view.ViewGroup#isTransitionGroup()} true.
+ * @hide
+ */
+ public void captureTransitioningViews(List<View> transitioningViews) {
+ if (getVisibility() == View.VISIBLE) {
+ transitioningViews.add(this);
+ }
+ }
+
+ /**
+ * Adds all Views that have {@link #getTransitionName()} non-null to namedElements.
+ * @param namedElements Will contain all Views in the hierarchy having a transitionName.
+ * @hide
+ */
+ public void findNamedViews(Map<String, View> namedElements) {
+ if (getVisibility() == VISIBLE || mGhostView != null) {
+ String transitionName = getTransitionName();
+ if (transitionName != null) {
+ namedElements.put(transitionName, this);
+ }
+ }
+ }
+
+ /**
+ * Returns the pointer icon for the motion event, or null if it doesn't specify the icon.
+ * The default implementation does not care the location or event types, but some subclasses
+ * may use it (such as WebViews).
+ * @param event The MotionEvent from a mouse
+ * @param pointerIndex The index of the pointer for which to retrieve the {@link PointerIcon}.
+ * This will be between 0 and {@link MotionEvent#getPointerCount()}.
+ * @see PointerIcon
+ */
+ public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ if (isDraggingScrollBar() || isOnScrollbarThumb(x, y)) {
+ return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+ }
+ return mPointerIcon;
+ }
+
+ /**
+ * Set the pointer icon for the current view.
+ * Passing {@code null} will restore the pointer icon to its default value.
+ * @param pointerIcon A PointerIcon instance which will be shown when the mouse hovers.
+ */
+ public void setPointerIcon(PointerIcon pointerIcon) {
+ mPointerIcon = pointerIcon;
+ if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
+ return;
+ }
+ try {
+ mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Gets the pointer icon for the current view.
+ */
+ public PointerIcon getPointerIcon() {
+ return mPointerIcon;
+ }
+
+ /**
+ * Checks pointer capture status.
+ *
+ * @return true if the view has pointer capture.
+ * @see #requestPointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public boolean hasPointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return false;
+ }
+ return viewRootImpl.hasPointerCapture();
+ }
+
+ /**
+ * Requests pointer capture mode.
+ * <p>
+ * When the window has pointer capture, the mouse pointer icon will disappear and will not
+ * change its position. Further mouse will be dispatched with the source
+ * {@link InputDevice#SOURCE_MOUSE_RELATIVE}, and relative position changes will be available
+ * through {@link MotionEvent#getX} and {@link MotionEvent#getY}. Non-mouse events
+ * (touchscreens, or stylus) will not be affected.
+ * <p>
+ * If the window already has pointer capture, this call does nothing.
+ * <p>
+ * The capture may be released through {@link #releasePointerCapture()}, or will be lost
+ * automatically when the window loses focus.
+ *
+ * @see #releasePointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public void requestPointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.requestPointerCapture(true);
+ }
+ }
+
+
+ /**
+ * Releases the pointer capture.
+ * <p>
+ * If the window does not have pointer capture, this call will do nothing.
+ * @see #requestPointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public void releasePointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.requestPointerCapture(false);
+ }
+ }
+
+ /**
+ * Called when the window has just acquired or lost pointer capture.
+ *
+ * @param hasCapture True if the view now has pointerCapture, false otherwise.
+ */
+ @CallSuper
+ public void onPointerCaptureChange(boolean hasCapture) {
+ }
+
+ /**
+ * @see #onPointerCaptureChange
+ */
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ onPointerCaptureChange(hasCapture);
+ }
+
+ /**
+ * Implement this method to handle captured pointer events
+ *
+ * @param event The captured pointer event.
+ * @return True if the event was handled, false otherwise.
+ * @see #requestPointerCapture()
+ */
+ public boolean onCapturedPointerEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a captured pointer event
+ * is being dispatched this view. The callback will be invoked before the event is
+ * given to the view.
+ */
+ public interface OnCapturedPointerListener {
+ /**
+ * Called when a captured pointer event is dispatched to a view.
+ * @param view The view this event has been dispatched to.
+ * @param event The captured event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onCapturedPointer(View view, MotionEvent event);
+ }
+
+ /**
+ * Set a listener to receive callbacks when the pointer capture state of a view changes.
+ * @param l The {@link OnCapturedPointerListener} to receive callbacks.
+ */
+ public void setOnCapturedPointerListener(OnCapturedPointerListener l) {
+ getListenerInfo().mOnCapturedPointerListener = l;
+ }
+
+ // Properties
+ //
+ /**
+ * A Property wrapper around the <code>alpha</code> functionality handled by the
+ * {@link View#setAlpha(float)} and {@link View#getAlpha()} methods.
+ */
+ public static final Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setAlpha(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getAlpha();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>translationX</code> functionality handled by the
+ * {@link View#setTranslationX(float)} and {@link View#getTranslationX()} methods.
+ */
+ public static final Property<View, Float> TRANSLATION_X = new FloatProperty<View>("translationX") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setTranslationX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getTranslationX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>translationY</code> functionality handled by the
+ * {@link View#setTranslationY(float)} and {@link View#getTranslationY()} methods.
+ */
+ public static final Property<View, Float> TRANSLATION_Y = new FloatProperty<View>("translationY") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setTranslationY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getTranslationY();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>translationZ</code> functionality handled by the
+ * {@link View#setTranslationZ(float)} and {@link View#getTranslationZ()} methods.
+ */
+ public static final Property<View, Float> TRANSLATION_Z = new FloatProperty<View>("translationZ") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setTranslationZ(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getTranslationZ();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>x</code> functionality handled by the
+ * {@link View#setX(float)} and {@link View#getX()} methods.
+ */
+ public static final Property<View, Float> X = new FloatProperty<View>("x") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>y</code> functionality handled by the
+ * {@link View#setY(float)} and {@link View#getY()} methods.
+ */
+ public static final Property<View, Float> Y = new FloatProperty<View>("y") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getY();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>z</code> functionality handled by the
+ * {@link View#setZ(float)} and {@link View#getZ()} methods.
+ */
+ public static final Property<View, Float> Z = new FloatProperty<View>("z") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setZ(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getZ();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>rotation</code> functionality handled by the
+ * {@link View#setRotation(float)} and {@link View#getRotation()} methods.
+ */
+ public static final Property<View, Float> ROTATION = new FloatProperty<View>("rotation") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setRotation(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getRotation();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>rotationX</code> functionality handled by the
+ * {@link View#setRotationX(float)} and {@link View#getRotationX()} methods.
+ */
+ public static final Property<View, Float> ROTATION_X = new FloatProperty<View>("rotationX") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setRotationX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getRotationX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>rotationY</code> functionality handled by the
+ * {@link View#setRotationY(float)} and {@link View#getRotationY()} methods.
+ */
+ public static final Property<View, Float> ROTATION_Y = new FloatProperty<View>("rotationY") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setRotationY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getRotationY();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>scaleX</code> functionality handled by the
+ * {@link View#setScaleX(float)} and {@link View#getScaleX()} methods.
+ */
+ public static final Property<View, Float> SCALE_X = new FloatProperty<View>("scaleX") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setScaleX(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getScaleX();
+ }
+ };
+
+ /**
+ * A Property wrapper around the <code>scaleY</code> functionality handled by the
+ * {@link View#setScaleY(float)} and {@link View#getScaleY()} methods.
+ */
+ public static final Property<View, Float> SCALE_Y = new FloatProperty<View>("scaleY") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setScaleY(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getScaleY();
+ }
+ };
+
+ /**
+ * A MeasureSpec encapsulates the layout requirements passed from parent to child.
+ * Each MeasureSpec represents a requirement for either the width or the height.
+ * A MeasureSpec is comprised of a size and a mode. There are three possible
+ * modes:
+ * <dl>
+ * <dt>UNSPECIFIED</dt>
+ * <dd>
+ * The parent has not imposed any constraint on the child. It can be whatever size
+ * it wants.
+ * </dd>
+ *
+ * <dt>EXACTLY</dt>
+ * <dd>
+ * The parent has determined an exact size for the child. The child is going to be
+ * given those bounds regardless of how big it wants to be.
+ * </dd>
+ *
+ * <dt>AT_MOST</dt>
+ * <dd>
+ * The child can be as large as it wants up to the specified size.
+ * </dd>
+ * </dl>
+ *
+ * MeasureSpecs are implemented as ints to reduce object allocation. This class
+ * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
+ */
+ public static class MeasureSpec {
+ private static final int MODE_SHIFT = 30;
+ private static final int MODE_MASK = 0x3 << MODE_SHIFT;
+
+ /** @hide */
+ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MeasureSpecMode {}
+
+ /**
+ * Measure specification mode: The parent has not imposed any constraint
+ * on the child. It can be whatever size it wants.
+ */
+ public static final int UNSPECIFIED = 0 << MODE_SHIFT;
+
+ /**
+ * Measure specification mode: The parent has determined an exact size
+ * for the child. The child is going to be given those bounds regardless
+ * of how big it wants to be.
+ */
+ public static final int EXACTLY = 1 << MODE_SHIFT;
+
+ /**
+ * Measure specification mode: The child can be as large as it wants up
+ * to the specified size.
+ */
+ public static final int AT_MOST = 2 << MODE_SHIFT;
+
+ /**
+ * Creates a measure specification based on the supplied size and mode.
+ *
+ * The mode must always be one of the following:
+ * <ul>
+ * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
+ * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
+ * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
+ * </ul>
+ *
+ * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
+ * implementation was such that the order of arguments did not matter
+ * and overflow in either value could impact the resulting MeasureSpec.
+ * {@link android.widget.RelativeLayout} was affected by this bug.
+ * Apps targeting API levels greater than 17 will get the fixed, more strict
+ * behavior.</p>
+ *
+ * @param size the size of the measure specification
+ * @param mode the mode of the measure specification
+ * @return the measure specification based on size and mode
+ */
+ public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
+ @MeasureSpecMode int mode) {
+ if (sUseBrokenMakeMeasureSpec) {
+ return size + mode;
+ } else {
+ return (size & ~MODE_MASK) | (mode & MODE_MASK);
+ }
+ }
+
+ /**
+ * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
+ * will automatically get a size of 0. Older apps expect this.
+ *
+ * @hide internal use only for compatibility with system widgets and older apps
+ */
+ public static int makeSafeMeasureSpec(int size, int mode) {
+ if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
+ return 0;
+ }
+ return makeMeasureSpec(size, mode);
+ }
+
+ /**
+ * Extracts the mode from the supplied measure specification.
+ *
+ * @param measureSpec the measure specification to extract the mode from
+ * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
+ * {@link android.view.View.MeasureSpec#AT_MOST} or
+ * {@link android.view.View.MeasureSpec#EXACTLY}
+ */
+ @MeasureSpecMode
+ public static int getMode(int measureSpec) {
+ //noinspection ResourceType
+ return (measureSpec & MODE_MASK);
+ }
+
+ /**
+ * Extracts the size from the supplied measure specification.
+ *
+ * @param measureSpec the measure specification to extract the size from
+ * @return the size in pixels defined in the supplied measure specification
+ */
+ public static int getSize(int measureSpec) {
+ return (measureSpec & ~MODE_MASK);
+ }
+
+ static int adjust(int measureSpec, int delta) {
+ final int mode = getMode(measureSpec);
+ int size = getSize(measureSpec);
+ if (mode == UNSPECIFIED) {
+ // No need to adjust size for UNSPECIFIED mode.
+ return makeMeasureSpec(size, UNSPECIFIED);
+ }
+ size += delta;
+ if (size < 0) {
+ Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
+ ") spec: " + toString(measureSpec) + " delta: " + delta);
+ size = 0;
+ }
+ return makeMeasureSpec(size, mode);
+ }
+
+ /**
+ * Returns a String representation of the specified measure
+ * specification.
+ *
+ * @param measureSpec the measure specification to convert to a String
+ * @return a String with the following format: "MeasureSpec: MODE SIZE"
+ */
+ public static String toString(int measureSpec) {
+ int mode = getMode(measureSpec);
+ int size = getSize(measureSpec);
+
+ StringBuilder sb = new StringBuilder("MeasureSpec: ");
+
+ if (mode == UNSPECIFIED)
+ sb.append("UNSPECIFIED ");
+ else if (mode == EXACTLY)
+ sb.append("EXACTLY ");
+ else if (mode == AT_MOST)
+ sb.append("AT_MOST ");
+ else
+ sb.append(mode).append(" ");
+
+ sb.append(size);
+ return sb.toString();
+ }
+ }
+
+ private final class CheckForLongPress implements Runnable {
+ private int mOriginalWindowAttachCount;
+ private float mX;
+ private float mY;
+ private boolean mOriginalPressedState;
+
+ @Override
+ public void run() {
+ if ((mOriginalPressedState == isPressed()) && (mParent != null)
+ && mOriginalWindowAttachCount == mWindowAttachCount) {
+ if (performLongClick(mX, mY)) {
+ mHasPerformedLongPress = true;
+ }
+ }
+ }
+
+ public void setAnchor(float x, float y) {
+ mX = x;
+ mY = y;
+ }
+
+ public void rememberWindowAttachCount() {
+ mOriginalWindowAttachCount = mWindowAttachCount;
+ }
+
+ public void rememberPressedState() {
+ mOriginalPressedState = isPressed();
+ }
+ }
+
+ private final class CheckForTap implements Runnable {
+ public float x;
+ public float y;
+
+ @Override
+ public void run() {
+ mPrivateFlags &= ~PFLAG_PREPRESSED;
+ setPressed(true, x, y);
+ checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
+ }
+ }
+
+ private final class PerformClick implements Runnable {
+ @Override
+ public void run() {
+ performClick();
+ }
+ }
+
+ /**
+ * This method returns a ViewPropertyAnimator object, which can be used to animate
+ * specific properties on this View.
+ *
+ * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
+ */
+ public ViewPropertyAnimator animate() {
+ if (mAnimator == null) {
+ mAnimator = new ViewPropertyAnimator(this);
+ }
+ return mAnimator;
+ }
+
+ /**
+ * Sets the name of the View to be used to identify Views in Transitions.
+ * Names should be unique in the View hierarchy.
+ *
+ * @param transitionName The name of the View to uniquely identify it for Transitions.
+ */
+ public final void setTransitionName(String transitionName) {
+ mTransitionName = transitionName;
+ }
+
+ /**
+ * Returns the name of the View to be used to identify Views in Transitions.
+ * Names should be unique in the View hierarchy.
+ *
+ * <p>This returns null if the View has not been given a name.</p>
+ *
+ * @return The name used of the View to be used to identify Views in Transitions or null
+ * if no name has been given.
+ */
+ @ViewDebug.ExportedProperty
+ public String getTransitionName() {
+ return mTransitionName;
+ }
+
+ /**
+ * @hide
+ */
+ public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> data, int deviceId) {
+ // Do nothing.
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a hardware key event is
+ * dispatched to this view. The callback will be invoked before the key event is
+ * given to the view. This is only useful for hardware keyboards; a software input
+ * method has no obligation to trigger this listener.
+ */
+ public interface OnKeyListener {
+ /**
+ * Called when a hardware key is dispatched to a view. This allows listeners to
+ * get a chance to respond before the target view.
+ * <p>Key presses in software keyboards will generally NOT trigger this method,
+ * although some may elect to do so in some situations. Do not assume a
+ * software input method has to be key-based; even if it is, it may use key presses
+ * in a different way than you expect, so there is no way to reliably catch soft
+ * input key presses.
+ *
+ * @param v The view the key has been dispatched to.
+ * @param keyCode The code for the physical key that was pressed
+ * @param event The KeyEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onKey(View v, int keyCode, KeyEvent event);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a touch event is
+ * dispatched to this view. The callback will be invoked before the touch
+ * event is given to the view.
+ */
+ public interface OnTouchListener {
+ /**
+ * Called when a touch event is dispatched to a view. This allows listeners to
+ * get a chance to respond before the target view.
+ *
+ * @param v The view the touch event has been dispatched to.
+ * @param event The MotionEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onTouch(View v, MotionEvent event);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a hover event is
+ * dispatched to this view. The callback will be invoked before the hover
+ * event is given to the view.
+ */
+ public interface OnHoverListener {
+ /**
+ * Called when a hover event is dispatched to a view. This allows listeners to
+ * get a chance to respond before the target view.
+ *
+ * @param v The view the hover event has been dispatched to.
+ * @param event The MotionEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onHover(View v, MotionEvent event);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a generic motion event is
+ * dispatched to this view. The callback will be invoked before the generic motion
+ * event is given to the view.
+ */
+ public interface OnGenericMotionListener {
+ /**
+ * Called when a generic motion event is dispatched to a view. This allows listeners to
+ * get a chance to respond before the target view.
+ *
+ * @param v The view the generic motion event has been dispatched to.
+ * @param event The MotionEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onGenericMotion(View v, MotionEvent event);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a view has been clicked and held.
+ */
+ public interface OnLongClickListener {
+ /**
+ * Called when a view has been clicked and held.
+ *
+ * @param v The view that was clicked and held.
+ *
+ * @return true if the callback consumed the long click, false otherwise.
+ */
+ boolean onLongClick(View v);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a drag is being dispatched
+ * to this view. The callback will be invoked before the hosting view's own
+ * onDrag(event) method. If the listener wants to fall back to the hosting view's
+ * onDrag(event) behavior, it should return 'false' from this callback.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For a guide to implementing drag and drop features, read the
+ * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+ * </div>
+ */
+ public interface OnDragListener {
+ /**
+ * Called when a drag event is dispatched to a view. This allows listeners
+ * to get a chance to override base View behavior.
+ *
+ * @param v The View that received the drag event.
+ * @param event The {@link android.view.DragEvent} object for the drag event.
+ * @return {@code true} if the drag event was handled successfully, or {@code false}
+ * if the drag event was not handled. Note that {@code false} will trigger the View
+ * to call its {@link #onDragEvent(DragEvent) onDragEvent()} handler.
+ */
+ boolean onDrag(View v, DragEvent event);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the focus state of
+ * a view changed.
+ */
+ public interface OnFocusChangeListener {
+ /**
+ * Called when the focus state of a view has changed.
+ *
+ * @param v The view whose state has changed.
+ * @param hasFocus The new focus state of v.
+ */
+ void onFocusChange(View v, boolean hasFocus);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a view is clicked.
+ */
+ public interface OnClickListener {
+ /**
+ * Called when a view has been clicked.
+ *
+ * @param v The view that was clicked.
+ */
+ void onClick(View v);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a view is context clicked.
+ */
+ public interface OnContextClickListener {
+ /**
+ * Called when a view is context clicked.
+ *
+ * @param v The view that has been context clicked.
+ * @return true if the callback consumed the context click, false otherwise.
+ */
+ boolean onContextClick(View v);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the context menu
+ * for this view is being built.
+ */
+ public interface OnCreateContextMenuListener {
+ /**
+ * Called when the context menu for this view is being built. It is not
+ * safe to hold onto the menu after this method returns.
+ *
+ * @param menu The context menu that is being built
+ * @param v The view for which the context menu is being built
+ * @param menuInfo Extra information about the item for which the
+ * context menu should be shown. This information will vary
+ * depending on the class of v.
+ */
+ void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the status bar changes
+ * visibility. This reports <strong>global</strong> changes to the system UI
+ * state, not what the application is requesting.
+ *
+ * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener)
+ */
+ public interface OnSystemUiVisibilityChangeListener {
+ /**
+ * Called when the status bar changes visibility because of a call to
+ * {@link View#setSystemUiVisibility(int)}.
+ *
+ * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, and {@link #SYSTEM_UI_FLAG_FULLSCREEN}.
+ * This tells you the <strong>global</strong> state of these UI visibility
+ * flags, not what your app is currently applying.
+ */
+ public void onSystemUiVisibilityChange(int visibility);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when this view is attached
+ * or detached from its window.
+ */
+ public interface OnAttachStateChangeListener {
+ /**
+ * Called when the view is attached to a window.
+ * @param v The view that was attached
+ */
+ public void onViewAttachedToWindow(View v);
+ /**
+ * Called when the view is detached from a window.
+ * @param v The view that was detached
+ */
+ public void onViewDetachedFromWindow(View v);
+ }
+
+ /**
+ * Listener for applying window insets on a view in a custom way.
+ *
+ * <p>Apps may choose to implement this interface if they want to apply custom policy
+ * to the way that window insets are treated for a view. If an OnApplyWindowInsetsListener
+ * is set, its
+ * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
+ * method will be called instead of the View's own
+ * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method. The listener
+ * may optionally call the parameter View's <code>onApplyWindowInsets</code> method to apply
+ * the View's normal behavior as part of its own.</p>
+ */
+ public interface OnApplyWindowInsetsListener {
+ /**
+ * When {@link View#setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) set}
+ * on a View, this listener method will be called instead of the view's own
+ * {@link View#onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
+ *
+ * @param v The view applying window insets
+ * @param insets The insets to apply
+ * @return The insets supplied, minus any insets that were consumed
+ */
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets);
+ }
+
+ private final class UnsetPressedState implements Runnable {
+ @Override
+ public void run() {
+ setPressed(false);
+ }
+ }
+
+ /**
+ * When a view becomes invisible checks if autofill considers the view invisible too. This
+ * happens after the regular removal operation to make sure the operation is finished by the
+ * time this is called.
+ */
+ private static class VisibilityChangeForAutofillHandler extends Handler {
+ private final AutofillManager mAfm;
+ private final View mView;
+
+ private VisibilityChangeForAutofillHandler(@NonNull AutofillManager afm,
+ @NonNull View view) {
+ mAfm = afm;
+ mView = view;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mAfm.notifyViewVisibilityChanged(mView, mView.isShown());
+ }
+ }
+
+ /**
+ * Base class for derived classes that want to save and restore their own
+ * state in {@link android.view.View#onSaveInstanceState()}.
+ */
+ public static class BaseSavedState extends AbsSavedState {
+ static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1;
+ static final int IS_AUTOFILLED = 0b10;
+ static final int AUTOFILL_ID = 0b100;
+
+ // Flags that describe what data in this state is valid
+ int mSavedData;
+ String mStartActivityRequestWhoSaved;
+ boolean mIsAutofilled;
+ int mAutofillViewId;
+
+ /**
+ * Constructor used when reading from a parcel. Reads the state of the superclass.
+ *
+ * @param source parcel to read from
+ */
+ public BaseSavedState(Parcel source) {
+ this(source, null);
+ }
+
+ /**
+ * Constructor used when reading from a parcel using a given class loader.
+ * Reads the state of the superclass.
+ *
+ * @param source parcel to read from
+ * @param loader ClassLoader to use for reading
+ */
+ public BaseSavedState(Parcel source, ClassLoader loader) {
+ super(source, loader);
+ mSavedData = source.readInt();
+ mStartActivityRequestWhoSaved = source.readString();
+ mIsAutofilled = source.readBoolean();
+ mAutofillViewId = source.readInt();
+ }
+
+ /**
+ * Constructor called by derived classes when creating their SavedState objects
+ *
+ * @param superState The state of the superclass of this view
+ */
+ public BaseSavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+
+ out.writeInt(mSavedData);
+ out.writeString(mStartActivityRequestWhoSaved);
+ out.writeBoolean(mIsAutofilled);
+ out.writeInt(mAutofillViewId);
+ }
+
+ public static final Parcelable.Creator<BaseSavedState> CREATOR
+ = new Parcelable.ClassLoaderCreator<BaseSavedState>() {
+ @Override
+ public BaseSavedState createFromParcel(Parcel in) {
+ return new BaseSavedState(in);
+ }
+
+ @Override
+ public BaseSavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new BaseSavedState(in, loader);
+ }
+
+ @Override
+ public BaseSavedState[] newArray(int size) {
+ return new BaseSavedState[size];
+ }
+ };
+ }
+
+ /**
+ * A set of information given to a view when it is attached to its parent
+ * window.
+ */
+ final static class AttachInfo {
+ interface Callbacks {
+ void playSoundEffect(int effectId);
+ boolean performHapticFeedback(int effectId, boolean always);
+ }
+
+ /**
+ * InvalidateInfo is used to post invalidate(int, int, int, int) messages
+ * to a Handler. This class contains the target (View) to invalidate and
+ * the coordinates of the dirty rectangle.
+ *
+ * For performance purposes, this class also implements a pool of up to
+ * POOL_LIMIT objects that get reused. This reduces memory allocations
+ * whenever possible.
+ */
+ static class InvalidateInfo {
+ private static final int POOL_LIMIT = 10;
+
+ private static final SynchronizedPool<InvalidateInfo> sPool =
+ new SynchronizedPool<InvalidateInfo>(POOL_LIMIT);
+
+ View target;
+
+ int left;
+ int top;
+ int right;
+ int bottom;
+
+ public static InvalidateInfo obtain() {
+ InvalidateInfo instance = sPool.acquire();
+ return (instance != null) ? instance : new InvalidateInfo();
+ }
+
+ public void recycle() {
+ target = null;
+ sPool.release(this);
+ }
+ }
+
+ final IWindowSession mSession;
+
+ final IWindow mWindow;
+
+ final IBinder mWindowToken;
+
+ Display mDisplay;
+
+ final Callbacks mRootCallbacks;
+
+ IWindowId mIWindowId;
+ WindowId mWindowId;
+
+ /**
+ * The top view of the hierarchy.
+ */
+ View mRootView;
+
+ IBinder mPanelParentWindowToken;
+
+ boolean mHardwareAccelerated;
+ boolean mHardwareAccelerationRequested;
+ ThreadedRenderer mThreadedRenderer;
+ List<RenderNode> mPendingAnimatingRenderNodes;
+
+ /**
+ * The state of the display to which the window is attached, as reported
+ * by {@link Display#getState()}. Note that the display state constants
+ * declared by {@link Display} do not exactly line up with the screen state
+ * constants declared by {@link View} (there are more display states than
+ * screen states).
+ */
+ int mDisplayState = Display.STATE_UNKNOWN;
+
+ /**
+ * Scale factor used by the compatibility mode
+ */
+ float mApplicationScale;
+
+ /**
+ * Indicates whether the application is in compatibility mode
+ */
+ boolean mScalingRequired;
+
+ /**
+ * Left position of this view's window
+ */
+ int mWindowLeft;
+
+ /**
+ * Top position of this view's window
+ */
+ int mWindowTop;
+
+ /**
+ * Indicates whether views need to use 32-bit drawing caches
+ */
+ boolean mUse32BitDrawingCache;
+
+ /**
+ * For windows that are full-screen but using insets to layout inside
+ * of the screen areas, these are the current insets to appear inside
+ * the overscan area of the display.
+ */
+ final Rect mOverscanInsets = new Rect();
+
+ /**
+ * For windows that are full-screen but using insets to layout inside
+ * of the screen decorations, these are the current insets for the
+ * content of the window.
+ */
+ final Rect mContentInsets = new Rect();
+
+ /**
+ * For windows that are full-screen but using insets to layout inside
+ * of the screen decorations, these are the current insets for the
+ * actual visible parts of the window.
+ */
+ final Rect mVisibleInsets = new Rect();
+
+ /**
+ * For windows that are full-screen but using insets to layout inside
+ * of the screen decorations, these are the current insets for the
+ * stable system windows.
+ */
+ final Rect mStableInsets = new Rect();
+
+ /**
+ * For windows that include areas that are not covered by real surface these are the outsets
+ * for real surface.
+ */
+ final Rect mOutsets = new Rect();
+
+ /**
+ * In multi-window we force show the navigation bar. Because we don't want that the surface
+ * size changes in this mode, we instead have a flag whether the navigation bar size should
+ * always be consumed, so the app is treated like there is no virtual navigation bar at all.
+ */
+ boolean mAlwaysConsumeNavBar;
+
+ /**
+ * The internal insets given by this window. This value is
+ * supplied by the client (through
+ * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will
+ * be given to the window manager when changed to be used in laying
+ * out windows behind it.
+ */
+ final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets
+ = new ViewTreeObserver.InternalInsetsInfo();
+
+ /**
+ * Set to true when mGivenInternalInsets is non-empty.
+ */
+ boolean mHasNonEmptyGivenInternalInsets;
+
+ /**
+ * All views in the window's hierarchy that serve as scroll containers,
+ * used to determine if the window can be resized or must be panned
+ * to adjust for a soft input area.
+ */
+ final ArrayList<View> mScrollContainers = new ArrayList<View>();
+
+ final KeyEvent.DispatcherState mKeyDispatchState
+ = new KeyEvent.DispatcherState();
+
+ /**
+ * Indicates whether the view's window currently has the focus.
+ */
+ boolean mHasWindowFocus;
+
+ /**
+ * The current visibility of the window.
+ */
+ int mWindowVisibility;
+
+ /**
+ * Indicates the time at which drawing started to occur.
+ */
+ long mDrawingTime;
+
+ /**
+ * Indicates whether or not ignoring the DIRTY_MASK flags.
+ */
+ boolean mIgnoreDirtyState;
+
+ /**
+ * This flag tracks when the mIgnoreDirtyState flag is set during draw(),
+ * to avoid clearing that flag prematurely.
+ */
+ boolean mSetIgnoreDirtyState = false;
+
+ /**
+ * Indicates whether the view's window is currently in touch mode.
+ */
+ boolean mInTouchMode;
+
+ /**
+ * Indicates whether the view has requested unbuffered input dispatching for the current
+ * event stream.
+ */
+ boolean mUnbufferedDispatchRequested;
+
+ /**
+ * Indicates that ViewAncestor should trigger a global layout change
+ * the next time it performs a traversal
+ */
+ boolean mRecomputeGlobalAttributes;
+
+ /**
+ * Always report new attributes at next traversal.
+ */
+ boolean mForceReportNewAttributes;
+
+ /**
+ * Set during a traveral if any views want to keep the screen on.
+ */
+ boolean mKeepScreenOn;
+
+ /**
+ * Set during a traveral if the light center needs to be updated.
+ */
+ boolean mNeedsUpdateLightCenter;
+
+ /**
+ * Bitwise-or of all of the values that views have passed to setSystemUiVisibility().
+ */
+ int mSystemUiVisibility;
+
+ /**
+ * Hack to force certain system UI visibility flags to be cleared.
+ */
+ int mDisabledSystemUiVisibility;
+
+ /**
+ * Last global system UI visibility reported by the window manager.
+ */
+ int mGlobalSystemUiVisibility = -1;
+
+ /**
+ * True if a view in this hierarchy has an OnSystemUiVisibilityChangeListener
+ * attached.
+ */
+ boolean mHasSystemUiListeners;
+
+ /**
+ * Set if the window has requested to extend into the overscan region
+ * via WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN.
+ */
+ boolean mOverscanRequested;
+
+ /**
+ * Set if the visibility of any views has changed.
+ */
+ boolean mViewVisibilityChanged;
+
+ /**
+ * Set to true if a view has been scrolled.
+ */
+ boolean mViewScrollChanged;
+
+ /**
+ * Set to true if a pointer event is currently being handled.
+ */
+ boolean mHandlingPointerEvent;
+
+ /**
+ * Global to the view hierarchy used as a temporary for dealing with
+ * x/y points in the transparent region computations.
+ */
+ final int[] mTransparentLocation = new int[2];
+
+ /**
+ * Global to the view hierarchy used as a temporary for dealing with
+ * x/y points in the ViewGroup.invalidateChild implementation.
+ */
+ final int[] mInvalidateChildLocation = new int[2];
+
+ /**
+ * Global to the view hierarchy used as a temporary for dealing with
+ * computing absolute on-screen location.
+ */
+ final int[] mTmpLocation = new int[2];
+
+ /**
+ * Global to the view hierarchy used as a temporary for dealing with
+ * x/y location when view is transformed.
+ */
+ final float[] mTmpTransformLocation = new float[2];
+
+ /**
+ * The view tree observer used to dispatch global events like
+ * layout, pre-draw, touch mode change, etc.
+ */
+ final ViewTreeObserver mTreeObserver;
+
+ /**
+ * A Canvas used by the view hierarchy to perform bitmap caching.
+ */
+ Canvas mCanvas;
+
+ /**
+ * The view root impl.
+ */
+ final ViewRootImpl mViewRootImpl;
+
+ /**
+ * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
+ * handler can be used to pump events in the UI events queue.
+ */
+ final Handler mHandler;
+
+ /**
+ * Temporary for use in computing invalidate rectangles while
+ * calling up the hierarchy.
+ */
+ final Rect mTmpInvalRect = new Rect();
+
+ /**
+ * Temporary for use in computing hit areas with transformed views
+ */
+ final RectF mTmpTransformRect = new RectF();
+
+ /**
+ * Temporary for use in computing hit areas with transformed views
+ */
+ final RectF mTmpTransformRect1 = new RectF();
+
+ /**
+ * Temporary list of rectanges.
+ */
+ final List<RectF> mTmpRectList = new ArrayList<>();
+
+ /**
+ * Temporary for use in transforming invalidation rect
+ */
+ final Matrix mTmpMatrix = new Matrix();
+
+ /**
+ * Temporary for use in transforming invalidation rect
+ */
+ final Transformation mTmpTransformation = new Transformation();
+
+ /**
+ * Temporary for use in querying outlines from OutlineProviders
+ */
+ final Outline mTmpOutline = new Outline();
+
+ /**
+ * Temporary list for use in collecting focusable descendents of a view.
+ */
+ final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
+
+ /**
+ * The id of the window for accessibility purposes.
+ */
+ int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+
+ /**
+ * Flags related to accessibility processing.
+ *
+ * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
+ */
+ int mAccessibilityFetchFlags;
+
+ /**
+ * The drawable for highlighting accessibility focus.
+ */
+ Drawable mAccessibilityFocusDrawable;
+
+ /**
+ * The drawable for highlighting autofilled views.
+ *
+ * @see #isAutofilled()
+ */
+ Drawable mAutofilledDrawable;
+
+ /**
+ * Show where the margins, bounds and layout bounds are for each view.
+ */
+ boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false);
+
+ /**
+ * Point used to compute visible regions.
+ */
+ final Point mPoint = new Point();
+
+ /**
+ * Used to track which View originated a requestLayout() call, used when
+ * requestLayout() is called during layout.
+ */
+ View mViewRequestingLayout;
+
+ /**
+ * Used to track views that need (at least) a partial relayout at their current size
+ * during the next traversal.
+ */
+ List<View> mPartialLayoutViews = new ArrayList<>();
+
+ /**
+ * Swapped with mPartialLayoutViews during layout to avoid concurrent
+ * modification. Lazily assigned during ViewRootImpl layout.
+ */
+ List<View> mEmptyPartialLayoutViews;
+
+ /**
+ * Used to track the identity of the current drag operation.
+ */
+ IBinder mDragToken;
+
+ /**
+ * The drag shadow surface for the current drag operation.
+ */
+ public Surface mDragSurface;
+
+
+ /**
+ * The view that currently has a tooltip displayed.
+ */
+ View mTooltipHost;
+
+ /**
+ * Creates a new set of attachment information with the specified
+ * events handler and thread.
+ *
+ * @param handler the events handler the view must use
+ */
+ AttachInfo(IWindowSession session, IWindow window, Display display,
+ ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
+ Context context) {
+ mSession = session;
+ mWindow = window;
+ mWindowToken = window.asBinder();
+ mDisplay = display;
+ mViewRootImpl = viewRootImpl;
+ mHandler = handler;
+ mRootCallbacks = effectPlayer;
+ mTreeObserver = new ViewTreeObserver(context);
+ }
+ }
+
+ /**
+ * <p>ScrollabilityCache holds various fields used by a View when scrolling
+ * is supported. This avoids keeping too many unused fields in most
+ * instances of View.</p>
+ */
+ private static class ScrollabilityCache implements Runnable {
+
+ /**
+ * Scrollbars are not visible
+ */
+ public static final int OFF = 0;
+
+ /**
+ * Scrollbars are visible
+ */
+ public static final int ON = 1;
+
+ /**
+ * Scrollbars are fading away
+ */
+ public static final int FADING = 2;
+
+ public boolean fadeScrollBars;
+
+ public int fadingEdgeLength;
+ public int scrollBarDefaultDelayBeforeFade;
+ public int scrollBarFadeDuration;
+
+ public int scrollBarSize;
+ public int scrollBarMinTouchTarget;
+ public ScrollBarDrawable scrollBar;
+ public float[] interpolatorValues;
+ public View host;
+
+ public final Paint paint;
+ public final Matrix matrix;
+ public Shader shader;
+
+ public final Interpolator scrollBarInterpolator = new Interpolator(1, 2);
+
+ private static final float[] OPAQUE = { 255 };
+ private static final float[] TRANSPARENT = { 0.0f };
+
+ /**
+ * When fading should start. This time moves into the future every time
+ * a new scroll happens. Measured based on SystemClock.uptimeMillis()
+ */
+ public long fadeStartTime;
+
+
+ /**
+ * The current state of the scrollbars: ON, OFF, or FADING
+ */
+ public int state = OFF;
+
+ private int mLastColor;
+
+ public final Rect mScrollBarBounds = new Rect();
+ public final Rect mScrollBarTouchBounds = new Rect();
+
+ public static final int NOT_DRAGGING = 0;
+ public static final int DRAGGING_VERTICAL_SCROLL_BAR = 1;
+ public static final int DRAGGING_HORIZONTAL_SCROLL_BAR = 2;
+ public int mScrollBarDraggingState = NOT_DRAGGING;
+
+ public float mScrollBarDraggingPos = 0;
+
+ public ScrollabilityCache(ViewConfiguration configuration, View host) {
+ fadingEdgeLength = configuration.getScaledFadingEdgeLength();
+ scrollBarSize = configuration.getScaledScrollBarSize();
+ scrollBarMinTouchTarget = configuration.getScaledMinScrollbarTouchTarget();
+ scrollBarDefaultDelayBeforeFade = ViewConfiguration.getScrollDefaultDelay();
+ scrollBarFadeDuration = ViewConfiguration.getScrollBarFadeDuration();
+
+ paint = new Paint();
+ matrix = new Matrix();
+ // use use a height of 1, and then wack the matrix each time we
+ // actually use it.
+ shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+ paint.setShader(shader);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+
+ this.host = host;
+ }
+
+ public void setFadeColor(int color) {
+ if (color != mLastColor) {
+ mLastColor = color;
+
+ if (color != 0) {
+ shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,
+ color & 0x00FFFFFF, Shader.TileMode.CLAMP);
+ paint.setShader(shader);
+ // Restore the default transfer mode (src_over)
+ paint.setXfermode(null);
+ } else {
+ shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+ paint.setShader(shader);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ }
+ }
+ }
+
+ public void run() {
+ long now = AnimationUtils.currentAnimationTimeMillis();
+ if (now >= fadeStartTime) {
+
+ // the animation fades the scrollbars out by changing
+ // the opacity (alpha) from fully opaque to fully
+ // transparent
+ int nextFrame = (int) now;
+ int framesCount = 0;
+
+ Interpolator interpolator = scrollBarInterpolator;
+
+ // Start opaque
+ interpolator.setKeyFrame(framesCount++, nextFrame, OPAQUE);
+
+ // End transparent
+ nextFrame += scrollBarFadeDuration;
+ interpolator.setKeyFrame(framesCount, nextFrame, TRANSPARENT);
+
+ state = FADING;
+
+ // Kick off the fade animation
+ host.invalidate(true);
+ }
+ }
+ }
+
+ /**
+ * Resuable callback for sending
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+ */
+ private class SendViewScrolledAccessibilityEvent implements Runnable {
+ public volatile boolean mIsPending;
+ public int mDeltaX;
+ public int mDeltaY;
+
+ public void post(int dx, int dy) {
+ mDeltaX += dx;
+ mDeltaY += dy;
+ if (!mIsPending) {
+ mIsPending = true;
+ postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+ }
+ }
+
+ @Override
+ public void run() {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ event.setScrollDeltaX(mDeltaX);
+ event.setScrollDeltaY(mDeltaY);
+ sendAccessibilityEventUnchecked(event);
+ }
+ reset();
+ }
+
+ private void reset() {
+ mIsPending = false;
+ mDeltaX = 0;
+ mDeltaY = 0;
+ }
+ }
+
+ /**
+ * Remove the pending callback for sending a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+ */
+ private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) {
+ if (callback == null || !callback.mIsPending) return;
+ removeCallbacks(callback);
+ callback.reset();
+ }
+
+ /**
+ * <p>
+ * This class represents a delegate that can be registered in a {@link View}
+ * to enhance accessibility support via composition rather via inheritance.
+ * It is specifically targeted to widget developers that extend basic View
+ * classes i.e. classes in package android.view, that would like their
+ * applications to be backwards compatible.
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about making applications accessible, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ * <p>
+ * A scenario in which a developer would like to use an accessibility delegate
+ * is overriding a method introduced in a later API version than the minimal API
+ * version supported by the application. For example, the method
+ * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} is not available
+ * in API version 4 when the accessibility APIs were first introduced. If a
+ * developer would like his application to run on API version 4 devices (assuming
+ * all other APIs used by the application are version 4 or lower) and take advantage
+ * of this method, instead of overriding the method which would break the application's
+ * backwards compatibility, he can override the corresponding method in this
+ * delegate and register the delegate in the target View if the API version of
+ * the system is high enough, i.e. the API version is the same as or higher than the API
+ * version that introduced
+ * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}.
+ * </p>
+ * <p>
+ * Here is an example implementation:
+ * </p>
+ * <code><pre><p>
+ * if (Build.VERSION.SDK_INT >= 14) {
+ * // If the API version is equal of higher than the version in
+ * // which onInitializeAccessibilityNodeInfo was introduced we
+ * // register a delegate with a customized implementation.
+ * View view = findViewById(R.id.view_id);
+ * view.setAccessibilityDelegate(new AccessibilityDelegate() {
+ * public void onInitializeAccessibilityNodeInfo(View host,
+ * AccessibilityNodeInfo info) {
+ * // Let the default implementation populate the info.
+ * super.onInitializeAccessibilityNodeInfo(host, info);
+ * // Set some other information.
+ * info.setEnabled(host.isEnabled());
+ * }
+ * });
+ * }
+ * </code></pre></p>
+ * <p>
+ * This delegate contains methods that correspond to the accessibility methods
+ * in View. If a delegate has been specified the implementation in View hands
+ * off handling to the corresponding method in this delegate. The default
+ * implementation the delegate methods behaves exactly as the corresponding
+ * method in View for the case of no accessibility delegate been set. Hence,
+ * to customize the behavior of a View method, clients can override only the
+ * corresponding delegate method without altering the behavior of the rest
+ * accessibility related methods of the host view.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfo)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
+ */
+ public static class AccessibilityDelegate {
+
+ /**
+ * Sends an accessibility event of the given type. If accessibility is not
+ * enabled this method has no effect.
+ * <p>
+ * The default implementation behaves as {@link View#sendAccessibilityEvent(int)
+ * View#sendAccessibilityEvent(int)} for the case of no accessibility delegate
+ * been set.
+ * </p>
+ *
+ * @param host The View hosting the delegate.
+ * @param eventType The type of the event to send.
+ *
+ * @see View#sendAccessibilityEvent(int) View#sendAccessibilityEvent(int)
+ */
+ public void sendAccessibilityEvent(View host, int eventType) {
+ host.sendAccessibilityEventInternal(eventType);
+ }
+
+ /**
+ * Performs the specified accessibility action on the view. For
+ * possible accessibility actions look at {@link AccessibilityNodeInfo}.
+ * <p>
+ * The default implementation behaves as
+ * {@link View#performAccessibilityAction(int, Bundle)
+ * View#performAccessibilityAction(int, Bundle)} for the case of
+ * no accessibility delegate been set.
+ * </p>
+ *
+ * @param action The action to perform.
+ * @return Whether the action was performed.
+ *
+ * @see View#performAccessibilityAction(int, Bundle)
+ * View#performAccessibilityAction(int, Bundle)
+ */
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ return host.performAccessibilityActionInternal(action, args);
+ }
+
+ /**
+ * Sends an accessibility event. This method behaves exactly as
+ * {@link #sendAccessibilityEvent(View, int)} but takes as an argument an
+ * empty {@link AccessibilityEvent} and does not perform a check whether
+ * accessibility is enabled.
+ * <p>
+ * The default implementation behaves as
+ * {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)
+ * View#sendAccessibilityEventUnchecked(AccessibilityEvent)} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ *
+ * @param host The View hosting the delegate.
+ * @param event The event to send.
+ *
+ * @see View#sendAccessibilityEventUnchecked(AccessibilityEvent)
+ * View#sendAccessibilityEventUnchecked(AccessibilityEvent)
+ */
+ public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
+ host.sendAccessibilityEventUncheckedInternal(event);
+ }
+
+ /**
+ * Dispatches an {@link AccessibilityEvent} to the host {@link View} first and then
+ * to its children for adding their text content to the event.
+ * <p>
+ * The default implementation behaves as
+ * {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ * View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ *
+ * @param host The View hosting the delegate.
+ * @param event The event.
+ * @return True if the event population was completed.
+ *
+ * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ * View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ */
+ public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+ return host.dispatchPopulateAccessibilityEventInternal(event);
+ }
+
+ /**
+ * Gives a chance to the host View to populate the accessibility event with its
+ * text content.
+ * <p>
+ * The default implementation behaves as
+ * {@link View#onPopulateAccessibilityEvent(AccessibilityEvent)
+ * View#onPopulateAccessibilityEvent(AccessibilityEvent)} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ *
+ * @param host The View hosting the delegate.
+ * @param event The accessibility event which to populate.
+ *
+ * @see View#onPopulateAccessibilityEvent(AccessibilityEvent)
+ * View#onPopulateAccessibilityEvent(AccessibilityEvent)
+ */
+ public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+ host.onPopulateAccessibilityEventInternal(event);
+ }
+
+ /**
+ * Initializes an {@link AccessibilityEvent} with information about the
+ * the host View which is the event source.
+ * <p>
+ * The default implementation behaves as
+ * {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)
+ * View#onInitializeAccessibilityEvent(AccessibilityEvent)} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ *
+ * @param host The View hosting the delegate.
+ * @param event The event to initialize.
+ *
+ * @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
+ * View#onInitializeAccessibilityEvent(AccessibilityEvent)
+ */
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ host.onInitializeAccessibilityEventInternal(event);
+ }
+
+ /**
+ * Initializes an {@link AccessibilityNodeInfo} with information about the host view.
+ * <p>
+ * The default implementation behaves as
+ * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ * View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ *
+ * @param host The View hosting the delegate.
+ * @param info The instance to initialize.
+ *
+ * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ * View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ host.onInitializeAccessibilityNodeInfoInternal(info);
+ }
+
+ /**
+ * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+ * additional data.
+ * <p>
+ * This method only needs to be implemented if the View offers to provide additional data.
+ * </p>
+ * <p>
+ * The default implementation behaves as
+ * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)
+ * for the case where no accessibility delegate is set.
+ * </p>
+ *
+ * @param host The View hosting the delegate. Never {@code null}.
+ * @param info The info to which to add the extra data. Never {@code null}.
+ * @param extraDataKey A key specifying the type of extra data to add to the info. The
+ * extra data should be added to the {@link Bundle} returned by
+ * the info's {@link AccessibilityNodeInfo#getExtras} method. Never
+ * {@code null}.
+ * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+ * May be {@code null} if the if the service provided no arguments.
+ *
+ * @see AccessibilityNodeInfo#setExtraAvailableData
+ */
+ public void addExtraDataToAccessibilityNodeInfo(@NonNull View host,
+ @NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
+ @Nullable Bundle arguments) {
+ host.addExtraDataToAccessibilityNodeInfo(info, extraDataKey, arguments);
+ }
+
+ /**
+ * Called when a child of the host View has requested sending an
+ * {@link AccessibilityEvent} and gives an opportunity to the parent (the host)
+ * to augment the event.
+ * <p>
+ * The default implementation behaves as
+ * {@link ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+ * ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ *
+ * @param host The View hosting the delegate.
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event should be sent
+ *
+ * @see ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+ * ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+ */
+ public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+ AccessibilityEvent event) {
+ return host.onRequestSendAccessibilityEventInternal(child, event);
+ }
+
+ /**
+ * Gets the provider for managing a virtual view hierarchy rooted at this View
+ * and reported to {@link android.accessibilityservice.AccessibilityService}s
+ * that explore the window content.
+ * <p>
+ * The default implementation behaves as
+ * {@link View#getAccessibilityNodeProvider() View#getAccessibilityNodeProvider()} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ *
+ * @return The provider.
+ *
+ * @see AccessibilityNodeProvider
+ */
+ public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+ return null;
+ }
+
+ /**
+ * Returns an {@link AccessibilityNodeInfo} representing the host view from the
+ * point of view of an {@link android.accessibilityservice.AccessibilityService}.
+ * This method is responsible for obtaining an accessibility node info from a
+ * pool of reusable instances and calling
+ * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on the host
+ * view to initialize the former.
+ * <p>
+ * <strong>Note:</strong> The client is responsible for recycling the obtained
+ * instance by calling {@link AccessibilityNodeInfo#recycle()} to minimize object
+ * creation.
+ * </p>
+ * <p>
+ * The default implementation behaves as
+ * {@link View#createAccessibilityNodeInfo() View#createAccessibilityNodeInfo()} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ * @return A populated {@link AccessibilityNodeInfo}.
+ *
+ * @see AccessibilityNodeInfo
+ *
+ * @hide
+ */
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
+ return host.createAccessibilityNodeInfoInternal();
+ }
+ }
+
+ private static class MatchIdPredicate implements Predicate<View> {
+ public int mId;
+
+ @Override
+ public boolean test(View view) {
+ return (view.mID == mId);
+ }
+ }
+
+ private static class MatchLabelForPredicate implements Predicate<View> {
+ private int mLabeledId;
+
+ @Override
+ public boolean test(View view) {
+ return (view.mLabelForId == mLabeledId);
+ }
+ }
+
+ /**
+ * Dump all private flags in readable format, useful for documentation and
+ * sanity checking.
+ */
+ private static void dumpFlags() {
+ final HashMap<String, String> found = Maps.newHashMap();
+ try {
+ for (Field field : View.class.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
+ if (field.getType().equals(int.class)) {
+ final int value = field.getInt(null);
+ dumpFlag(found, field.getName(), value);
+ } else if (field.getType().equals(int[].class)) {
+ final int[] values = (int[]) field.get(null);
+ for (int i = 0; i < values.length; i++) {
+ dumpFlag(found, field.getName() + "[" + i + "]", values[i]);
+ }
+ }
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+
+ final ArrayList<String> keys = Lists.newArrayList();
+ keys.addAll(found.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ Log.d(VIEW_LOG_TAG, found.get(key));
+ }
+ }
+
+ private static void dumpFlag(HashMap<String, String> found, String name, int value) {
+ // Sort flags by prefix, then by bits, always keeping unique keys
+ final String bits = String.format("%32s", Integer.toBinaryString(value)).replace('0', ' ');
+ final int prefix = name.indexOf('_');
+ final String key = (prefix > 0 ? name.substring(0, prefix) : name) + bits + name;
+ final String output = bits + " " + name;
+ found.put(key, output);
+ }
+
+ /** {@hide} */
+ public void encode(@NonNull ViewHierarchyEncoder stream) {
+ stream.beginObject(this);
+ encodeProperties(stream);
+ stream.endObject();
+ }
+
+ /** {@hide} */
+ @CallSuper
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ Object resolveId = ViewDebug.resolveId(getContext(), mID);
+ if (resolveId instanceof String) {
+ stream.addProperty("id", (String) resolveId);
+ } else {
+ stream.addProperty("id", mID);
+ }
+
+ stream.addProperty("misc:transformation.alpha",
+ mTransformationInfo != null ? mTransformationInfo.mAlpha : 0);
+ stream.addProperty("misc:transitionName", getTransitionName());
+
+ // layout
+ stream.addProperty("layout:left", mLeft);
+ stream.addProperty("layout:right", mRight);
+ stream.addProperty("layout:top", mTop);
+ stream.addProperty("layout:bottom", mBottom);
+ stream.addProperty("layout:width", getWidth());
+ stream.addProperty("layout:height", getHeight());
+ stream.addProperty("layout:layoutDirection", getLayoutDirection());
+ stream.addProperty("layout:layoutRtl", isLayoutRtl());
+ stream.addProperty("layout:hasTransientState", hasTransientState());
+ stream.addProperty("layout:baseline", getBaseline());
+
+ // layout params
+ ViewGroup.LayoutParams layoutParams = getLayoutParams();
+ if (layoutParams != null) {
+ stream.addPropertyKey("layoutParams");
+ layoutParams.encode(stream);
+ }
+
+ // scrolling
+ stream.addProperty("scrolling:scrollX", mScrollX);
+ stream.addProperty("scrolling:scrollY", mScrollY);
+
+ // padding
+ stream.addProperty("padding:paddingLeft", mPaddingLeft);
+ stream.addProperty("padding:paddingRight", mPaddingRight);
+ stream.addProperty("padding:paddingTop", mPaddingTop);
+ stream.addProperty("padding:paddingBottom", mPaddingBottom);
+ stream.addProperty("padding:userPaddingRight", mUserPaddingRight);
+ stream.addProperty("padding:userPaddingLeft", mUserPaddingLeft);
+ stream.addProperty("padding:userPaddingBottom", mUserPaddingBottom);
+ stream.addProperty("padding:userPaddingStart", mUserPaddingStart);
+ stream.addProperty("padding:userPaddingEnd", mUserPaddingEnd);
+
+ // measurement
+ stream.addProperty("measurement:minHeight", mMinHeight);
+ stream.addProperty("measurement:minWidth", mMinWidth);
+ stream.addProperty("measurement:measuredWidth", mMeasuredWidth);
+ stream.addProperty("measurement:measuredHeight", mMeasuredHeight);
+
+ // drawing
+ stream.addProperty("drawing:elevation", getElevation());
+ stream.addProperty("drawing:translationX", getTranslationX());
+ stream.addProperty("drawing:translationY", getTranslationY());
+ stream.addProperty("drawing:translationZ", getTranslationZ());
+ stream.addProperty("drawing:rotation", getRotation());
+ stream.addProperty("drawing:rotationX", getRotationX());
+ stream.addProperty("drawing:rotationY", getRotationY());
+ stream.addProperty("drawing:scaleX", getScaleX());
+ stream.addProperty("drawing:scaleY", getScaleY());
+ stream.addProperty("drawing:pivotX", getPivotX());
+ stream.addProperty("drawing:pivotY", getPivotY());
+ stream.addProperty("drawing:opaque", isOpaque());
+ stream.addProperty("drawing:alpha", getAlpha());
+ stream.addProperty("drawing:transitionAlpha", getTransitionAlpha());
+ stream.addProperty("drawing:shadow", hasShadow());
+ stream.addProperty("drawing:solidColor", getSolidColor());
+ stream.addProperty("drawing:layerType", mLayerType);
+ stream.addProperty("drawing:willNotDraw", willNotDraw());
+ stream.addProperty("drawing:hardwareAccelerated", isHardwareAccelerated());
+ stream.addProperty("drawing:willNotCacheDrawing", willNotCacheDrawing());
+ stream.addProperty("drawing:drawingCacheEnabled", isDrawingCacheEnabled());
+ stream.addProperty("drawing:overlappingRendering", hasOverlappingRendering());
+
+ // focus
+ stream.addProperty("focus:hasFocus", hasFocus());
+ stream.addProperty("focus:isFocused", isFocused());
+ stream.addProperty("focus:focusable", getFocusable());
+ stream.addProperty("focus:isFocusable", isFocusable());
+ stream.addProperty("focus:isFocusableInTouchMode", isFocusableInTouchMode());
+
+ stream.addProperty("misc:clickable", isClickable());
+ stream.addProperty("misc:pressed", isPressed());
+ stream.addProperty("misc:selected", isSelected());
+ stream.addProperty("misc:touchMode", isInTouchMode());
+ stream.addProperty("misc:hovered", isHovered());
+ stream.addProperty("misc:activated", isActivated());
+
+ stream.addProperty("misc:visibility", getVisibility());
+ stream.addProperty("misc:fitsSystemWindows", getFitsSystemWindows());
+ stream.addProperty("misc:filterTouchesWhenObscured", getFilterTouchesWhenObscured());
+
+ stream.addProperty("misc:enabled", isEnabled());
+ stream.addProperty("misc:soundEffectsEnabled", isSoundEffectsEnabled());
+ stream.addProperty("misc:hapticFeedbackEnabled", isHapticFeedbackEnabled());
+
+ // theme attributes
+ Resources.Theme theme = getContext().getTheme();
+ if (theme != null) {
+ stream.addPropertyKey("theme");
+ theme.encode(stream);
+ }
+
+ // view attribute information
+ int n = mAttributes != null ? mAttributes.length : 0;
+ stream.addProperty("meta:__attrCount__", n/2);
+ for (int i = 0; i < n; i += 2) {
+ stream.addProperty("meta:__attr__" + mAttributes[i], mAttributes[i+1]);
+ }
+
+ stream.addProperty("misc:scrollBarStyle", getScrollBarStyle());
+
+ // text
+ stream.addProperty("text:textDirection", getTextDirection());
+ stream.addProperty("text:textAlignment", getTextAlignment());
+
+ // accessibility
+ CharSequence contentDescription = getContentDescription();
+ stream.addProperty("accessibility:contentDescription",
+ contentDescription == null ? "" : contentDescription.toString());
+ stream.addProperty("accessibility:labelFor", getLabelFor());
+ stream.addProperty("accessibility:importantForAccessibility", getImportantForAccessibility());
+ }
+
+ /**
+ * Determine if this view is rendered on a round wearable device and is the main view
+ * on the screen.
+ */
+ boolean shouldDrawRoundScrollbar() {
+ if (!mResources.getConfiguration().isScreenRound() || mAttachInfo == null) {
+ return false;
+ }
+
+ final View rootView = getRootView();
+ final WindowInsets insets = getRootWindowInsets();
+
+ int height = getHeight();
+ int width = getWidth();
+ int displayHeight = rootView.getHeight();
+ int displayWidth = rootView.getWidth();
+
+ if (height != displayHeight || width != displayWidth) {
+ return false;
+ }
+
+ getLocationInWindow(mAttachInfo.mTmpLocation);
+ return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
+ && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
+ }
+
+ /**
+ * Sets the tooltip text which will be displayed in a small popup next to the view.
+ * <p>
+ * The tooltip will be displayed:
+ * <ul>
+ * <li>On long click, unless it is handled otherwise (by OnLongClickListener or a context
+ * menu). </li>
+ * <li>On hover, after a brief delay since the pointer has stopped moving </li>
+ * </ul>
+ * <p>
+ * <strong>Note:</strong> Do not override this method, as it will have no
+ * effect on the text displayed in the tooltip.
+ *
+ * @param tooltipText the tooltip text, or null if no tooltip is required
+ * @see #getTooltipText()
+ * @attr ref android.R.styleable#View_tooltipText
+ */
+ public void setTooltipText(@Nullable CharSequence tooltipText) {
+ if (TextUtils.isEmpty(tooltipText)) {
+ setFlags(0, TOOLTIP);
+ hideTooltip();
+ mTooltipInfo = null;
+ } else {
+ setFlags(TOOLTIP, TOOLTIP);
+ if (mTooltipInfo == null) {
+ mTooltipInfo = new TooltipInfo();
+ mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip;
+ mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
+ }
+ mTooltipInfo.mTooltipText = tooltipText;
+ }
+ }
+
+ /**
+ * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+ */
+ public void setTooltip(@Nullable CharSequence tooltipText) {
+ setTooltipText(tooltipText);
+ }
+
+ /**
+ * Returns the view's tooltip text.
+ *
+ * <strong>Note:</strong> Do not override this method, as it will have no
+ * effect on the text displayed in the tooltip. You must call
+ * {@link #setTooltipText(CharSequence)} to modify the tooltip text.
+ *
+ * @return the tooltip text
+ * @see #setTooltipText(CharSequence)
+ * @attr ref android.R.styleable#View_tooltipText
+ */
+ @Nullable
+ public CharSequence getTooltipText() {
+ return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null;
+ }
+
+ /**
+ * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+ */
+ @Nullable
+ public CharSequence getTooltip() {
+ return getTooltipText();
+ }
+
+ private boolean showTooltip(int x, int y, boolean fromLongClick) {
+ if (mAttachInfo == null || mTooltipInfo == null) {
+ return false;
+ }
+ if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+ return false;
+ }
+ if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) {
+ return false;
+ }
+ hideTooltip();
+ mTooltipInfo.mTooltipFromLongClick = fromLongClick;
+ mTooltipInfo.mTooltipPopup = new TooltipPopup(getContext());
+ final boolean fromTouch = (mPrivateFlags3 & PFLAG3_FINGER_DOWN) == PFLAG3_FINGER_DOWN;
+ mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText);
+ mAttachInfo.mTooltipHost = this;
+ return true;
+ }
+
+ void hideTooltip() {
+ if (mTooltipInfo == null) {
+ return;
+ }
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ if (mTooltipInfo.mTooltipPopup == null) {
+ return;
+ }
+ mTooltipInfo.mTooltipPopup.hide();
+ mTooltipInfo.mTooltipPopup = null;
+ mTooltipInfo.mTooltipFromLongClick = false;
+ if (mAttachInfo != null) {
+ mAttachInfo.mTooltipHost = null;
+ }
+ }
+
+ private boolean showLongClickTooltip(int x, int y) {
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+ return showTooltip(x, y, true);
+ }
+
+ private void showHoverTooltip() {
+ showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
+ }
+
+ boolean dispatchTooltipHoverEvent(MotionEvent event) {
+ if (mTooltipInfo == null) {
+ return false;
+ }
+ switch(event.getAction()) {
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+ break;
+ }
+ if (!mTooltipInfo.mTooltipFromLongClick) {
+ if (mTooltipInfo.mTooltipPopup == null) {
+ // Schedule showing the tooltip after a timeout.
+ mTooltipInfo.mAnchorX = (int) event.getX();
+ mTooltipInfo.mAnchorY = (int) event.getY();
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ postDelayed(mTooltipInfo.mShowTooltipRunnable,
+ ViewConfiguration.getHoverTooltipShowTimeout());
+ }
+
+ // Hide hover-triggered tooltip after a period of inactivity.
+ // Match the timeout used by NativeInputManager to hide the mouse pointer
+ // (depends on SYSTEM_UI_FLAG_LOW_PROFILE being set).
+ final int timeout;
+ if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LOW_PROFILE)
+ == SYSTEM_UI_FLAG_LOW_PROFILE) {
+ timeout = ViewConfiguration.getHoverTooltipHideShortTimeout();
+ } else {
+ timeout = ViewConfiguration.getHoverTooltipHideTimeout();
+ }
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+ postDelayed(mTooltipInfo.mHideTooltipRunnable, timeout);
+ }
+ return true;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (!mTooltipInfo.mTooltipFromLongClick) {
+ hideTooltip();
+ }
+ break;
+ }
+ return false;
+ }
+
+ void handleTooltipKey(KeyEvent event) {
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ if (event.getRepeatCount() == 0) {
+ hideTooltip();
+ }
+ break;
+
+ case KeyEvent.ACTION_UP:
+ handleTooltipUp();
+ break;
+ }
+ }
+
+ private void handleTooltipUp() {
+ if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+ return;
+ }
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+ postDelayed(mTooltipInfo.mHideTooltipRunnable,
+ ViewConfiguration.getLongPressTooltipHideTimeout());
+ }
+
+ private int getFocusableAttribute(TypedArray attributes) {
+ TypedValue val = new TypedValue();
+ if (attributes.getValue(com.android.internal.R.styleable.View_focusable, val)) {
+ if (val.type == TypedValue.TYPE_INT_BOOLEAN) {
+ return (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE);
+ } else {
+ return val.data;
+ }
+ } else {
+ return FOCUSABLE_AUTO;
+ }
+ }
+
+ /**
+ * @return The content view of the tooltip popup currently being shown, or null if the tooltip
+ * is not showing.
+ * @hide
+ */
+ @TestApi
+ public View getTooltipView() {
+ if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+ return null;
+ }
+ return mTooltipInfo.mTooltipPopup.getContentView();
+ }
+}
diff --git a/android/view/ViewAnimationUtils.java b/android/view/ViewAnimationUtils.java
new file mode 100644
index 00000000..3e277eb4
--- /dev/null
+++ b/android/view/ViewAnimationUtils.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 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.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.RevealAnimator;
+
+/**
+ * Defines common utilities for working with View's animations.
+ *
+ */
+public final class ViewAnimationUtils {
+ private ViewAnimationUtils() {}
+ /**
+ * Returns an Animator which can animate a clipping circle.
+ * <p>
+ * Any shadow cast by the View will respect the circular clip from this animator.
+ * <p>
+ * Only a single non-rectangular clip can be applied on a View at any time.
+ * Views clipped by a circular reveal animation take priority over
+ * {@link View#setClipToOutline(boolean) View Outline clipping}.
+ * <p>
+ * Note that the animation returned here is a one-shot animation. It cannot
+ * be re-used, and once started it cannot be paused or resumed. It is also
+ * an asynchronous animation that automatically runs off of the UI thread.
+ * As a result {@link AnimatorListener#onAnimationEnd(Animator)}
+ * will occur after the animation has ended, but it may be delayed depending
+ * on thread responsiveness.
+ * <p>
+ * Note that if any start delay is set on the reveal animator, the start radius
+ * will not be applied to the reveal circle until the start delay has passed.
+ * If it's desired to set a start radius on the reveal circle during the start
+ * delay, one workaround could be adding an animator with the same start and
+ * end radius. For example:
+ * <pre><code>
+ * public static Animator createRevealWithDelay(View view, int centerX, int centerY, float startRadius, float endRadius) {
+ * Animator delayAnimator = ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, startRadius);
+ * delayAnimator.setDuration(delayTimeMS);
+ * Animator revealAnimator = ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius);
+ * AnimatorSet set = new AnimatorSet();
+ * set.playSequentially(delayAnimator, revealAnimator);
+ * return set;
+ * }
+ * </code></pre>
+ *
+ * @param view The View will be clipped to the animating circle.
+ * @param centerX The x coordinate of the center of the animating circle, relative to
+ * <code>view</code>.
+ * @param centerY The y coordinate of the center of the animating circle, relative to
+ * <code>view</code>.
+ * @param startRadius The starting radius of the animating circle.
+ * @param endRadius The ending radius of the animating circle.
+ */
+ public static Animator createCircularReveal(View view,
+ int centerX, int centerY, float startRadius, float endRadius) {
+ return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
+ }
+}
diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java
new file mode 100644
index 00000000..574137b3
--- /dev/null
+++ b/android/view/ViewConfiguration.java
@@ -0,0 +1,932 @@
+/*
+ * Copyright (C) 2006 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.TestApi;
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.SparseArray;
+
+/**
+ * Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
+ */
+public class ViewConfiguration {
+ /**
+ * Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
+ * dips
+ */
+ private static final int SCROLL_BAR_SIZE = 4;
+
+ /**
+ * Duration of the fade when scrollbars fade away in milliseconds
+ */
+ private static final int SCROLL_BAR_FADE_DURATION = 250;
+
+ /**
+ * Default delay before the scrollbars fade in milliseconds
+ */
+ private static final int SCROLL_BAR_DEFAULT_DELAY = 300;
+
+ /**
+ * Defines the length of the fading edges in dips
+ */
+ private static final int FADING_EDGE_LENGTH = 12;
+
+ /**
+ * Defines the duration in milliseconds of the pressed state in child
+ * components.
+ */
+ private static final int PRESSED_STATE_DURATION = 64;
+
+ /**
+ * Defines the default duration in milliseconds before a press turns into
+ * a long press
+ */
+ private static final int DEFAULT_LONG_PRESS_TIMEOUT = 500;
+
+ /**
+ * Defines the default duration in milliseconds between the first tap's up event and the second
+ * tap's down event for an interaction to be considered part of the same multi-press.
+ */
+ private static final int DEFAULT_MULTI_PRESS_TIMEOUT = 300;
+
+ /**
+ * Defines the time between successive key repeats in milliseconds.
+ */
+ private static final int KEY_REPEAT_DELAY = 50;
+
+ /**
+ * Defines the duration in milliseconds a user needs to hold down the
+ * appropriate button to bring up the global actions dialog (power off,
+ * lock screen, etc).
+ */
+ private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;
+
+ /**
+ * Defines the duration in milliseconds a user needs to hold down the
+ * appropriate button to bring up the accessibility shortcut (first time) or enable it
+ * (once shortcut is configured).
+ */
+ private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
+
+ /**
+ * Defines the duration in milliseconds we will wait to see if a touch event
+ * is a tap or a scroll. If the user does not move within this interval, it is
+ * considered to be a tap.
+ */
+ private static final int TAP_TIMEOUT = 100;
+
+ /**
+ * Defines the duration in milliseconds we will wait to see if a touch event
+ * is a jump tap. If the user does not complete the jump tap within this interval, it is
+ * considered to be a tap.
+ */
+ private static final int JUMP_TAP_TIMEOUT = 500;
+
+ /**
+ * Defines the duration in milliseconds between the first tap's up event and
+ * the second tap's down event for an interaction to be considered a
+ * double-tap.
+ */
+ private static final int DOUBLE_TAP_TIMEOUT = 300;
+
+ /**
+ * Defines the minimum duration in milliseconds between the first tap's up event and
+ * the second tap's down event for an interaction to be considered a
+ * double-tap.
+ */
+ private static final int DOUBLE_TAP_MIN_TIME = 40;
+
+ /**
+ * Defines the maximum duration in milliseconds between a touch pad
+ * touch and release for a given touch to be considered a tap (click) as
+ * opposed to a hover movement gesture.
+ */
+ private static final int HOVER_TAP_TIMEOUT = 150;
+
+ /**
+ * Defines the maximum distance in pixels that a touch pad touch can move
+ * before being released for it to be considered a tap (click) as opposed
+ * to a hover movement gesture.
+ */
+ private static final int HOVER_TAP_SLOP = 20;
+
+ /**
+ * Defines the duration in milliseconds we want to display zoom controls in response
+ * to a user panning within an application.
+ */
+ private static final int ZOOM_CONTROLS_TIMEOUT = 3000;
+
+ /**
+ * Inset in dips to look for touchable content when the user touches the edge of the screen
+ */
+ private static final int EDGE_SLOP = 12;
+
+ /**
+ * Distance a touch can wander before we think the user is scrolling in dips.
+ * Note that this value defined here is only used as a fallback by legacy/misbehaving
+ * applications that do not provide a Context for determining density/configuration-dependent
+ * values.
+ *
+ * To alter this value, see the configuration resource config_viewConfigurationTouchSlop
+ * in frameworks/base/core/res/res/values/config.xml or the appropriate device resource overlay.
+ * It may be appropriate to tweak this on a device-specific basis in an overlay based on
+ * the characteristics of the touch panel and firmware.
+ */
+ private static final int TOUCH_SLOP = 8;
+
+ /**
+ * Defines the minimum size of the touch target for a scrollbar in dips
+ */
+ private static final int MIN_SCROLLBAR_TOUCH_TARGET = 48;
+
+ /**
+ * Distance the first touch can wander before we stop considering this event a double tap
+ * (in dips)
+ */
+ private static final int DOUBLE_TAP_TOUCH_SLOP = TOUCH_SLOP;
+
+ /**
+ * Distance a touch can wander before we think the user is attempting a paged scroll
+ * (in dips)
+ *
+ * Note that this value defined here is only used as a fallback by legacy/misbehaving
+ * applications that do not provide a Context for determining density/configuration-dependent
+ * values.
+ *
+ * See the note above on {@link #TOUCH_SLOP} regarding the dimen resource
+ * config_viewConfigurationTouchSlop. ViewConfiguration will report a paging touch slop of
+ * config_viewConfigurationTouchSlop * 2 when provided with a Context.
+ */
+ private static final int PAGING_TOUCH_SLOP = TOUCH_SLOP * 2;
+
+ /**
+ * Distance in dips between the first touch and second touch to still be considered a double tap
+ */
+ private static final int DOUBLE_TAP_SLOP = 100;
+
+ /**
+ * Distance in dips a touch needs to be outside of a window's bounds for it to
+ * count as outside for purposes of dismissing the window.
+ */
+ private static final int WINDOW_TOUCH_SLOP = 16;
+
+ /**
+ * Minimum velocity to initiate a fling, as measured in dips per second
+ */
+ private static final int MINIMUM_FLING_VELOCITY = 50;
+
+ /**
+ * Maximum velocity to initiate a fling, as measured in dips per second
+ */
+ private static final int MAXIMUM_FLING_VELOCITY = 8000;
+
+ /**
+ * Delay before dispatching a recurring accessibility event in milliseconds.
+ * This delay guarantees that a recurring event will be send at most once
+ * during the {@link #SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS} time
+ * frame.
+ */
+ private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 100;
+
+ /**
+ * The maximum size of View's drawing cache, expressed in bytes. This size
+ * should be at least equal to the size of the screen in ARGB888 format.
+ */
+ @Deprecated
+ private static final int MAXIMUM_DRAWING_CACHE_SIZE = 480 * 800 * 4; // ARGB8888
+
+ /**
+ * The coefficient of friction applied to flings/scrolls.
+ */
+ private static final float SCROLL_FRICTION = 0.015f;
+
+ /**
+ * Max distance in dips to overscroll for edge effects
+ */
+ private static final int OVERSCROLL_DISTANCE = 0;
+
+ /**
+ * Max distance in dips to overfling for edge effects
+ */
+ private static final int OVERFLING_DISTANCE = 6;
+
+ /**
+ * Amount to scroll in response to a horizontal {@link MotionEvent#ACTION_SCROLL} event,
+ * in dips per axis value.
+ */
+ private static final float HORIZONTAL_SCROLL_FACTOR = 64;
+
+ /**
+ * Amount to scroll in response to a vertical {@link MotionEvent#ACTION_SCROLL} event,
+ * in dips per axis value.
+ */
+ private static final float VERTICAL_SCROLL_FACTOR = 64;
+
+ /**
+ * Default duration to hide an action mode for.
+ */
+ private static final long ACTION_MODE_HIDE_DURATION_DEFAULT = 2000;
+
+ /**
+ * Defines the duration in milliseconds before an end of a long press causes a tooltip to be
+ * hidden.
+ */
+ private static final int LONG_PRESS_TOOLTIP_HIDE_TIMEOUT = 1500;
+
+ /**
+ * Defines the duration in milliseconds before a hover event causes a tooltip to be shown.
+ */
+ private static final int HOVER_TOOLTIP_SHOW_TIMEOUT = 500;
+
+ /**
+ * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden.
+ * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+ */
+ private static final int HOVER_TOOLTIP_HIDE_TIMEOUT = 15000;
+
+ /**
+ * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+ * (short version to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+ */
+ private static final int HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT = 3000;
+
+ /**
+ * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
+ * These constants must match the definition in res/values/config.xml.
+ */
+ private static final int HAS_PERMANENT_MENU_KEY_AUTODETECT = 0;
+ private static final int HAS_PERMANENT_MENU_KEY_TRUE = 1;
+ private static final int HAS_PERMANENT_MENU_KEY_FALSE = 2;
+
+ private final int mEdgeSlop;
+ private final int mFadingEdgeLength;
+ private final int mMinimumFlingVelocity;
+ private final int mMaximumFlingVelocity;
+ private final int mScrollbarSize;
+ private final int mTouchSlop;
+ private final int mMinScrollbarTouchTarget;
+ private final int mDoubleTapTouchSlop;
+ private final int mPagingTouchSlop;
+ private final int mDoubleTapSlop;
+ private final int mWindowTouchSlop;
+ private final int mMaximumDrawingCacheSize;
+ private final int mOverscrollDistance;
+ private final int mOverflingDistance;
+ private final boolean mFadingMarqueeEnabled;
+ private final long mGlobalActionsKeyTimeout;
+ private final float mVerticalScrollFactor;
+ private final float mHorizontalScrollFactor;
+
+ private boolean sHasPermanentMenuKey;
+ private boolean sHasPermanentMenuKeySet;
+
+ static final SparseArray<ViewConfiguration> sConfigurations =
+ new SparseArray<ViewConfiguration>(2);
+
+ /**
+ * @deprecated Use {@link android.view.ViewConfiguration#get(android.content.Context)} instead.
+ */
+ @Deprecated
+ public ViewConfiguration() {
+ mEdgeSlop = EDGE_SLOP;
+ mFadingEdgeLength = FADING_EDGE_LENGTH;
+ mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
+ mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
+ mScrollbarSize = SCROLL_BAR_SIZE;
+ mTouchSlop = TOUCH_SLOP;
+ mMinScrollbarTouchTarget = MIN_SCROLLBAR_TOUCH_TARGET;
+ mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
+ mPagingTouchSlop = PAGING_TOUCH_SLOP;
+ mDoubleTapSlop = DOUBLE_TAP_SLOP;
+ mWindowTouchSlop = WINDOW_TOUCH_SLOP;
+ //noinspection deprecation
+ mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
+ mOverscrollDistance = OVERSCROLL_DISTANCE;
+ mOverflingDistance = OVERFLING_DISTANCE;
+ mFadingMarqueeEnabled = true;
+ mGlobalActionsKeyTimeout = GLOBAL_ACTIONS_KEY_TIMEOUT;
+ mHorizontalScrollFactor = HORIZONTAL_SCROLL_FACTOR;
+ mVerticalScrollFactor = VERTICAL_SCROLL_FACTOR;
+ }
+
+ /**
+ * Creates a new configuration for the specified context. The configuration depends on
+ * various parameters of the context, like the dimension of the display or the density
+ * of the display.
+ *
+ * @param context The application context used to initialize this view configuration.
+ *
+ * @see #get(android.content.Context)
+ * @see android.util.DisplayMetrics
+ */
+ private ViewConfiguration(Context context) {
+ final Resources res = context.getResources();
+ final DisplayMetrics metrics = res.getDisplayMetrics();
+ final Configuration config = res.getConfiguration();
+ final float density = metrics.density;
+ final float sizeAndDensity;
+ if (config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_XLARGE)) {
+ sizeAndDensity = density * 1.5f;
+ } else {
+ sizeAndDensity = density;
+ }
+
+ mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
+ mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
+ mScrollbarSize = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_scrollbarSize);
+ mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
+ mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
+
+ // Size of the screen in bytes, in ARGB_8888 format
+ final WindowManager win = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ final Display display = win.getDefaultDisplay();
+ final Point size = new Point();
+ display.getRealSize(size);
+ mMaximumDrawingCacheSize = 4 * size.x * size.y;
+
+ mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f);
+ mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
+
+ if (!sHasPermanentMenuKeySet) {
+ final int configVal = res.getInteger(
+ com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
+
+ switch (configVal) {
+ default:
+ case HAS_PERMANENT_MENU_KEY_AUTODETECT: {
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ sHasPermanentMenuKey = !wm.hasNavigationBar();
+ sHasPermanentMenuKeySet = true;
+ } catch (RemoteException ex) {
+ sHasPermanentMenuKey = false;
+ }
+ }
+ break;
+
+ case HAS_PERMANENT_MENU_KEY_TRUE:
+ sHasPermanentMenuKey = true;
+ sHasPermanentMenuKeySet = true;
+ break;
+
+ case HAS_PERMANENT_MENU_KEY_FALSE:
+ sHasPermanentMenuKey = false;
+ sHasPermanentMenuKeySet = true;
+ break;
+ }
+ }
+
+ mFadingMarqueeEnabled = res.getBoolean(
+ com.android.internal.R.bool.config_ui_enableFadingMarquee);
+ mTouchSlop = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
+ mMinScrollbarTouchTarget = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_minScrollbarTouchTarget);
+ mPagingTouchSlop = mTouchSlop * 2;
+
+ mDoubleTapTouchSlop = mTouchSlop;
+
+ mMinimumFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMinFlingVelocity);
+ mMaximumFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMaxFlingVelocity);
+ mGlobalActionsKeyTimeout = res.getInteger(
+ com.android.internal.R.integer.config_globalActionsKeyTimeout);
+
+ mHorizontalScrollFactor = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_horizontalScrollFactor);
+ mVerticalScrollFactor = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_verticalScrollFactor);
+ }
+
+ /**
+ * Returns a configuration for the specified context. The configuration depends on
+ * various parameters of the context, like the dimension of the display or the
+ * density of the display.
+ *
+ * @param context The application context used to initialize the view configuration.
+ */
+ public static ViewConfiguration get(Context context) {
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ final int density = (int) (100.0f * metrics.density);
+
+ ViewConfiguration configuration = sConfigurations.get(density);
+ if (configuration == null) {
+ configuration = new ViewConfiguration(context);
+ sConfigurations.put(density, configuration);
+ }
+
+ return configuration;
+ }
+
+ /**
+ * @return The width of the horizontal scrollbar and the height of the vertical
+ * scrollbar in dips
+ *
+ * @deprecated Use {@link #getScaledScrollBarSize()} instead.
+ */
+ @Deprecated
+ public static int getScrollBarSize() {
+ return SCROLL_BAR_SIZE;
+ }
+
+ /**
+ * @return The width of the horizontal scrollbar and the height of the vertical
+ * scrollbar in pixels
+ */
+ public int getScaledScrollBarSize() {
+ return mScrollbarSize;
+ }
+
+ /**
+ * @return the minimum size of the scrollbar thumb's touch target in pixels
+ * @hide
+ */
+ public int getScaledMinScrollbarTouchTarget() {
+ return mMinScrollbarTouchTarget;
+ }
+
+ /**
+ * @return Duration of the fade when scrollbars fade away in milliseconds
+ */
+ public static int getScrollBarFadeDuration() {
+ return SCROLL_BAR_FADE_DURATION;
+ }
+
+ /**
+ * @return Default delay before the scrollbars fade in milliseconds
+ */
+ public static int getScrollDefaultDelay() {
+ return SCROLL_BAR_DEFAULT_DELAY;
+ }
+
+ /**
+ * @return the length of the fading edges in dips
+ *
+ * @deprecated Use {@link #getScaledFadingEdgeLength()} instead.
+ */
+ @Deprecated
+ public static int getFadingEdgeLength() {
+ return FADING_EDGE_LENGTH;
+ }
+
+ /**
+ * @return the length of the fading edges in pixels
+ */
+ public int getScaledFadingEdgeLength() {
+ return mFadingEdgeLength;
+ }
+
+ /**
+ * @return the duration in milliseconds of the pressed state in child
+ * components.
+ */
+ public static int getPressedStateDuration() {
+ return PRESSED_STATE_DURATION;
+ }
+
+ /**
+ * @return the duration in milliseconds before a press turns into
+ * a long press
+ */
+ public static int getLongPressTimeout() {
+ return AppGlobals.getIntCoreSetting(Settings.Secure.LONG_PRESS_TIMEOUT,
+ DEFAULT_LONG_PRESS_TIMEOUT);
+ }
+
+ /**
+ * @return the duration in milliseconds between the first tap's up event and the second tap's
+ * down event for an interaction to be considered part of the same multi-press.
+ * @hide
+ */
+ public static int getMultiPressTimeout() {
+ return AppGlobals.getIntCoreSetting(Settings.Secure.MULTI_PRESS_TIMEOUT,
+ DEFAULT_MULTI_PRESS_TIMEOUT);
+ }
+
+ /**
+ * @return the time before the first key repeat in milliseconds.
+ */
+ public static int getKeyRepeatTimeout() {
+ return getLongPressTimeout();
+ }
+
+ /**
+ * @return the time between successive key repeats in milliseconds.
+ */
+ public static int getKeyRepeatDelay() {
+ return KEY_REPEAT_DELAY;
+ }
+
+ /**
+ * @return the duration in milliseconds we will wait to see if a touch event
+ * is a tap or a scroll. If the user does not move within this interval, it is
+ * considered to be a tap.
+ */
+ public static int getTapTimeout() {
+ return TAP_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds we will wait to see if a touch event
+ * is a jump tap. If the user does not move within this interval, it is
+ * considered to be a tap.
+ */
+ public static int getJumpTapTimeout() {
+ return JUMP_TAP_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds between the first tap's up event and
+ * the second tap's down event for an interaction to be considered a
+ * double-tap.
+ */
+ public static int getDoubleTapTimeout() {
+ return DOUBLE_TAP_TIMEOUT;
+ }
+
+ /**
+ * @return the minimum duration in milliseconds between the first tap's
+ * up event and the second tap's down event for an interaction to be considered a
+ * double-tap.
+ *
+ * @hide
+ */
+ public static int getDoubleTapMinTime() {
+ return DOUBLE_TAP_MIN_TIME;
+ }
+
+ /**
+ * @return the maximum duration in milliseconds between a touch pad
+ * touch and release for a given touch to be considered a tap (click) as
+ * opposed to a hover movement gesture.
+ * @hide
+ */
+ public static int getHoverTapTimeout() {
+ return HOVER_TAP_TIMEOUT;
+ }
+
+ /**
+ * @return the maximum distance in pixels that a touch pad touch can move
+ * before being released for it to be considered a tap (click) as opposed
+ * to a hover movement gesture.
+ * @hide
+ */
+ public static int getHoverTapSlop() {
+ return HOVER_TAP_SLOP;
+ }
+
+ /**
+ * @return Inset in dips to look for touchable content when the user touches the edge of the
+ * screen
+ *
+ * @deprecated Use {@link #getScaledEdgeSlop()} instead.
+ */
+ @Deprecated
+ public static int getEdgeSlop() {
+ return EDGE_SLOP;
+ }
+
+ /**
+ * @return Inset in pixels to look for touchable content when the user touches the edge of the
+ * screen
+ */
+ public int getScaledEdgeSlop() {
+ return mEdgeSlop;
+ }
+
+ /**
+ * @return Distance in dips a touch can wander before we think the user is scrolling
+ *
+ * @deprecated Use {@link #getScaledTouchSlop()} instead.
+ */
+ @Deprecated
+ public static int getTouchSlop() {
+ return TOUCH_SLOP;
+ }
+
+ /**
+ * @return Distance in pixels a touch can wander before we think the user is scrolling
+ */
+ public int getScaledTouchSlop() {
+ return mTouchSlop;
+ }
+
+ /**
+ * @return Distance in pixels the first touch can wander before we do not consider this a
+ * potential double tap event
+ * @hide
+ */
+ public int getScaledDoubleTapTouchSlop() {
+ return mDoubleTapTouchSlop;
+ }
+
+ /**
+ * @return Distance in pixels a touch can wander before we think the user is scrolling a full
+ * page
+ */
+ public int getScaledPagingTouchSlop() {
+ return mPagingTouchSlop;
+ }
+
+ /**
+ * @return Distance in dips between the first touch and second touch to still be
+ * considered a double tap
+ * @deprecated Use {@link #getScaledDoubleTapSlop()} instead.
+ * @hide The only client of this should be GestureDetector, which needs this
+ * for clients that still use its deprecated constructor.
+ */
+ @Deprecated
+ public static int getDoubleTapSlop() {
+ return DOUBLE_TAP_SLOP;
+ }
+
+ /**
+ * @return Distance in pixels between the first touch and second touch to still be
+ * considered a double tap
+ */
+ public int getScaledDoubleTapSlop() {
+ return mDoubleTapSlop;
+ }
+
+ /**
+ * Interval for dispatching a recurring accessibility event in milliseconds.
+ * This interval guarantees that a recurring event will be send at most once
+ * during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame.
+ *
+ * @return The delay in milliseconds.
+ *
+ * @hide
+ */
+ public static long getSendRecurringAccessibilityEventsInterval() {
+ return SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS;
+ }
+
+ /**
+ * @return Distance in dips a touch must be outside the bounds of a window for it
+ * to be counted as outside the window for purposes of dismissing that
+ * window.
+ *
+ * @deprecated Use {@link #getScaledWindowTouchSlop()} instead.
+ */
+ @Deprecated
+ public static int getWindowTouchSlop() {
+ return WINDOW_TOUCH_SLOP;
+ }
+
+ /**
+ * @return Distance in pixels a touch must be outside the bounds of a window for it
+ * to be counted as outside the window for purposes of dismissing that window.
+ */
+ public int getScaledWindowTouchSlop() {
+ return mWindowTouchSlop;
+ }
+
+ /**
+ * @return Minimum velocity to initiate a fling, as measured in dips per second.
+ *
+ * @deprecated Use {@link #getScaledMinimumFlingVelocity()} instead.
+ */
+ @Deprecated
+ public static int getMinimumFlingVelocity() {
+ return MINIMUM_FLING_VELOCITY;
+ }
+
+ /**
+ * @return Minimum velocity to initiate a fling, as measured in pixels per second.
+ */
+ public int getScaledMinimumFlingVelocity() {
+ return mMinimumFlingVelocity;
+ }
+
+ /**
+ * @return Maximum velocity to initiate a fling, as measured in dips per second.
+ *
+ * @deprecated Use {@link #getScaledMaximumFlingVelocity()} instead.
+ */
+ @Deprecated
+ public static int getMaximumFlingVelocity() {
+ return MAXIMUM_FLING_VELOCITY;
+ }
+
+ /**
+ * @return Maximum velocity to initiate a fling, as measured in pixels per second.
+ */
+ public int getScaledMaximumFlingVelocity() {
+ return mMaximumFlingVelocity;
+ }
+
+ /**
+ * @return Amount to scroll in response to a {@link MotionEvent#ACTION_SCROLL} event. Multiply
+ * this by the event's axis value to obtain the number of pixels to be scrolled.
+ *
+ * @removed
+ */
+ public int getScaledScrollFactor() {
+ return (int) mVerticalScrollFactor;
+ }
+
+ /**
+ * @return Amount to scroll in response to a horizontal {@link MotionEvent#ACTION_SCROLL} event.
+ * Multiply this by the event's axis value to obtain the number of pixels to be scrolled.
+ */
+ public float getScaledHorizontalScrollFactor() {
+ return mHorizontalScrollFactor;
+ }
+
+ /**
+ * @return Amount to scroll in response to a vertical {@link MotionEvent#ACTION_SCROLL} event.
+ * Multiply this by the event's axis value to obtain the number of pixels to be scrolled.
+ */
+ public float getScaledVerticalScrollFactor() {
+ return mVerticalScrollFactor;
+ }
+
+ /**
+ * The maximum drawing cache size expressed in bytes.
+ *
+ * @return the maximum size of View's drawing cache expressed in bytes
+ *
+ * @deprecated Use {@link #getScaledMaximumDrawingCacheSize()} instead.
+ */
+ @Deprecated
+ public static int getMaximumDrawingCacheSize() {
+ //noinspection deprecation
+ return MAXIMUM_DRAWING_CACHE_SIZE;
+ }
+
+ /**
+ * The maximum drawing cache size expressed in bytes.
+ *
+ * @return the maximum size of View's drawing cache expressed in bytes
+ */
+ public int getScaledMaximumDrawingCacheSize() {
+ return mMaximumDrawingCacheSize;
+ }
+
+ /**
+ * @return The maximum distance a View should overscroll by when showing edge effects (in
+ * pixels).
+ */
+ public int getScaledOverscrollDistance() {
+ return mOverscrollDistance;
+ }
+
+ /**
+ * @return The maximum distance a View should overfling by when showing edge effects (in
+ * pixels).
+ */
+ public int getScaledOverflingDistance() {
+ return mOverflingDistance;
+ }
+
+ /**
+ * The amount of time that the zoom controls should be
+ * displayed on the screen expressed in milliseconds.
+ *
+ * @return the time the zoom controls should be visible expressed
+ * in milliseconds.
+ */
+ public static long getZoomControlsTimeout() {
+ return ZOOM_CONTROLS_TIMEOUT;
+ }
+
+ /**
+ * The amount of time a user needs to press the relevant key to bring up
+ * the global actions dialog.
+ *
+ * @return how long a user needs to press the relevant key to bring up
+ * the global actions dialog.
+ * @deprecated This timeout should not be used by applications
+ */
+ @Deprecated
+ public static long getGlobalActionKeyTimeout() {
+ return GLOBAL_ACTIONS_KEY_TIMEOUT;
+ }
+
+ /**
+ * The amount of time a user needs to press the relevant key to bring up
+ * the global actions dialog.
+ *
+ * @return how long a user needs to press the relevant key to bring up
+ * the global actions dialog.
+ * @hide
+ */
+ public long getDeviceGlobalActionKeyTimeout() {
+ return mGlobalActionsKeyTimeout;
+ }
+
+ /**
+ * The amount of time a user needs to press the relevant keys to activate the accessibility
+ * shortcut.
+ *
+ * @return how long a user needs to press the relevant keys to activate the accessibility
+ * shortcut.
+ * @hide
+ */
+ public long getAccessibilityShortcutKeyTimeout() {
+ return A11Y_SHORTCUT_KEY_TIMEOUT;
+ }
+
+ /**
+ * The amount of friction applied to scrolls and flings.
+ *
+ * @return A scalar dimensionless value representing the coefficient of
+ * friction.
+ */
+ public static float getScrollFriction() {
+ return SCROLL_FRICTION;
+ }
+
+ /**
+ * @return the default duration in milliseconds for {@link ActionMode#hide(long)}.
+ */
+ public static long getDefaultActionModeHideDuration() {
+ return ACTION_MODE_HIDE_DURATION_DEFAULT;
+ }
+
+ /**
+ * Report if the device has a permanent menu key available to the user.
+ *
+ * <p>As of Android 3.0, devices may not have a permanent menu key available.
+ * Apps should use the action bar to present menu options to users.
+ * However, there are some apps where the action bar is inappropriate
+ * or undesirable. This method may be used to detect if a menu key is present.
+ * If not, applications should provide another on-screen affordance to access
+ * functionality.
+ *
+ * @return true if a permanent menu key is present, false otherwise.
+ */
+ public boolean hasPermanentMenuKey() {
+ return sHasPermanentMenuKey;
+ }
+
+ /**
+ * @hide
+ * @return Whether or not marquee should use fading edges.
+ */
+ public boolean isFadingMarqueeEnabled() {
+ return mFadingMarqueeEnabled;
+ }
+
+ /**
+ * @return the duration in milliseconds before an end of a long press causes a tooltip to be
+ * hidden
+ * @hide
+ */
+ @TestApi
+ public static int getLongPressTooltipHideTimeout() {
+ return LONG_PRESS_TOOLTIP_HIDE_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds before a hover event causes a tooltip to be shown
+ * @hide
+ */
+ @TestApi
+ public static int getHoverTooltipShowTimeout() {
+ return HOVER_TOOLTIP_SHOW_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+ * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+ * @hide
+ */
+ @TestApi
+ public static int getHoverTooltipHideTimeout() {
+ return HOVER_TOOLTIP_HIDE_TIMEOUT;
+ }
+
+ /**
+ * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+ * (shorter variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+ * @hide
+ */
+ @TestApi
+ public static int getHoverTooltipHideShortTimeout() {
+ return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
+ }
+}
diff --git a/android/view/ViewConfiguration_Accessor.java b/android/view/ViewConfiguration_Accessor.java
new file mode 100644
index 00000000..c3533e01
--- /dev/null
+++ b/android/view/ViewConfiguration_Accessor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class ViewConfiguration_Accessor {
+
+ public static void clearConfigurations() {
+ // clear the stored ViewConfiguration since the map is per density and not per context.
+ ViewConfiguration.sConfigurations.clear();
+ }
+
+}
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
new file mode 100644
index 00000000..66c05785
--- /dev/null
+++ b/android/view/ViewDebug.java
@@ -0,0 +1,1677 @@
+/*
+ * Copyright (C) 2007 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.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Various debugging/tracing tools related to {@link View} and the view hierarchy.
+ */
+public class ViewDebug {
+ /**
+ * @deprecated This flag is now unused
+ */
+ @Deprecated
+ public static final boolean TRACE_HIERARCHY = false;
+
+ /**
+ * @deprecated This flag is now unused
+ */
+ @Deprecated
+ public static final boolean TRACE_RECYCLER = false;
+
+ /**
+ * Enables detailed logging of drag/drop operations.
+ * @hide
+ */
+ public static final boolean DEBUG_DRAG = false;
+
+ /**
+ * Enables detailed logging of task positioning operations.
+ * @hide
+ */
+ public static final boolean DEBUG_POSITIONING = false;
+
+ /**
+ * This annotation can be used to mark fields and methods to be dumped by
+ * the view server. Only non-void methods with no arguments can be annotated
+ * by this annotation.
+ */
+ @Target({ ElementType.FIELD, ElementType.METHOD })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ExportedProperty {
+ /**
+ * When resolveId is true, and if the annotated field/method return value
+ * is an int, the value is converted to an Android's resource name.
+ *
+ * @return true if the property's value must be transformed into an Android
+ * resource name, false otherwise
+ */
+ boolean resolveId() default false;
+
+ /**
+ * A mapping can be defined to map int values to specific strings. For
+ * instance, View.getVisibility() returns 0, 4 or 8. However, these values
+ * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
+ * these human readable values:
+ *
+ * <pre>
+ * {@literal @}ViewDebug.ExportedProperty(mapping = {
+ * {@literal @}ViewDebug.IntToString(from = 0, to = "VISIBLE"),
+ * {@literal @}ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
+ * {@literal @}ViewDebug.IntToString(from = 8, to = "GONE")
+ * })
+ * public int getVisibility() { ...
+ * <pre>
+ *
+ * @return An array of int to String mappings
+ *
+ * @see android.view.ViewDebug.IntToString
+ */
+ IntToString[] mapping() default { };
+
+ /**
+ * A mapping can be defined to map array indices to specific strings.
+ * A mapping can be used to see human readable values for the indices
+ * of an array:
+ *
+ * <pre>
+ * {@literal @}ViewDebug.ExportedProperty(indexMapping = {
+ * {@literal @}ViewDebug.IntToString(from = 0, to = "INVALID"),
+ * {@literal @}ViewDebug.IntToString(from = 1, to = "FIRST"),
+ * {@literal @}ViewDebug.IntToString(from = 2, to = "SECOND")
+ * })
+ * private int[] mElements;
+ * <pre>
+ *
+ * @return An array of int to String mappings
+ *
+ * @see android.view.ViewDebug.IntToString
+ * @see #mapping()
+ */
+ IntToString[] indexMapping() default { };
+
+ /**
+ * A flags mapping can be defined to map flags encoded in an integer to
+ * specific strings. A mapping can be used to see human readable values
+ * for the flags of an integer:
+ *
+ * <pre>
+ * {@literal @}ViewDebug.ExportedProperty(flagMapping = {
+ * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED,
+ * name = "ENABLED"),
+ * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED,
+ * name = "DISABLED"),
+ * })
+ * private int mFlags;
+ * <pre>
+ *
+ * A specified String is output when the following is true:
+ *
+ * @return An array of int to String mappings
+ */
+ FlagToString[] flagMapping() default { };
+
+ /**
+ * When deep export is turned on, this property is not dumped. Instead, the
+ * properties contained in this property are dumped. Each child property
+ * is prefixed with the name of this property.
+ *
+ * @return true if the properties of this property should be dumped
+ *
+ * @see #prefix()
+ */
+ boolean deepExport() default false;
+
+ /**
+ * The prefix to use on child properties when deep export is enabled
+ *
+ * @return a prefix as a String
+ *
+ * @see #deepExport()
+ */
+ String prefix() default "";
+
+ /**
+ * Specifies the category the property falls into, such as measurement,
+ * layout, drawing, etc.
+ *
+ * @return the category as String
+ */
+ String category() default "";
+
+ /**
+ * Indicates whether or not to format an {@code int} or {@code byte} value as a hex string.
+ *
+ * @return true if the supported values should be formatted as a hex string.
+ */
+ boolean formatToHexString() default false;
+
+ /**
+ * Indicates whether or not the key to value mappings are held in adjacent indices.
+ *
+ * Note: Applies only to fields and methods that return String[].
+ *
+ * @return true if the key to value mappings are held in adjacent indices.
+ */
+ boolean hasAdjacentMapping() default false;
+ }
+
+ /**
+ * Defines a mapping from an int value to a String. Such a mapping can be used
+ * in an @ExportedProperty to provide more meaningful values to the end user.
+ *
+ * @see android.view.ViewDebug.ExportedProperty
+ */
+ @Target({ ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface IntToString {
+ /**
+ * The original int value to map to a String.
+ *
+ * @return An arbitrary int value.
+ */
+ int from();
+
+ /**
+ * The String to use in place of the original int value.
+ *
+ * @return An arbitrary non-null String.
+ */
+ String to();
+ }
+
+ /**
+ * Defines a mapping from a flag to a String. Such a mapping can be used
+ * in an @ExportedProperty to provide more meaningful values to the end user.
+ *
+ * @see android.view.ViewDebug.ExportedProperty
+ */
+ @Target({ ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface FlagToString {
+ /**
+ * The mask to apply to the original value.
+ *
+ * @return An arbitrary int value.
+ */
+ int mask();
+
+ /**
+ * The value to compare to the result of:
+ * <code>original value &amp; {@link #mask()}</code>.
+ *
+ * @return An arbitrary value.
+ */
+ int equals();
+
+ /**
+ * The String to use in place of the original int value.
+ *
+ * @return An arbitrary non-null String.
+ */
+ String name();
+
+ /**
+ * Indicates whether to output the flag when the test is true,
+ * or false. Defaults to true.
+ */
+ boolean outputIf() default true;
+ }
+
+ /**
+ * This annotation can be used to mark fields and methods to be dumped when
+ * the view is captured. Methods with this annotation must have no arguments
+ * and must return a valid type of data.
+ */
+ @Target({ ElementType.FIELD, ElementType.METHOD })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface CapturedViewProperty {
+ /**
+ * When retrieveReturn is true, we need to retrieve second level methods
+ * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
+ * we will set retrieveReturn = true on the annotation of
+ * myView.getFirstLevelMethod()
+ * @return true if we need the second level methods
+ */
+ boolean retrieveReturn() default false;
+ }
+
+ /**
+ * Allows a View to inject custom children into HierarchyViewer. For example,
+ * WebView uses this to add its internal layer tree as a child to itself
+ * @hide
+ */
+ public interface HierarchyHandler {
+ /**
+ * Dumps custom children to hierarchy viewer.
+ * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int)
+ * for the format
+ *
+ * An empty implementation should simply do nothing
+ *
+ * @param out The output writer
+ * @param level The indentation level
+ */
+ public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
+
+ /**
+ * Returns a View to enable grabbing screenshots from custom children
+ * returned in dumpViewHierarchyWithProperties.
+ *
+ * @param className The className of the view to find
+ * @param hashCode The hashCode of the view to find
+ * @return the View to capture from, or null if not found
+ */
+ public View findHierarchyView(String className, int hashCode);
+ }
+
+ private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
+ private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
+
+ // Maximum delay in ms after which we stop trying to capture a View's drawing
+ private static final int CAPTURE_TIMEOUT = 4000;
+
+ private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
+ private static final String REMOTE_COMMAND_DUMP = "DUMP";
+ private static final String REMOTE_COMMAND_DUMP_THEME = "DUMP_THEME";
+ private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
+ private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
+ private static final String REMOTE_PROFILE = "PROFILE";
+ private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
+ private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
+
+ private static HashMap<Class<?>, Field[]> sFieldsForClasses;
+ private static HashMap<Class<?>, Method[]> sMethodsForClasses;
+ private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
+
+ /**
+ * @deprecated This enum is now unused
+ */
+ @Deprecated
+ public enum HierarchyTraceType {
+ INVALIDATE,
+ INVALIDATE_CHILD,
+ INVALIDATE_CHILD_IN_PARENT,
+ REQUEST_LAYOUT,
+ ON_LAYOUT,
+ ON_MEASURE,
+ DRAW,
+ BUILD_CACHE
+ }
+
+ /**
+ * @deprecated This enum is now unused
+ */
+ @Deprecated
+ public enum RecyclerTraceType {
+ NEW_VIEW,
+ BIND_VIEW,
+ RECYCLE_FROM_ACTIVE_HEAP,
+ RECYCLE_FROM_SCRAP_HEAP,
+ MOVE_TO_SCRAP_HEAP,
+ MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
+ }
+
+ /**
+ * Returns the number of instanciated Views.
+ *
+ * @return The number of Views instanciated in the current process.
+ *
+ * @hide
+ */
+ public static long getViewInstanceCount() {
+ return Debug.countInstancesOfClass(View.class);
+ }
+
+ /**
+ * Returns the number of instanciated ViewAncestors.
+ *
+ * @return The number of ViewAncestors instanciated in the current process.
+ *
+ * @hide
+ */
+ public static long getViewRootImplCount() {
+ return Debug.countInstancesOfClass(ViewRootImpl.class);
+ }
+
+ /**
+ * @deprecated This method is now unused and invoking it is a no-op
+ */
+ @Deprecated
+ @SuppressWarnings({ "UnusedParameters", "deprecation" })
+ public static void trace(View view, RecyclerTraceType type, int... parameters) {
+ }
+
+ /**
+ * @deprecated This method is now unused and invoking it is a no-op
+ */
+ @Deprecated
+ @SuppressWarnings("UnusedParameters")
+ public static void startRecyclerTracing(String prefix, View view) {
+ }
+
+ /**
+ * @deprecated This method is now unused and invoking it is a no-op
+ */
+ @Deprecated
+ @SuppressWarnings("UnusedParameters")
+ public static void stopRecyclerTracing() {
+ }
+
+ /**
+ * @deprecated This method is now unused and invoking it is a no-op
+ */
+ @Deprecated
+ @SuppressWarnings({ "UnusedParameters", "deprecation" })
+ public static void trace(View view, HierarchyTraceType type) {
+ }
+
+ /**
+ * @deprecated This method is now unused and invoking it is a no-op
+ */
+ @Deprecated
+ @SuppressWarnings("UnusedParameters")
+ public static void startHierarchyTracing(String prefix, View view) {
+ }
+
+ /**
+ * @deprecated This method is now unused and invoking it is a no-op
+ */
+ @Deprecated
+ public static void stopHierarchyTracing() {
+ }
+
+ static void dispatchCommand(View view, String command, String parameters,
+ OutputStream clientStream) throws IOException {
+
+ // Paranoid but safe...
+ view = view.getRootView();
+
+ if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
+ dump(view, false, true, clientStream);
+ } else if (REMOTE_COMMAND_DUMP_THEME.equalsIgnoreCase(command)) {
+ dumpTheme(view, clientStream);
+ } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
+ captureLayers(view, new DataOutputStream(clientStream));
+ } else {
+ final String[] params = parameters.split(" ");
+ if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
+ capture(view, clientStream, params[0]);
+ } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
+ outputDisplayList(view, params[0]);
+ } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
+ invalidate(view, params[0]);
+ } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
+ requestLayout(view, params[0]);
+ } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
+ profile(view, clientStream, params[0]);
+ }
+ }
+ }
+
+ /** @hide */
+ public static View findView(View root, String parameter) {
+ // Look by type/hashcode
+ if (parameter.indexOf('@') != -1) {
+ final String[] ids = parameter.split("@");
+ final String className = ids[0];
+ final int hashCode = (int) Long.parseLong(ids[1], 16);
+
+ View view = root.getRootView();
+ if (view instanceof ViewGroup) {
+ return findView((ViewGroup) view, className, hashCode);
+ }
+ } else {
+ // Look by id
+ final int id = root.getResources().getIdentifier(parameter, null, null);
+ return root.getRootView().findViewById(id);
+ }
+
+ return null;
+ }
+
+ private static void invalidate(View root, String parameter) {
+ final View view = findView(root, parameter);
+ if (view != null) {
+ view.postInvalidate();
+ }
+ }
+
+ private static void requestLayout(View root, String parameter) {
+ final View view = findView(root, parameter);
+ if (view != null) {
+ root.post(new Runnable() {
+ public void run() {
+ view.requestLayout();
+ }
+ });
+ }
+ }
+
+ private static void profile(View root, OutputStream clientStream, String parameter)
+ throws IOException {
+
+ final View view = findView(root, parameter);
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+
+ if (view != null) {
+ profileViewAndChildren(view, out);
+ } else {
+ out.write("-1 -1 -1");
+ out.newLine();
+ }
+ out.write("DONE.");
+ out.newLine();
+ } catch (Exception e) {
+ android.util.Log.w("View", "Problem profiling the view:", e);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ /** @hide */
+ public static void profileViewAndChildren(final View view, BufferedWriter out)
+ throws IOException {
+ profileViewAndChildren(view, out, true);
+ }
+
+ private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
+ throws IOException {
+
+ long durationMeasure =
+ (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
+ ? profileViewOperation(view, new ViewOperation<Void>() {
+ public Void[] pre() {
+ forceLayout(view);
+ return null;
+ }
+
+ private void forceLayout(View view) {
+ view.forceLayout();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ forceLayout(group.getChildAt(i));
+ }
+ }
+ }
+
+ public void run(Void... data) {
+ view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+ }
+
+ public void post(Void... data) {
+ }
+ })
+ : 0;
+ long durationLayout =
+ (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
+ ? profileViewOperation(view, new ViewOperation<Void>() {
+ public Void[] pre() {
+ return null;
+ }
+
+ public void run(Void... data) {
+ view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
+ }
+
+ public void post(Void... data) {
+ }
+ }) : 0;
+ long durationDraw =
+ (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
+ ? profileViewOperation(view, new ViewOperation<Object>() {
+ public Object[] pre() {
+ final DisplayMetrics metrics =
+ (view != null && view.getResources() != null) ?
+ view.getResources().getDisplayMetrics() : null;
+ final Bitmap bitmap = metrics != null ?
+ Bitmap.createBitmap(metrics, metrics.widthPixels,
+ metrics.heightPixels, Bitmap.Config.RGB_565) : null;
+ final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
+ return new Object[] {
+ bitmap, canvas
+ };
+ }
+
+ public void run(Object... data) {
+ if (data[1] != null) {
+ view.draw((Canvas) data[1]);
+ }
+ }
+
+ public void post(Object... data) {
+ if (data[1] != null) {
+ ((Canvas) data[1]).setBitmap(null);
+ }
+ if (data[0] != null) {
+ ((Bitmap) data[0]).recycle();
+ }
+ }
+ }) : 0;
+ out.write(String.valueOf(durationMeasure));
+ out.write(' ');
+ out.write(String.valueOf(durationLayout));
+ out.write(' ');
+ out.write(String.valueOf(durationDraw));
+ out.newLine();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ profileViewAndChildren(group.getChildAt(i), out, false);
+ }
+ }
+ }
+
+ interface ViewOperation<T> {
+ T[] pre();
+ void run(T... data);
+ void post(T... data);
+ }
+
+ private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final long[] duration = new long[1];
+
+ view.post(new Runnable() {
+ public void run() {
+ try {
+ T[] data = operation.pre();
+ long start = Debug.threadCpuTimeNanos();
+ //noinspection unchecked
+ operation.run(data);
+ duration[0] = Debug.threadCpuTimeNanos() - start;
+ //noinspection unchecked
+ operation.post(data);
+ } finally {
+ latch.countDown();
+ }
+ }
+ });
+
+ try {
+ if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ Log.w("View", "Could not complete the profiling of the view " + view);
+ return -1;
+ }
+ } catch (InterruptedException e) {
+ Log.w("View", "Could not complete the profiling of the view " + view);
+ Thread.currentThread().interrupt();
+ return -1;
+ }
+
+ return duration[0];
+ }
+
+ /** @hide */
+ public static void captureLayers(View root, final DataOutputStream clientStream)
+ throws IOException {
+
+ try {
+ Rect outRect = new Rect();
+ try {
+ root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+
+ clientStream.writeInt(outRect.width());
+ clientStream.writeInt(outRect.height());
+
+ captureViewLayer(root, clientStream, true);
+
+ clientStream.write(2);
+ } finally {
+ clientStream.close();
+ }
+ }
+
+ private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
+ throws IOException {
+
+ final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
+
+ if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) {
+ final int id = view.getId();
+ String name = view.getClass().getSimpleName();
+ if (id != View.NO_ID) {
+ name = resolveId(view.getContext(), id).toString();
+ }
+
+ clientStream.write(1);
+ clientStream.writeUTF(name);
+ clientStream.writeByte(localVisible ? 1 : 0);
+
+ int[] position = new int[2];
+ // XXX: Should happen on the UI thread
+ view.getLocationInWindow(position);
+
+ clientStream.writeInt(position[0]);
+ clientStream.writeInt(position[1]);
+ clientStream.flush();
+
+ Bitmap b = performViewCapture(view, true);
+ if (b != null) {
+ ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
+ b.getHeight() * 2);
+ b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
+ clientStream.writeInt(arrayOut.size());
+ arrayOut.writeTo(clientStream);
+ }
+ clientStream.flush();
+ }
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ int count = group.getChildCount();
+
+ for (int i = 0; i < count; i++) {
+ captureViewLayer(group.getChildAt(i), clientStream, localVisible);
+ }
+ }
+
+ if (view.mOverlay != null) {
+ ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup;
+ captureViewLayer(overlayContainer, clientStream, localVisible);
+ }
+ }
+
+ private static void outputDisplayList(View root, String parameter) throws IOException {
+ final View view = findView(root, parameter);
+ view.getViewRootImpl().outputDisplayList(view);
+ }
+
+ /** @hide */
+ public static void outputDisplayList(View root, View target) {
+ root.getViewRootImpl().outputDisplayList(target);
+ }
+
+ private static void capture(View root, final OutputStream clientStream, String parameter)
+ throws IOException {
+
+ final View captureView = findView(root, parameter);
+ capture(root, clientStream, captureView);
+ }
+
+ /** @hide */
+ public static void capture(View root, final OutputStream clientStream, View captureView)
+ throws IOException {
+ Bitmap b = performViewCapture(captureView, false);
+
+ if (b == null) {
+ Log.w("View", "Failed to create capture bitmap!");
+ // Send an empty one so that it doesn't get stuck waiting for
+ // something.
+ b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
+ 1, 1, Bitmap.Config.ARGB_8888);
+ }
+
+ BufferedOutputStream out = null;
+ try {
+ out = new BufferedOutputStream(clientStream, 32 * 1024);
+ b.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ b.recycle();
+ }
+ }
+
+ private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) {
+ if (captureView != null) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Bitmap[] cache = new Bitmap[1];
+
+ captureView.post(new Runnable() {
+ public void run() {
+ try {
+ cache[0] = captureView.createSnapshot(
+ Bitmap.Config.ARGB_8888, 0, skipChildren);
+ } catch (OutOfMemoryError e) {
+ Log.w("View", "Out of memory for bitmap");
+ } finally {
+ latch.countDown();
+ }
+ }
+ });
+
+ try {
+ latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
+ return cache[0];
+ } catch (InterruptedException e) {
+ Log.w("View", "Could not complete the capture of the view " + captureView);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Dumps the view hierarchy starting from the given view.
+ * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below.
+ * @hide
+ */
+ @Deprecated
+ public static void dump(View root, boolean skipChildren, boolean includeProperties,
+ OutputStream clientStream) throws IOException {
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
+ View view = root.getRootView();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ dumpViewHierarchy(group.getContext(), group, out, 0,
+ skipChildren, includeProperties);
+ }
+ out.write("DONE.");
+ out.newLine();
+ } catch (Exception e) {
+ android.util.Log.w("View", "Problem dumping the view:", e);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ /**
+ * Dumps the view hierarchy starting from the given view.
+ * Rather than using reflection, it uses View's encode method to obtain all the properties.
+ * @hide
+ */
+ public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out)
+ throws InterruptedException {
+ final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out);
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
+ encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
+ view.encode(encoder);
+ latch.countDown();
+ }
+ });
+
+ latch.await(2, TimeUnit.SECONDS);
+ encoder.endStream();
+ }
+
+ /**
+ * Dumps the theme attributes from the given View.
+ * @hide
+ */
+ public static void dumpTheme(View view, OutputStream clientStream) throws IOException {
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
+ String[] attributes = getStyleAttributesDump(view.getContext().getResources(),
+ view.getContext().getTheme());
+ if (attributes != null) {
+ for (int i = 0; i < attributes.length; i += 2) {
+ if (attributes[i] != null) {
+ out.write(attributes[i] + "\n");
+ out.write(attributes[i + 1] + "\n");
+ }
+ }
+ }
+ out.write("DONE.");
+ out.newLine();
+ } catch (Exception e) {
+ android.util.Log.w("View", "Problem dumping View Theme:", e);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ /**
+ * Gets the style attributes from the {@link Resources.Theme}. For debugging only.
+ *
+ * @param resources Resources to resolve attributes from.
+ * @param theme Theme to dump.
+ * @return a String array containing pairs of adjacent Theme attribute data: name followed by
+ * its value.
+ *
+ * @hide
+ */
+ private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) {
+ TypedValue outValue = new TypedValue();
+ String nullString = "null";
+ int i = 0;
+ int[] attributes = theme.getAllAttributes();
+ String[] data = new String[attributes.length * 2];
+ for (int attributeId : attributes) {
+ try {
+ data[i] = resources.getResourceName(attributeId);
+ data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ?
+ outValue.coerceToString().toString() : nullString;
+ i += 2;
+
+ // attempt to replace reference data with its name
+ if (outValue.type == TypedValue.TYPE_REFERENCE) {
+ data[i - 1] = resources.getResourceName(outValue.resourceId);
+ }
+ } catch (Resources.NotFoundException e) {
+ // ignore resources we can't resolve
+ }
+ }
+ return data;
+ }
+
+ private static View findView(ViewGroup group, String className, int hashCode) {
+ if (isRequestedView(group, className, hashCode)) {
+ return group;
+ }
+
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = group.getChildAt(i);
+ if (view instanceof ViewGroup) {
+ final View found = findView((ViewGroup) view, className, hashCode);
+ if (found != null) {
+ return found;
+ }
+ } else if (isRequestedView(view, className, hashCode)) {
+ return view;
+ }
+ if (view.mOverlay != null) {
+ final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
+ className, hashCode);
+ if (found != null) {
+ return found;
+ }
+ }
+ if (view instanceof HierarchyHandler) {
+ final View found = ((HierarchyHandler)view)
+ .findHierarchyView(className, hashCode);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static boolean isRequestedView(View view, String className, int hashCode) {
+ if (view.hashCode() == hashCode) {
+ String viewClassName = view.getClass().getName();
+ if (className.equals("ViewOverlay")) {
+ return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
+ } else {
+ return className.equals(viewClassName);
+ }
+ }
+ return false;
+ }
+
+ private static void dumpViewHierarchy(Context context, ViewGroup group,
+ BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+ if (!dumpView(context, group, out, level, includeProperties)) {
+ return;
+ }
+
+ if (skipChildren) {
+ return;
+ }
+
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = group.getChildAt(i);
+ if (view instanceof ViewGroup) {
+ dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
+ includeProperties);
+ } else {
+ dumpView(context, view, out, level + 1, includeProperties);
+ }
+ if (view.mOverlay != null) {
+ ViewOverlay overlay = view.getOverlay();
+ ViewGroup overlayContainer = overlay.mOverlayViewGroup;
+ dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
+ includeProperties);
+ }
+ }
+ if (group instanceof HierarchyHandler) {
+ ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
+ }
+ }
+
+ private static boolean dumpView(Context context, View view,
+ BufferedWriter out, int level, boolean includeProperties) {
+
+ try {
+ for (int i = 0; i < level; i++) {
+ out.write(' ');
+ }
+ String className = view.getClass().getName();
+ if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
+ className = "ViewOverlay";
+ }
+ out.write(className);
+ out.write('@');
+ out.write(Integer.toHexString(view.hashCode()));
+ out.write(' ');
+ if (includeProperties) {
+ dumpViewProperties(context, view, out);
+ }
+ out.newLine();
+ } catch (IOException e) {
+ Log.w("View", "Error while dumping hierarchy tree");
+ return false;
+ }
+ return true;
+ }
+
+ private static Field[] getExportedPropertyFields(Class<?> klass) {
+ if (sFieldsForClasses == null) {
+ sFieldsForClasses = new HashMap<Class<?>, Field[]>();
+ }
+ if (sAnnotations == null) {
+ sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
+ }
+
+ final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
+
+ Field[] fields = map.get(klass);
+ if (fields != null) {
+ return fields;
+ }
+
+ try {
+ final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
+ final ArrayList<Field> foundFields = new ArrayList<Field>();
+ for (final Field field : declaredFields) {
+ // Fields which can't be resolved have a null type.
+ if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
+ field.setAccessible(true);
+ foundFields.add(field);
+ sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
+ }
+ }
+ fields = foundFields.toArray(new Field[foundFields.size()]);
+ map.put(klass, fields);
+ } catch (NoClassDefFoundError e) {
+ throw new AssertionError(e);
+ }
+
+ return fields;
+ }
+
+ private static Method[] getExportedPropertyMethods(Class<?> klass) {
+ if (sMethodsForClasses == null) {
+ sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
+ }
+ if (sAnnotations == null) {
+ sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
+ }
+
+ final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
+
+ Method[] methods = map.get(klass);
+ if (methods != null) {
+ return methods;
+ }
+
+ methods = klass.getDeclaredMethodsUnchecked(false);
+
+ final ArrayList<Method> foundMethods = new ArrayList<Method>();
+ for (final Method method : methods) {
+ // Ensure the method return and parameter types can be resolved.
+ try {
+ method.getReturnType();
+ method.getParameterTypes();
+ } catch (NoClassDefFoundError e) {
+ continue;
+ }
+
+ if (method.getParameterTypes().length == 0 &&
+ method.isAnnotationPresent(ExportedProperty.class) &&
+ method.getReturnType() != Void.class) {
+ method.setAccessible(true);
+ foundMethods.add(method);
+ sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
+ }
+ }
+
+ methods = foundMethods.toArray(new Method[foundMethods.size()]);
+ map.put(klass, methods);
+
+ return methods;
+ }
+
+ private static void dumpViewProperties(Context context, Object view,
+ BufferedWriter out) throws IOException {
+
+ dumpViewProperties(context, view, out, "");
+ }
+
+ private static void dumpViewProperties(Context context, Object view,
+ BufferedWriter out, String prefix) throws IOException {
+
+ if (view == null) {
+ out.write(prefix + "=4,null ");
+ return;
+ }
+
+ Class<?> klass = view.getClass();
+ do {
+ exportFields(context, view, out, klass, prefix);
+ exportMethods(context, view, out, klass, prefix);
+ klass = klass.getSuperclass();
+ } while (klass != Object.class);
+ }
+
+ private static Object callMethodOnAppropriateTheadBlocking(final Method method,
+ final Object object) throws IllegalAccessException, InvocationTargetException,
+ TimeoutException {
+ if (!(object instanceof View)) {
+ return method.invoke(object, (Object[]) null);
+ }
+
+ final View view = (View) object;
+ Callable<Object> callable = new Callable<Object>() {
+ @Override
+ public Object call() throws IllegalAccessException, InvocationTargetException {
+ return method.invoke(view, (Object[]) null);
+ }
+ };
+ FutureTask<Object> future = new FutureTask<Object>(callable);
+ // Try to use the handler provided by the view
+ Handler handler = view.getHandler();
+ // Fall back on using the main thread
+ if (handler == null) {
+ handler = new Handler(android.os.Looper.getMainLooper());
+ }
+ handler.post(future);
+ while (true) {
+ try {
+ return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
+ } catch (ExecutionException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IllegalAccessException) {
+ throw (IllegalAccessException)t;
+ }
+ if (t instanceof InvocationTargetException) {
+ throw (InvocationTargetException)t;
+ }
+ throw new RuntimeException("Unexpected exception", t);
+ } catch (InterruptedException e) {
+ // Call get again
+ } catch (CancellationException e) {
+ throw new RuntimeException("Unexpected cancellation exception", e);
+ }
+ }
+ }
+
+ private static String formatIntToHexString(int value) {
+ return "0x" + Integer.toHexString(value).toUpperCase();
+ }
+
+ private static void exportMethods(Context context, Object view, BufferedWriter out,
+ Class<?> klass, String prefix) throws IOException {
+
+ final Method[] methods = getExportedPropertyMethods(klass);
+ int count = methods.length;
+ for (int i = 0; i < count; i++) {
+ final Method method = methods[i];
+ //noinspection EmptyCatchBlock
+ try {
+ Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
+ final Class<?> returnType = method.getReturnType();
+ final ExportedProperty property = sAnnotations.get(method);
+ String categoryPrefix =
+ property.category().length() != 0 ? property.category() + ":" : "";
+
+ if (returnType == int.class) {
+ if (property.resolveId() && context != null) {
+ final int id = (Integer) methodValue;
+ methodValue = resolveId(context, id);
+ } else {
+ final FlagToString[] flagsMapping = property.flagMapping();
+ if (flagsMapping.length > 0) {
+ final int intValue = (Integer) methodValue;
+ final String valuePrefix =
+ categoryPrefix + prefix + method.getName() + '_';
+ exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
+ }
+
+ final IntToString[] mapping = property.mapping();
+ if (mapping.length > 0) {
+ final int intValue = (Integer) methodValue;
+ boolean mapped = false;
+ int mappingCount = mapping.length;
+ for (int j = 0; j < mappingCount; j++) {
+ final IntToString mapper = mapping[j];
+ if (mapper.from() == intValue) {
+ methodValue = mapper.to();
+ mapped = true;
+ break;
+ }
+ }
+
+ if (!mapped) {
+ methodValue = intValue;
+ }
+ }
+ }
+ } else if (returnType == int[].class) {
+ final int[] array = (int[]) methodValue;
+ final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
+ final String suffix = "()";
+
+ exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
+
+ continue;
+ } else if (returnType == String[].class) {
+ final String[] array = (String[]) methodValue;
+ if (property.hasAdjacentMapping() && array != null) {
+ for (int j = 0; j < array.length; j += 2) {
+ if (array[j] != null) {
+ writeEntry(out, categoryPrefix + prefix, array[j], "()",
+ array[j + 1] == null ? "null" : array[j + 1]);
+ }
+
+ }
+ }
+
+ continue;
+ } else if (!returnType.isPrimitive()) {
+ if (property.deepExport()) {
+ dumpViewProperties(context, methodValue, out, prefix + property.prefix());
+ continue;
+ }
+ }
+
+ writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ } catch (TimeoutException e) {
+ }
+ }
+ }
+
+ private static void exportFields(Context context, Object view, BufferedWriter out,
+ Class<?> klass, String prefix) throws IOException {
+
+ final Field[] fields = getExportedPropertyFields(klass);
+
+ int count = fields.length;
+ for (int i = 0; i < count; i++) {
+ final Field field = fields[i];
+
+ //noinspection EmptyCatchBlock
+ try {
+ Object fieldValue = null;
+ final Class<?> type = field.getType();
+ final ExportedProperty property = sAnnotations.get(field);
+ String categoryPrefix =
+ property.category().length() != 0 ? property.category() + ":" : "";
+
+ if (type == int.class || type == byte.class) {
+ if (property.resolveId() && context != null) {
+ final int id = field.getInt(view);
+ fieldValue = resolveId(context, id);
+ } else {
+ final FlagToString[] flagsMapping = property.flagMapping();
+ if (flagsMapping.length > 0) {
+ final int intValue = field.getInt(view);
+ final String valuePrefix =
+ categoryPrefix + prefix + field.getName() + '_';
+ exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
+ }
+
+ final IntToString[] mapping = property.mapping();
+ if (mapping.length > 0) {
+ final int intValue = field.getInt(view);
+ int mappingCount = mapping.length;
+ for (int j = 0; j < mappingCount; j++) {
+ final IntToString mapped = mapping[j];
+ if (mapped.from() == intValue) {
+ fieldValue = mapped.to();
+ break;
+ }
+ }
+
+ if (fieldValue == null) {
+ fieldValue = intValue;
+ }
+ }
+
+ if (property.formatToHexString()) {
+ fieldValue = field.get(view);
+ if (type == int.class) {
+ fieldValue = formatIntToHexString((Integer) fieldValue);
+ } else if (type == byte.class) {
+ fieldValue = "0x" + Byte.toHexString((Byte) fieldValue, true);
+ }
+ }
+ }
+ } else if (type == int[].class) {
+ final int[] array = (int[]) field.get(view);
+ final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
+ final String suffix = "";
+
+ exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
+
+ continue;
+ } else if (type == String[].class) {
+ final String[] array = (String[]) field.get(view);
+ if (property.hasAdjacentMapping() && array != null) {
+ for (int j = 0; j < array.length; j += 2) {
+ if (array[j] != null) {
+ writeEntry(out, categoryPrefix + prefix, array[j], "",
+ array[j + 1] == null ? "null" : array[j + 1]);
+ }
+ }
+ }
+
+ continue;
+ } else if (!type.isPrimitive()) {
+ if (property.deepExport()) {
+ dumpViewProperties(context, field.get(view), out, prefix +
+ property.prefix());
+ continue;
+ }
+ }
+
+ if (fieldValue == null) {
+ fieldValue = field.get(view);
+ }
+
+ writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
+ } catch (IllegalAccessException e) {
+ }
+ }
+ }
+
+ private static void writeEntry(BufferedWriter out, String prefix, String name,
+ String suffix, Object value) throws IOException {
+
+ out.write(prefix);
+ out.write(name);
+ out.write(suffix);
+ out.write("=");
+ writeValue(out, value);
+ out.write(' ');
+ }
+
+ private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
+ int intValue, String prefix) throws IOException {
+
+ final int count = mapping.length;
+ for (int j = 0; j < count; j++) {
+ final FlagToString flagMapping = mapping[j];
+ final boolean ifTrue = flagMapping.outputIf();
+ final int maskResult = intValue & flagMapping.mask();
+ final boolean test = maskResult == flagMapping.equals();
+ if ((test && ifTrue) || (!test && !ifTrue)) {
+ final String name = flagMapping.name();
+ final String value = formatIntToHexString(maskResult);
+ writeEntry(out, prefix, name, "", value);
+ }
+ }
+ }
+
+ private static void exportUnrolledArray(Context context, BufferedWriter out,
+ ExportedProperty property, int[] array, String prefix, String suffix)
+ throws IOException {
+
+ final IntToString[] indexMapping = property.indexMapping();
+ final boolean hasIndexMapping = indexMapping.length > 0;
+
+ final IntToString[] mapping = property.mapping();
+ final boolean hasMapping = mapping.length > 0;
+
+ final boolean resolveId = property.resolveId() && context != null;
+ final int valuesCount = array.length;
+
+ for (int j = 0; j < valuesCount; j++) {
+ String name;
+ String value = null;
+
+ final int intValue = array[j];
+
+ name = String.valueOf(j);
+ if (hasIndexMapping) {
+ int mappingCount = indexMapping.length;
+ for (int k = 0; k < mappingCount; k++) {
+ final IntToString mapped = indexMapping[k];
+ if (mapped.from() == j) {
+ name = mapped.to();
+ break;
+ }
+ }
+ }
+
+ if (hasMapping) {
+ int mappingCount = mapping.length;
+ for (int k = 0; k < mappingCount; k++) {
+ final IntToString mapped = mapping[k];
+ if (mapped.from() == intValue) {
+ value = mapped.to();
+ break;
+ }
+ }
+ }
+
+ if (resolveId) {
+ if (value == null) value = (String) resolveId(context, intValue);
+ } else {
+ value = String.valueOf(intValue);
+ }
+
+ writeEntry(out, prefix, name, suffix, value);
+ }
+ }
+
+ static Object resolveId(Context context, int id) {
+ Object fieldValue;
+ final Resources resources = context.getResources();
+ if (id >= 0) {
+ try {
+ fieldValue = resources.getResourceTypeName(id) + '/' +
+ resources.getResourceEntryName(id);
+ } catch (Resources.NotFoundException e) {
+ fieldValue = "id/" + formatIntToHexString(id);
+ }
+ } else {
+ fieldValue = "NO_ID";
+ }
+ return fieldValue;
+ }
+
+ private static void writeValue(BufferedWriter out, Object value) throws IOException {
+ if (value != null) {
+ String output = "[EXCEPTION]";
+ try {
+ output = value.toString().replace("\n", "\\n");
+ } finally {
+ out.write(String.valueOf(output.length()));
+ out.write(",");
+ out.write(output);
+ }
+ } else {
+ out.write("4,null");
+ }
+ }
+
+ private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
+ if (mCapturedViewFieldsForClasses == null) {
+ mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
+ }
+ final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
+
+ Field[] fields = map.get(klass);
+ if (fields != null) {
+ return fields;
+ }
+
+ final ArrayList<Field> foundFields = new ArrayList<Field>();
+ fields = klass.getFields();
+
+ int count = fields.length;
+ for (int i = 0; i < count; i++) {
+ final Field field = fields[i];
+ if (field.isAnnotationPresent(CapturedViewProperty.class)) {
+ field.setAccessible(true);
+ foundFields.add(field);
+ }
+ }
+
+ fields = foundFields.toArray(new Field[foundFields.size()]);
+ map.put(klass, fields);
+
+ return fields;
+ }
+
+ private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
+ if (mCapturedViewMethodsForClasses == null) {
+ mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
+ }
+ final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
+
+ Method[] methods = map.get(klass);
+ if (methods != null) {
+ return methods;
+ }
+
+ final ArrayList<Method> foundMethods = new ArrayList<Method>();
+ methods = klass.getMethods();
+
+ int count = methods.length;
+ for (int i = 0; i < count; i++) {
+ final Method method = methods[i];
+ if (method.getParameterTypes().length == 0 &&
+ method.isAnnotationPresent(CapturedViewProperty.class) &&
+ method.getReturnType() != Void.class) {
+ method.setAccessible(true);
+ foundMethods.add(method);
+ }
+ }
+
+ methods = foundMethods.toArray(new Method[foundMethods.size()]);
+ map.put(klass, methods);
+
+ return methods;
+ }
+
+ private static String capturedViewExportMethods(Object obj, Class<?> klass,
+ String prefix) {
+
+ if (obj == null) {
+ return "null";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ final Method[] methods = capturedViewGetPropertyMethods(klass);
+
+ int count = methods.length;
+ for (int i = 0; i < count; i++) {
+ final Method method = methods[i];
+ try {
+ Object methodValue = method.invoke(obj, (Object[]) null);
+ final Class<?> returnType = method.getReturnType();
+
+ CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
+ if (property.retrieveReturn()) {
+ //we are interested in the second level data only
+ sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
+ } else {
+ sb.append(prefix);
+ sb.append(method.getName());
+ sb.append("()=");
+
+ if (methodValue != null) {
+ final String value = methodValue.toString().replace("\n", "\\n");
+ sb.append(value);
+ } else {
+ sb.append("null");
+ }
+ sb.append("; ");
+ }
+ } catch (IllegalAccessException e) {
+ //Exception IllegalAccess, it is OK here
+ //we simply ignore this method
+ } catch (InvocationTargetException e) {
+ //Exception InvocationTarget, it is OK here
+ //we simply ignore this method
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
+ if (obj == null) {
+ return "null";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ final Field[] fields = capturedViewGetPropertyFields(klass);
+
+ int count = fields.length;
+ for (int i = 0; i < count; i++) {
+ final Field field = fields[i];
+ try {
+ Object fieldValue = field.get(obj);
+
+ sb.append(prefix);
+ sb.append(field.getName());
+ sb.append("=");
+
+ if (fieldValue != null) {
+ final String value = fieldValue.toString().replace("\n", "\\n");
+ sb.append(value);
+ } else {
+ sb.append("null");
+ }
+ sb.append(' ');
+ } catch (IllegalAccessException e) {
+ //Exception IllegalAccess, it is OK here
+ //we simply ignore this field
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Dump view info for id based instrument test generation
+ * (and possibly further data analysis). The results are dumped
+ * to the log.
+ * @param tag for log
+ * @param view for dump
+ */
+ public static void dumpCapturedView(String tag, Object view) {
+ Class<?> klass = view.getClass();
+ StringBuilder sb = new StringBuilder(klass.getName() + ": ");
+ sb.append(capturedViewExportFields(view, klass, ""));
+ sb.append(capturedViewExportMethods(view, klass, ""));
+ Log.d(tag, sb.toString());
+ }
+
+ /**
+ * Invoke a particular method on given view.
+ * The given method is always invoked on the UI thread. The caller thread will stall until the
+ * method invocation is complete. Returns an object equal to the result of the method
+ * invocation, null if the method is declared to return void
+ * @throws Exception if the method invocation caused any exception
+ * @hide
+ */
+ public static Object invokeViewMethod(final View view, final Method method,
+ final Object[] args) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Object> result = new AtomicReference<Object>();
+ final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ result.set(method.invoke(view, args));
+ } catch (InvocationTargetException e) {
+ exception.set(e.getCause());
+ } catch (Exception e) {
+ exception.set(e);
+ }
+
+ latch.countDown();
+ }
+ });
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (exception.get() != null) {
+ throw new RuntimeException(exception.get());
+ }
+
+ return result.get();
+ }
+
+ /**
+ * @hide
+ */
+ public static void setLayoutParameter(final View view, final String param, final int value)
+ throws NoSuchFieldException, IllegalAccessException {
+ final ViewGroup.LayoutParams p = view.getLayoutParams();
+ final Field f = p.getClass().getField(param);
+ if (f.getType() != int.class) {
+ throw new RuntimeException("Only integer layout parameters can be set. Field "
+ + param + " is of type " + f.getType().getSimpleName());
+ }
+
+ f.set(p, Integer.valueOf(value));
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ view.setLayoutParams(p);
+ }
+ });
+ }
+}
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
new file mode 100644
index 00000000..b2e5a163
--- /dev/null
+++ b/android/view/ViewGroup.java
@@ -0,0 +1,8528 @@
+/*
+ * Copyright (C) 2006 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 static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+import android.animation.LayoutTransition;
+import android.annotation.CallSuper;
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.annotation.UiThread;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.LayoutAnimationController;
+import android.view.animation.Transformation;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * <p>
+ * A <code>ViewGroup</code> is a special view that can contain other views
+ * (called children.) The view group is the base class for layouts and views
+ * containers. This class also defines the
+ * {@link android.view.ViewGroup.LayoutParams} class which serves as the base
+ * class for layouts parameters.
+ * </p>
+ *
+ * <p>
+ * Also see {@link LayoutParams} for layout attributes.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating user interface layouts, read the
+ * <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> developer
+ * guide.</p></div>
+ *
+ * <p>Here is a complete implementation of a custom ViewGroup that implements
+ * a simple {@link android.widget.FrameLayout} along with the ability to stack
+ * children in left and right gutters.</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java
+ * Complete}
+ *
+ * <p>If you are implementing XML layout attributes as shown in the example, this is the
+ * corresponding definition for them that would go in <code>res/values/attrs.xml</code>:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/values/attrs.xml CustomLayout}
+ *
+ * <p>Finally the layout manager can be used in an XML layout like so:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout/custom_layout.xml Complete}
+ *
+ * @attr ref android.R.styleable#ViewGroup_clipChildren
+ * @attr ref android.R.styleable#ViewGroup_clipToPadding
+ * @attr ref android.R.styleable#ViewGroup_layoutAnimation
+ * @attr ref android.R.styleable#ViewGroup_animationCache
+ * @attr ref android.R.styleable#ViewGroup_persistentDrawingCache
+ * @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache
+ * @attr ref android.R.styleable#ViewGroup_addStatesFromChildren
+ * @attr ref android.R.styleable#ViewGroup_descendantFocusability
+ * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
+ * @attr ref android.R.styleable#ViewGroup_splitMotionEvents
+ * @attr ref android.R.styleable#ViewGroup_layoutMode
+ */
+@UiThread
+public abstract class ViewGroup extends View implements ViewParent, ViewManager {
+ private static final String TAG = "ViewGroup";
+
+ private static final boolean DBG = false;
+
+ /**
+ * Views which have been hidden or removed which need to be animated on
+ * their way out.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected ArrayList<View> mDisappearingChildren;
+
+ /**
+ * Listener used to propagate events indicating when children are added
+ * and/or removed from a view group.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnHierarchyChangeListener mOnHierarchyChangeListener;
+
+ // The view contained within this ViewGroup that has or contains focus.
+ private View mFocused;
+ // The view contained within this ViewGroup (excluding nested keyboard navigation clusters)
+ // that is or contains a default-focus view.
+ private View mDefaultFocus;
+ // The last child of this ViewGroup which held focus within the current cluster
+ View mFocusedInCluster;
+
+ /**
+ * A Transformation used when drawing children, to
+ * apply on the child being drawn.
+ */
+ private Transformation mChildTransformation;
+
+ /**
+ * Used to track the current invalidation region.
+ */
+ RectF mInvalidateRegion;
+
+ /**
+ * A Transformation used to calculate a correct
+ * invalidation area when the application is autoscaled.
+ */
+ Transformation mInvalidationTransformation;
+
+ // Current frontmost child that can accept drag and lies under the drag location.
+ // Used only to generate ENTER/EXIT events for pre-Nougat aps.
+ private View mCurrentDragChild;
+
+ // Metadata about the ongoing drag
+ private DragEvent mCurrentDragStartEvent;
+ private boolean mIsInterestedInDrag;
+ private HashSet<View> mChildrenInterestedInDrag;
+
+ // Used during drag dispatch
+ private PointF mLocalPoint;
+
+ // Lazily-created holder for point computations.
+ private float[] mTempPoint;
+
+ // Layout animation
+ private LayoutAnimationController mLayoutAnimationController;
+ private Animation.AnimationListener mAnimationListener;
+
+ // First touch target in the linked list of touch targets.
+ private TouchTarget mFirstTouchTarget;
+
+ // For debugging only. You can see these in hierarchyviewer.
+ @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+ @ViewDebug.ExportedProperty(category = "events")
+ private long mLastTouchDownTime;
+ @ViewDebug.ExportedProperty(category = "events")
+ private int mLastTouchDownIndex = -1;
+ @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+ @ViewDebug.ExportedProperty(category = "events")
+ private float mLastTouchDownX;
+ @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+ @ViewDebug.ExportedProperty(category = "events")
+ private float mLastTouchDownY;
+
+ // First hover target in the linked list of hover targets.
+ // The hover targets are children which have received ACTION_HOVER_ENTER.
+ // They might not have actually handled the hover event, but we will
+ // continue sending hover events to them as long as the pointer remains over
+ // their bounds and the view group does not intercept hover.
+ private HoverTarget mFirstHoverTarget;
+
+ // True if the view group itself received a hover event.
+ // It might not have actually handled the hover event.
+ private boolean mHoveredSelf;
+
+ // The child capable of showing a tooltip and currently under the pointer.
+ private View mTooltipHoverTarget;
+
+ // True if the view group is capable of showing a tooltip and the pointer is directly
+ // over the view group but not one of its child views.
+ private boolean mTooltipHoveredSelf;
+
+ /**
+ * Internal flags.
+ *
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(mask = FLAG_CLIP_CHILDREN, equals = FLAG_CLIP_CHILDREN,
+ name = "CLIP_CHILDREN"),
+ @ViewDebug.FlagToString(mask = FLAG_CLIP_TO_PADDING, equals = FLAG_CLIP_TO_PADDING,
+ name = "CLIP_TO_PADDING"),
+ @ViewDebug.FlagToString(mask = FLAG_PADDING_NOT_NULL, equals = FLAG_PADDING_NOT_NULL,
+ name = "PADDING_NOT_NULL")
+ }, formatToHexString = true)
+ protected int mGroupFlags;
+
+ /**
+ * Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
+ */
+ private int mLayoutMode = LAYOUT_MODE_UNDEFINED;
+
+ /**
+ * NOTE: If you change the flags below make sure to reflect the changes
+ * the DisplayList class
+ */
+
+ // When set, ViewGroup invalidates only the child's rectangle
+ // Set by default
+ static final int FLAG_CLIP_CHILDREN = 0x1;
+
+ // When set, ViewGroup excludes the padding area from the invalidate rectangle
+ // Set by default
+ private static final int FLAG_CLIP_TO_PADDING = 0x2;
+
+ // When set, dispatchDraw() will invoke invalidate(); this is set by drawChild() when
+ // a child needs to be invalidated and FLAG_OPTIMIZE_INVALIDATE is set
+ static final int FLAG_INVALIDATE_REQUIRED = 0x4;
+
+ // When set, dispatchDraw() will run the layout animation and unset the flag
+ private static final int FLAG_RUN_ANIMATION = 0x8;
+
+ // When set, there is either no layout animation on the ViewGroup or the layout
+ // animation is over
+ // Set by default
+ static final int FLAG_ANIMATION_DONE = 0x10;
+
+ // If set, this ViewGroup has padding; if unset there is no padding and we don't need
+ // to clip it, even if FLAG_CLIP_TO_PADDING is set
+ private static final int FLAG_PADDING_NOT_NULL = 0x20;
+
+ /** @deprecated - functionality removed */
+ @Deprecated
+ private static final int FLAG_ANIMATION_CACHE = 0x40;
+
+ // When set, this ViewGroup converts calls to invalidate(Rect) to invalidate() during a
+ // layout animation; this avoid clobbering the hierarchy
+ // Automatically set when the layout animation starts, depending on the animation's
+ // characteristics
+ static final int FLAG_OPTIMIZE_INVALIDATE = 0x80;
+
+ // When set, the next call to drawChild() will clear mChildTransformation's matrix
+ static final int FLAG_CLEAR_TRANSFORMATION = 0x100;
+
+ // When set, this ViewGroup invokes mAnimationListener.onAnimationEnd() and removes
+ // the children's Bitmap caches if necessary
+ // This flag is set when the layout animation is over (after FLAG_ANIMATION_DONE is set)
+ private static final int FLAG_NOTIFY_ANIMATION_LISTENER = 0x200;
+
+ /**
+ * When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
+ * to get the index of the child to draw for that iteration.
+ *
+ * @hide
+ */
+ protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
+
+ /**
+ * When set, this ViewGroup supports static transformations on children; this causes
+ * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
+ * invoked when a child is drawn.
+ *
+ * Any subclass overriding
+ * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
+ * set this flags in {@link #mGroupFlags}.
+ *
+ * {@hide}
+ */
+ protected static final int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
+
+ // UNUSED FLAG VALUE: 0x1000;
+
+ /**
+ * When set, this ViewGroup's drawable states also include those
+ * of its children.
+ */
+ private static final int FLAG_ADD_STATES_FROM_CHILDREN = 0x2000;
+
+ /** @deprecated functionality removed */
+ @Deprecated
+ private static final int FLAG_ALWAYS_DRAWN_WITH_CACHE = 0x4000;
+
+ /** @deprecated functionality removed */
+ @Deprecated
+ private static final int FLAG_CHILDREN_DRAWN_WITH_CACHE = 0x8000;
+
+ /**
+ * When set, this group will go through its list of children to notify them of
+ * any drawable state change.
+ */
+ private static final int FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE = 0x10000;
+
+ private static final int FLAG_MASK_FOCUSABILITY = 0x60000;
+
+ /**
+ * This view will get focus before any of its descendants.
+ */
+ public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000;
+
+ /**
+ * This view will get focus only if none of its descendants want it.
+ */
+ public static final int FOCUS_AFTER_DESCENDANTS = 0x40000;
+
+ /**
+ * This view will block any of its descendants from getting focus, even
+ * if they are focusable.
+ */
+ public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;
+
+ /**
+ * Used to map between enum in attrubutes and flag values.
+ */
+ private static final int[] DESCENDANT_FOCUSABILITY_FLAGS =
+ {FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS,
+ FOCUS_BLOCK_DESCENDANTS};
+
+ /**
+ * When set, this ViewGroup should not intercept touch events.
+ * {@hide}
+ */
+ protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
+
+ /**
+ * When set, this ViewGroup will split MotionEvents to multiple child Views when appropriate.
+ */
+ private static final int FLAG_SPLIT_MOTION_EVENTS = 0x200000;
+
+ /**
+ * When set, this ViewGroup will not dispatch onAttachedToWindow calls
+ * to children when adding new views. This is used to prevent multiple
+ * onAttached calls when a ViewGroup adds children in its own onAttached method.
+ */
+ private static final int FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW = 0x400000;
+
+ /**
+ * When true, indicates that a layoutMode has been explicitly set, either with
+ * an explicit call to {@link #setLayoutMode(int)} in code or from an XML resource.
+ * This distinguishes the situation in which a layout mode was inherited from
+ * one of the ViewGroup's ancestors and cached locally.
+ */
+ private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000;
+
+ static final int FLAG_IS_TRANSITION_GROUP = 0x1000000;
+
+ static final int FLAG_IS_TRANSITION_GROUP_SET = 0x2000000;
+
+ /**
+ * When set, focus will not be permitted to enter this group if a touchscreen is present.
+ */
+ static final int FLAG_TOUCHSCREEN_BLOCKS_FOCUS = 0x4000000;
+
+ /**
+ * When true, indicates that a call to startActionModeForChild was made with the type parameter
+ * and should not be ignored. This helps in backwards compatibility with the existing method
+ * without a type.
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+ */
+ private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED = 0x8000000;
+
+ /**
+ * When true, indicates that a call to startActionModeForChild was made without the type
+ * parameter. This helps in backwards compatibility with the existing method
+ * without a type.
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+ */
+ private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED = 0x10000000;
+
+ /**
+ * When set, indicates that a call to showContextMenuForChild was made with explicit
+ * coordinates within the initiating child view.
+ */
+ private static final int FLAG_SHOW_CONTEXT_MENU_WITH_COORDS = 0x20000000;
+
+ /**
+ * Indicates which types of drawing caches are to be kept in memory.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected int mPersistentDrawingCache;
+
+ /**
+ * Used to indicate that no drawing cache should be kept in memory.
+ */
+ public static final int PERSISTENT_NO_CACHE = 0x0;
+
+ /**
+ * Used to indicate that the animation drawing cache should be kept in memory.
+ */
+ public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
+
+ /**
+ * Used to indicate that the scrolling drawing cache should be kept in memory.
+ */
+ public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
+
+ /**
+ * Used to indicate that all drawing caches should be kept in memory.
+ */
+ public static final int PERSISTENT_ALL_CACHES = 0x3;
+
+ // Layout Modes
+
+ private static final int LAYOUT_MODE_UNDEFINED = -1;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ * Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
+ * {@link #getRight() right} and {@link #getBottom() bottom}.
+ */
+ public static final int LAYOUT_MODE_CLIP_BOUNDS = 0;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ * Optical bounds describe where a widget appears to be. They sit inside the clip
+ * bounds which need to cover a larger area to allow other effects,
+ * such as shadows and glows, to be drawn.
+ */
+ public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
+
+ /** @hide */
+ public static int LAYOUT_MODE_DEFAULT = LAYOUT_MODE_CLIP_BOUNDS;
+
+ /**
+ * We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
+ * are set at the same time.
+ */
+ protected static final int CLIP_TO_PADDING_MASK = FLAG_CLIP_TO_PADDING | FLAG_PADDING_NOT_NULL;
+
+ // Index of the child's left position in the mLocation array
+ private static final int CHILD_LEFT_INDEX = 0;
+ // Index of the child's top position in the mLocation array
+ private static final int CHILD_TOP_INDEX = 1;
+
+ // Child views of this ViewGroup
+ private View[] mChildren;
+ // Number of valid children in the mChildren array, the rest should be null or not
+ // considered as children
+ private int mChildrenCount;
+
+ // Whether layout calls are currently being suppressed, controlled by calls to
+ // suppressLayout()
+ boolean mSuppressLayout = false;
+
+ // Whether any layout calls have actually been suppressed while mSuppressLayout
+ // has been true. This tracks whether we need to issue a requestLayout() when
+ // layout is later re-enabled.
+ private boolean mLayoutCalledWhileSuppressed = false;
+
+ private static final int ARRAY_INITIAL_CAPACITY = 12;
+ private static final int ARRAY_CAPACITY_INCREMENT = 12;
+
+ private static float[] sDebugLines;
+
+ // Used to draw cached views
+ Paint mCachePaint;
+
+ // Used to animate add/remove changes in layout
+ private LayoutTransition mTransition;
+
+ // The set of views that are currently being transitioned. This list is used to track views
+ // being removed that should not actually be removed from the parent yet because they are
+ // being animated.
+ private ArrayList<View> mTransitioningViews;
+
+ // List of children changing visibility. This is used to potentially keep rendering
+ // views during a transition when they otherwise would have become gone/invisible
+ private ArrayList<View> mVisibilityChangingChildren;
+
+ // Temporary holder of presorted children, only used for
+ // input/software draw dispatch for correctly Z ordering.
+ private ArrayList<View> mPreSortedChildren;
+
+ // Indicates how many of this container's child subtrees contain transient state
+ @ViewDebug.ExportedProperty(category = "layout")
+ private int mChildCountWithTransientState = 0;
+
+ /**
+ * Currently registered axes for nested scrolling. Flag set consisting of
+ * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE}
+ * for null.
+ */
+ private int mNestedScrollAxes;
+
+ // Used to manage the list of transient views, added by addTransientView()
+ private List<Integer> mTransientIndices = null;
+ private List<View> mTransientViews = null;
+
+
+ /**
+ * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild.
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+ */
+ private static final ActionMode SENTINEL_ACTION_MODE = new ActionMode() {
+ @Override
+ public void setTitle(CharSequence title) {}
+
+ @Override
+ public void setTitle(int resId) {}
+
+ @Override
+ public void setSubtitle(CharSequence subtitle) {}
+
+ @Override
+ public void setSubtitle(int resId) {}
+
+ @Override
+ public void setCustomView(View view) {}
+
+ @Override
+ public void invalidate() {}
+
+ @Override
+ public void finish() {}
+
+ @Override
+ public Menu getMenu() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getSubtitle() {
+ return null;
+ }
+
+ @Override
+ public View getCustomView() {
+ return null;
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return null;
+ }
+ };
+
+ public ViewGroup(Context context) {
+ this(context, null);
+ }
+
+ public ViewGroup(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ initViewGroup();
+ initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private void initViewGroup() {
+ // ViewGroup doesn't draw by default
+ if (!debugDraw()) {
+ setFlags(WILL_NOT_DRAW, DRAW_MASK);
+ }
+ mGroupFlags |= FLAG_CLIP_CHILDREN;
+ mGroupFlags |= FLAG_CLIP_TO_PADDING;
+ mGroupFlags |= FLAG_ANIMATION_DONE;
+ mGroupFlags |= FLAG_ANIMATION_CACHE;
+ mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
+
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
+ mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
+ }
+
+ setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
+
+ mChildren = new View[ARRAY_INITIAL_CAPACITY];
+ mChildrenCount = 0;
+
+ mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
+ }
+
+ private void initFromAttributes(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr,
+ defStyleRes);
+
+ final int N = a.getIndexCount();
+ for (int i = 0; i < N; i++) {
+ int attr = a.getIndex(i);
+ switch (attr) {
+ case R.styleable.ViewGroup_clipChildren:
+ setClipChildren(a.getBoolean(attr, true));
+ break;
+ case R.styleable.ViewGroup_clipToPadding:
+ setClipToPadding(a.getBoolean(attr, true));
+ break;
+ case R.styleable.ViewGroup_animationCache:
+ setAnimationCacheEnabled(a.getBoolean(attr, true));
+ break;
+ case R.styleable.ViewGroup_persistentDrawingCache:
+ setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE));
+ break;
+ case R.styleable.ViewGroup_addStatesFromChildren:
+ setAddStatesFromChildren(a.getBoolean(attr, false));
+ break;
+ case R.styleable.ViewGroup_alwaysDrawnWithCache:
+ setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true));
+ break;
+ case R.styleable.ViewGroup_layoutAnimation:
+ int id = a.getResourceId(attr, -1);
+ if (id > 0) {
+ setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
+ }
+ break;
+ case R.styleable.ViewGroup_descendantFocusability:
+ setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
+ break;
+ case R.styleable.ViewGroup_splitMotionEvents:
+ setMotionEventSplittingEnabled(a.getBoolean(attr, false));
+ break;
+ case R.styleable.ViewGroup_animateLayoutChanges:
+ boolean animateLayoutChanges = a.getBoolean(attr, false);
+ if (animateLayoutChanges) {
+ setLayoutTransition(new LayoutTransition());
+ }
+ break;
+ case R.styleable.ViewGroup_layoutMode:
+ setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
+ break;
+ case R.styleable.ViewGroup_transitionGroup:
+ setTransitionGroup(a.getBoolean(attr, false));
+ break;
+ case R.styleable.ViewGroup_touchscreenBlocksFocus:
+ setTouchscreenBlocksFocus(a.getBoolean(attr, false));
+ break;
+ }
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Gets the descendant focusability of this view group. The descendant
+ * focusability defines the relationship between this view group and its
+ * descendants when looking for a view to take focus in
+ * {@link #requestFocus(int, android.graphics.Rect)}.
+ *
+ * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
+ * {@link #FOCUS_BLOCK_DESCENDANTS}.
+ */
+ @ViewDebug.ExportedProperty(category = "focus", mapping = {
+ @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"),
+ @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"),
+ @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS")
+ })
+ public int getDescendantFocusability() {
+ return mGroupFlags & FLAG_MASK_FOCUSABILITY;
+ }
+
+ /**
+ * Set the descendant focusability of this view group. This defines the relationship
+ * between this view group and its descendants when looking for a view to
+ * take focus in {@link #requestFocus(int, android.graphics.Rect)}.
+ *
+ * @param focusability one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
+ * {@link #FOCUS_BLOCK_DESCENDANTS}.
+ */
+ public void setDescendantFocusability(int focusability) {
+ switch (focusability) {
+ case FOCUS_BEFORE_DESCENDANTS:
+ case FOCUS_AFTER_DESCENDANTS:
+ case FOCUS_BLOCK_DESCENDANTS:
+ break;
+ default:
+ throw new IllegalArgumentException("must be one of FOCUS_BEFORE_DESCENDANTS, "
+ + "FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS");
+ }
+ mGroupFlags &= ~FLAG_MASK_FOCUSABILITY;
+ mGroupFlags |= (focusability & FLAG_MASK_FOCUSABILITY);
+ }
+
+ @Override
+ void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
+ if (mFocused != null) {
+ mFocused.unFocus(this);
+ mFocused = null;
+ mFocusedInCluster = null;
+ }
+ super.handleFocusGainInternal(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (DBG) {
+ System.out.println(this + " requestChildFocus()");
+ }
+ if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+ return;
+ }
+
+ // Unfocus us, if necessary
+ super.unFocus(focused);
+
+ // We had a previous notion of who had focus. Clear it.
+ if (mFocused != child) {
+ if (mFocused != null) {
+ mFocused.unFocus(focused);
+ }
+
+ mFocused = child;
+ }
+ if (mParent != null) {
+ mParent.requestChildFocus(this, focused);
+ }
+ }
+
+ void setDefaultFocus(View child) {
+ // Stop at any higher view which is explicitly focused-by-default
+ if (mDefaultFocus != null && mDefaultFocus.isFocusedByDefault()) {
+ return;
+ }
+
+ mDefaultFocus = child;
+
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).setDefaultFocus(this);
+ }
+ }
+
+ /**
+ * Clears the default-focus chain from {@param child} up to the first parent which has another
+ * default-focusable branch below it or until there is no default-focus chain.
+ *
+ * @param child
+ */
+ void clearDefaultFocus(View child) {
+ // Stop at any higher view which is explicitly focused-by-default
+ if (mDefaultFocus != child && mDefaultFocus != null
+ && mDefaultFocus.isFocusedByDefault()) {
+ return;
+ }
+
+ mDefaultFocus = null;
+
+ // Search child siblings for default focusables.
+ for (int i = 0; i < mChildrenCount; ++i) {
+ View sibling = mChildren[i];
+ if (sibling.isFocusedByDefault()) {
+ mDefaultFocus = sibling;
+ return;
+ } else if (mDefaultFocus == null && sibling.hasDefaultFocus()) {
+ mDefaultFocus = sibling;
+ }
+ }
+
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).clearDefaultFocus(this);
+ }
+ }
+
+ @Override
+ boolean hasDefaultFocus() {
+ return mDefaultFocus != null || super.hasDefaultFocus();
+ }
+
+ /**
+ * Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
+ * it.
+ * <br>
+ * This is intended to be run on {@code child}'s immediate parent. This is necessary because
+ * the chain is sometimes cleared after {@code child} has been detached.
+ */
+ void clearFocusedInCluster(View child) {
+ if (mFocusedInCluster != child) {
+ return;
+ }
+ clearFocusedInCluster();
+ }
+
+ /**
+ * Removes the focusedInCluster chain from this up to the cluster containing it.
+ */
+ void clearFocusedInCluster() {
+ View top = findKeyboardNavigationCluster();
+ ViewParent parent = this;
+ do {
+ ((ViewGroup) parent).mFocusedInCluster = null;
+ if (parent == top) {
+ break;
+ }
+ parent = parent.getParent();
+ } while (parent instanceof ViewGroup);
+ }
+
+ @Override
+ public void focusableViewAvailable(View v) {
+ if (mParent != null
+ // shortcut: don't report a new focusable view if we block our descendants from
+ // getting focus or if we're not visible
+ && (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS)
+ && ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
+ && (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen())
+ // shortcut: don't report a new focusable view if we already are focused
+ // (and we don't prefer our descendants)
+ //
+ // note: knowing that mFocused is non-null is not a good enough reason
+ // to break the traversal since in that case we'd actually have to find
+ // the focused view and make sure it wasn't FOCUS_AFTER_DESCENDANTS and
+ // an ancestor of v; this will get checked for at ViewAncestor
+ && !(isFocused() && getDescendantFocusability() != FOCUS_AFTER_DESCENDANTS)) {
+ mParent.focusableViewAvailable(v);
+ }
+ }
+
+ @Override
+ public boolean showContextMenuForChild(View originalView) {
+ if (isShowingContextMenuWithCoords()) {
+ // We're being called for compatibility. Return false and let the version
+ // with coordinates recurse up.
+ return false;
+ }
+ return mParent != null && mParent.showContextMenuForChild(originalView);
+ }
+
+ /**
+ * @hide used internally for compatibility with existing app code only
+ */
+ public final boolean isShowingContextMenuWithCoords() {
+ return (mGroupFlags & FLAG_SHOW_CONTEXT_MENU_WITH_COORDS) != 0;
+ }
+
+ @Override
+ public boolean showContextMenuForChild(View originalView, float x, float y) {
+ try {
+ mGroupFlags |= FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;
+ if (showContextMenuForChild(originalView)) {
+ return true;
+ }
+ } finally {
+ mGroupFlags &= ~FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;
+ }
+ return mParent != null && mParent.showContextMenuForChild(originalView, x, y);
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+ if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED) == 0) {
+ // This is the original call.
+ try {
+ mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
+ return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
+ } finally {
+ mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
+ }
+ } else {
+ // We are being called from the new method with type.
+ return SENTINEL_ACTION_MODE;
+ }
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(
+ View originalView, ActionMode.Callback callback, int type) {
+ if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED) == 0
+ && type == ActionMode.TYPE_PRIMARY) {
+ ActionMode mode;
+ try {
+ mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
+ mode = startActionModeForChild(originalView, callback);
+ } finally {
+ mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
+ }
+ if (mode != SENTINEL_ACTION_MODE) {
+ return mode;
+ }
+ }
+ if (mParent != null) {
+ try {
+ return mParent.startActionModeForChild(originalView, callback, type);
+ } catch (AbstractMethodError ame) {
+ // Custom view parents might not implement this method.
+ return mParent.startActionModeForChild(originalView, callback);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean dispatchActivityResult(
+ String who, int requestCode, int resultCode, Intent data) {
+ if (super.dispatchActivityResult(who, requestCode, resultCode, data)) {
+ return true;
+ }
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.dispatchActivityResult(who, requestCode, resultCode, data)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find the nearest view in the specified direction that wants to take
+ * focus.
+ *
+ * @param focused The view that currently has focus
+ * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
+ * FOCUS_RIGHT, or 0 for not applicable.
+ */
+ @Override
+ public View focusSearch(View focused, int direction) {
+ if (isRootNamespace()) {
+ // root namespace means we should consider ourselves the top of the
+ // tree for focus searching; otherwise we could be focus searching
+ // into other tabs. see LocalActivityManager and TabHost for more info.
+ return FocusFinder.getInstance().findNextFocus(this, focused, direction);
+ } else if (mParent != null) {
+ return mParent.focusSearch(focused, direction);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ return false;
+ }
+
+ @Override
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ ViewParent parent = mParent;
+ if (parent == null) {
+ return false;
+ }
+ final boolean propagate = onRequestSendAccessibilityEvent(child, event);
+ if (!propagate) {
+ return false;
+ }
+ return parent.requestSendAccessibilityEvent(this, event);
+ }
+
+ /**
+ * Called when a child has requested sending an {@link AccessibilityEvent} and
+ * gives an opportunity to its parent to augment the event.
+ * <p>
+ * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
+ * {@link android.view.View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
+ * {@link android.view.View.AccessibilityDelegate#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)}
+ * is responsible for handling this call.
+ * </p>
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event should be sent.
+ *
+ * @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
+ */
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (mAccessibilityDelegate != null) {
+ return mAccessibilityDelegate.onRequestSendAccessibilityEvent(this, child, event);
+ } else {
+ return onRequestSendAccessibilityEventInternal(child, event);
+ }
+ }
+
+ /**
+ * @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent)
+ *
+ * Note: Called from the default {@link View.AccessibilityDelegate}.
+ *
+ * @hide
+ */
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ return true;
+ }
+
+ /**
+ * Called when a child view has changed whether or not it is tracking transient state.
+ */
+ @Override
+ public void childHasTransientStateChanged(View child, boolean childHasTransientState) {
+ final boolean oldHasTransientState = hasTransientState();
+ if (childHasTransientState) {
+ mChildCountWithTransientState++;
+ } else {
+ mChildCountWithTransientState--;
+ }
+
+ final boolean newHasTransientState = hasTransientState();
+ if (mParent != null && oldHasTransientState != newHasTransientState) {
+ try {
+ mParent.childHasTransientStateChanged(this, newHasTransientState);
+ } catch (AbstractMethodError e) {
+ Log.e(TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ }
+
+ @Override
+ public boolean hasTransientState() {
+ return mChildCountWithTransientState > 0 || super.hasTransientState();
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ return mFocused != null &&
+ mFocused.dispatchUnhandledMove(focused, direction);
+ }
+
+ @Override
+ public void clearChildFocus(View child) {
+ if (DBG) {
+ System.out.println(this + " clearChildFocus()");
+ }
+
+ mFocused = null;
+ if (mParent != null) {
+ mParent.clearChildFocus(this);
+ }
+ }
+
+ @Override
+ public void clearFocus() {
+ if (DBG) {
+ System.out.println(this + " clearFocus()");
+ }
+ if (mFocused == null) {
+ super.clearFocus();
+ } else {
+ View focused = mFocused;
+ mFocused = null;
+ focused.clearFocus();
+ }
+ }
+
+ @Override
+ void unFocus(View focused) {
+ if (DBG) {
+ System.out.println(this + " unFocus()");
+ }
+ if (mFocused == null) {
+ super.unFocus(focused);
+ } else {
+ mFocused.unFocus(focused);
+ mFocused = null;
+ }
+ }
+
+ /**
+ * Returns the focused child of this view, if any. The child may have focus
+ * or contain focus.
+ *
+ * @return the focused child or null.
+ */
+ public View getFocusedChild() {
+ return mFocused;
+ }
+
+ View getDeepestFocusedChild() {
+ View v = this;
+ while (v != null) {
+ if (v.isFocused()) {
+ return v;
+ }
+ v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null;
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if this view has or contains focus
+ *
+ * @return true if this view has or contains focus
+ */
+ @Override
+ public boolean hasFocus() {
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View#findFocus()
+ */
+ @Override
+ public View findFocus() {
+ if (DBG) {
+ System.out.println("Find focus in " + this + ": flags="
+ + isFocused() + ", child=" + mFocused);
+ }
+
+ if (isFocused()) {
+ return this;
+ }
+
+ if (mFocused != null) {
+ return mFocused.findFocus();
+ }
+ return null;
+ }
+
+ @Override
+ boolean hasFocusable(boolean allowAutoFocus, boolean dispatchExplicit) {
+ // This should probably be super.hasFocusable, but that would change
+ // behavior. Historically, we have not checked the ancestor views for
+ // shouldBlockFocusForTouchscreen() in ViewGroup.hasFocusable.
+
+ // Invisible and gone views are never focusable.
+ if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+ return false;
+ }
+
+ // Only use effective focusable value when allowed.
+ if ((allowAutoFocus || getFocusable() != FOCUSABLE_AUTO) && isFocusable()) {
+ return true;
+ }
+
+ // Determine whether we have a focused descendant.
+ final int descendantFocusability = getDescendantFocusability();
+ if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+ return hasFocusableChild(dispatchExplicit);
+ }
+
+ return false;
+ }
+
+ boolean hasFocusableChild(boolean dispatchExplicit) {
+ // Determine whether we have a focusable descendant.
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+
+ // In case the subclass has overridden has[Explicit]Focusable, dispatch
+ // to the expected one for each child even though we share logic here.
+ if ((dispatchExplicit && child.hasExplicitFocusable())
+ || (!dispatchExplicit && child.hasFocusable())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ final int focusableCount = views.size();
+
+ final int descendantFocusability = getDescendantFocusability();
+ final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
+ final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
+
+ if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
+ if (focusSelf) {
+ super.addFocusables(views, direction, focusableMode);
+ }
+ return;
+ }
+
+ if (blockFocusForTouchscreen) {
+ focusableMode |= FOCUSABLES_TOUCH_MODE;
+ }
+
+ if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
+ super.addFocusables(views, direction, focusableMode);
+ }
+
+ int count = 0;
+ final View[] children = new View[mChildrenCount];
+ for (int i = 0; i < mChildrenCount; ++i) {
+ View child = mChildren[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ children[count++] = child;
+ }
+ }
+ FocusFinder.sort(children, 0, count, this, isLayoutRtl());
+ for (int i = 0; i < count; ++i) {
+ children[i].addFocusables(views, direction, focusableMode);
+ }
+
+ // When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
+ // there aren't any focusable descendants. this is
+ // to avoid the focus search finding layouts when a more precise search
+ // among the focusable children would be more interesting.
+ if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
+ && focusableCount == views.size()) {
+ super.addFocusables(views, direction, focusableMode);
+ }
+ }
+
+ @Override
+ public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
+ final int focusableCount = views.size();
+
+ if (isKeyboardNavigationCluster()) {
+ // Cluster-navigation can enter a touchscreenBlocksFocus cluster, so temporarily
+ // disable touchscreenBlocksFocus to evaluate whether it contains focusables.
+ final boolean blockedFocus = getTouchscreenBlocksFocus();
+ try {
+ setTouchscreenBlocksFocusNoRefocus(false);
+ super.addKeyboardNavigationClusters(views, direction);
+ } finally {
+ setTouchscreenBlocksFocusNoRefocus(blockedFocus);
+ }
+ } else {
+ super.addKeyboardNavigationClusters(views, direction);
+ }
+
+ if (focusableCount != views.size()) {
+ // No need to look for groups inside a group.
+ return;
+ }
+
+ if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+ return;
+ }
+
+ int count = 0;
+ final View[] visibleChildren = new View[mChildrenCount];
+ for (int i = 0; i < mChildrenCount; ++i) {
+ final View child = mChildren[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ visibleChildren[count++] = child;
+ }
+ }
+ FocusFinder.sort(visibleChildren, 0, count, this, isLayoutRtl());
+ for (int i = 0; i < count; ++i) {
+ visibleChildren[i].addKeyboardNavigationClusters(views, direction);
+ }
+ }
+
+ /**
+ * Set whether this ViewGroup should ignore focus requests for itself and its children.
+ * If this option is enabled and the ViewGroup or a descendant currently has focus, focus
+ * will proceed forward.
+ *
+ * @param touchscreenBlocksFocus true to enable blocking focus in the presence of a touchscreen
+ */
+ public void setTouchscreenBlocksFocus(boolean touchscreenBlocksFocus) {
+ if (touchscreenBlocksFocus) {
+ mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+ if (hasFocus() && !isKeyboardNavigationCluster()) {
+ final View focusedChild = getDeepestFocusedChild();
+ if (!focusedChild.isFocusableInTouchMode()) {
+ final View newFocus = focusSearch(FOCUS_FORWARD);
+ if (newFocus != null) {
+ newFocus.requestFocus();
+ }
+ }
+ }
+ } else {
+ mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+ }
+ }
+
+ private void setTouchscreenBlocksFocusNoRefocus(boolean touchscreenBlocksFocus) {
+ if (touchscreenBlocksFocus) {
+ mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+ } else {
+ mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+ }
+ }
+
+ /**
+ * Check whether this ViewGroup should ignore focus requests for itself and its children.
+ */
+ @ViewDebug.ExportedProperty(category = "focus")
+ public boolean getTouchscreenBlocksFocus() {
+ return (mGroupFlags & FLAG_TOUCHSCREEN_BLOCKS_FOCUS) != 0;
+ }
+
+ boolean shouldBlockFocusForTouchscreen() {
+ // There is a special case for keyboard-navigation clusters. We allow cluster navigation
+ // to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the
+ // cluster, focus is free to move around within it.
+ return getTouchscreenBlocksFocus() &&
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+ && !(isKeyboardNavigationCluster()
+ && (hasFocus() || (findKeyboardNavigationCluster() != this)));
+ }
+
+ @Override
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence text, int flags) {
+ super.findViewsWithText(outViews, text, flags);
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && (child.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+ child.findViewsWithText(outViews, text, flags);
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public View findViewByAccessibilityIdTraversal(int accessibilityId) {
+ View foundView = super.findViewByAccessibilityIdTraversal(accessibilityId);
+ if (foundView != null) {
+ return foundView;
+ }
+
+ if (getAccessibilityNodeProvider() != null) {
+ return null;
+ }
+
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ foundView = child.findViewByAccessibilityIdTraversal(accessibilityId);
+ if (foundView != null) {
+ return foundView;
+ }
+ }
+
+ return null;
+ }
+
+ /** @hide */
+ @Override
+ public View findViewByAutofillIdTraversal(int autofillId) {
+ View foundView = super.findViewByAutofillIdTraversal(autofillId);
+ if (foundView != null) {
+ return foundView;
+ }
+
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ foundView = child.findViewByAutofillIdTraversal(autofillId);
+ if (foundView != null) {
+ return foundView;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void dispatchWindowFocusChanged(boolean hasFocus) {
+ super.dispatchWindowFocusChanged(hasFocus);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchWindowFocusChanged(hasFocus);
+ }
+ }
+
+ @Override
+ public void addTouchables(ArrayList<View> views) {
+ super.addTouchables(views);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ child.addTouchables(views);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void makeOptionalFitsSystemWindows() {
+ super.makeOptionalFitsSystemWindows();
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].makeOptionalFitsSystemWindows();
+ }
+ }
+
+ @Override
+ public void dispatchDisplayHint(int hint) {
+ super.dispatchDisplayHint(hint);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchDisplayHint(hint);
+ }
+ }
+
+ /**
+ * Called when a view's visibility has changed. Notify the parent to take any appropriate
+ * action.
+ *
+ * @param child The view whose visibility has changed
+ * @param oldVisibility The previous visibility value (GONE, INVISIBLE, or VISIBLE).
+ * @param newVisibility The new visibility value (GONE, INVISIBLE, or VISIBLE).
+ * @hide
+ */
+ protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
+ if (mTransition != null) {
+ if (newVisibility == VISIBLE) {
+ mTransition.showChild(this, child, oldVisibility);
+ } else {
+ mTransition.hideChild(this, child, newVisibility);
+ if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
+ // Only track this on disappearing views - appearing views are already visible
+ // and don't need special handling during drawChild()
+ if (mVisibilityChangingChildren == null) {
+ mVisibilityChangingChildren = new ArrayList<View>();
+ }
+ mVisibilityChangingChildren.add(child);
+ addDisappearingView(child);
+ }
+ }
+ }
+
+ // in all cases, for drags
+ if (newVisibility == VISIBLE && mCurrentDragStartEvent != null) {
+ if (!mChildrenInterestedInDrag.contains(child)) {
+ notifyChildOfDragStart(child);
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchVisibilityChanged(View changedView, int visibility) {
+ super.dispatchVisibilityChanged(changedView, visibility);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchVisibilityChanged(changedView, visibility);
+ }
+ }
+
+ @Override
+ public void dispatchWindowVisibilityChanged(int visibility) {
+ super.dispatchWindowVisibilityChanged(visibility);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchWindowVisibilityChanged(visibility);
+ }
+ }
+
+ @Override
+ boolean dispatchVisibilityAggregated(boolean isVisible) {
+ isVisible = super.dispatchVisibilityAggregated(isVisible);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ // Only dispatch to visible children. Not visible children and their subtrees already
+ // know that they aren't visible and that's not going to change as a result of
+ // whatever triggered this dispatch.
+ if (children[i].getVisibility() == VISIBLE) {
+ children[i].dispatchVisibilityAggregated(isVisible);
+ }
+ }
+ return isVisible;
+ }
+
+ @Override
+ public void dispatchConfigurationChanged(Configuration newConfig) {
+ super.dispatchConfigurationChanged(newConfig);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchConfigurationChanged(newConfig);
+ }
+ }
+
+ @Override
+ public void recomputeViewAttributes(View child) {
+ if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
+ ViewParent parent = mParent;
+ if (parent != null) parent.recomputeViewAttributes(this);
+ }
+ }
+
+ @Override
+ void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+ if ((visibility & VISIBILITY_MASK) == VISIBLE) {
+ super.dispatchCollectViewAttributes(attachInfo, visibility);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ child.dispatchCollectViewAttributes(attachInfo,
+ visibility | (child.mViewFlags&VISIBILITY_MASK));
+ }
+ }
+ }
+
+ @Override
+ public void bringChildToFront(View child) {
+ final int index = indexOfChild(child);
+ if (index >= 0) {
+ removeFromArray(index);
+ addInArray(child, mChildrenCount);
+ child.mParent = this;
+ requestLayout();
+ invalidate();
+ }
+ }
+
+ private PointF getLocalPoint() {
+ if (mLocalPoint == null) mLocalPoint = new PointF();
+ return mLocalPoint;
+ }
+
+ @Override
+ boolean dispatchDragEnterExitInPreN(DragEvent event) {
+ if (event.mAction == DragEvent.ACTION_DRAG_EXITED && mCurrentDragChild != null) {
+ // The drag exited a sub-tree of views; notify of the exit all descendants that are in
+ // entered state.
+ // We don't need this recursive delivery for ENTERED events because they get generated
+ // from the recursive delivery of LOCATION/DROP events, and hence, don't need their own
+ // recursion.
+ mCurrentDragChild.dispatchDragEnterExitInPreN(event);
+ mCurrentDragChild = null;
+ }
+ return mIsInterestedInDrag && super.dispatchDragEnterExitInPreN(event);
+ }
+
+ // TODO: Write real docs
+ @Override
+ public boolean dispatchDragEvent(DragEvent event) {
+ boolean retval = false;
+ final float tx = event.mX;
+ final float ty = event.mY;
+ final ClipData td = event.mClipData;
+
+ // Dispatch down the view hierarchy
+ final PointF localPoint = getLocalPoint();
+
+ switch (event.mAction) {
+ case DragEvent.ACTION_DRAG_STARTED: {
+ // Clear the state to recalculate which views we drag over.
+ mCurrentDragChild = null;
+
+ // Set up our tracking of drag-started notifications
+ mCurrentDragStartEvent = DragEvent.obtain(event);
+ if (mChildrenInterestedInDrag == null) {
+ mChildrenInterestedInDrag = new HashSet<View>();
+ } else {
+ mChildrenInterestedInDrag.clear();
+ }
+
+ // Now dispatch down to our children, caching the responses
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ child.mPrivateFlags2 &= ~View.DRAG_MASK;
+ if (child.getVisibility() == VISIBLE) {
+ if (notifyChildOfDragStart(children[i])) {
+ retval = true;
+ }
+ }
+ }
+
+ // Notify itself of the drag start.
+ mIsInterestedInDrag = super.dispatchDragEvent(event);
+ if (mIsInterestedInDrag) {
+ retval = true;
+ }
+
+ if (!retval) {
+ // Neither us nor any of our children are interested in this drag, so stop tracking
+ // the current drag event.
+ mCurrentDragStartEvent.recycle();
+ mCurrentDragStartEvent = null;
+ }
+ } break;
+
+ case DragEvent.ACTION_DRAG_ENDED: {
+ // Release the bookkeeping now that the drag lifecycle has ended
+ final HashSet<View> childrenInterestedInDrag = mChildrenInterestedInDrag;
+ if (childrenInterestedInDrag != null) {
+ for (View child : childrenInterestedInDrag) {
+ // If a child was interested in the ongoing drag, it's told that it's over
+ if (child.dispatchDragEvent(event)) {
+ retval = true;
+ }
+ }
+ childrenInterestedInDrag.clear();
+ }
+ if (mCurrentDragStartEvent != null) {
+ mCurrentDragStartEvent.recycle();
+ mCurrentDragStartEvent = null;
+ }
+
+ if (mIsInterestedInDrag) {
+ if (super.dispatchDragEvent(event)) {
+ retval = true;
+ }
+ mIsInterestedInDrag = false;
+ }
+ } break;
+
+ case DragEvent.ACTION_DRAG_LOCATION:
+ case DragEvent.ACTION_DROP: {
+ // Find the [possibly new] drag target
+ View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint);
+
+ if (target != mCurrentDragChild) {
+ if (sCascadedDragDrop) {
+ // For pre-Nougat apps, make sure that the whole hierarchy of views that contain
+ // the drag location is kept in the state between ENTERED and EXITED events.
+ // (Starting with N, only the innermost view will be in that state).
+
+ final int action = event.mAction;
+ // Position should not be available for ACTION_DRAG_ENTERED and
+ // ACTION_DRAG_EXITED.
+ event.mX = 0;
+ event.mY = 0;
+ event.mClipData = null;
+
+ if (mCurrentDragChild != null) {
+ event.mAction = DragEvent.ACTION_DRAG_EXITED;
+ mCurrentDragChild.dispatchDragEnterExitInPreN(event);
+ }
+
+ if (target != null) {
+ event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+ target.dispatchDragEnterExitInPreN(event);
+ }
+
+ event.mAction = action;
+ event.mX = tx;
+ event.mY = ty;
+ event.mClipData = td;
+ }
+ mCurrentDragChild = target;
+ }
+
+ if (target == null && mIsInterestedInDrag) {
+ target = this;
+ }
+
+ // Dispatch the actual drag notice, localized into the target coordinates.
+ if (target != null) {
+ if (target != this) {
+ event.mX = localPoint.x;
+ event.mY = localPoint.y;
+
+ retval = target.dispatchDragEvent(event);
+
+ event.mX = tx;
+ event.mY = ty;
+
+ if (mIsInterestedInDrag) {
+ final boolean eventWasConsumed;
+ if (sCascadedDragDrop) {
+ eventWasConsumed = retval;
+ } else {
+ eventWasConsumed = event.mEventHandlerWasCalled;
+ }
+
+ if (!eventWasConsumed) {
+ retval = super.dispatchDragEvent(event);
+ }
+ }
+ } else {
+ retval = super.dispatchDragEvent(event);
+ }
+ }
+ } break;
+ }
+
+ return retval;
+ }
+
+ // Find the frontmost child view that lies under the given point, and calculate
+ // the position within its own local coordinate system.
+ View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (!child.canAcceptDrag()) {
+ continue;
+ }
+
+ if (isTransformedTouchPointInView(x, y, child, outLocalPoint)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ boolean notifyChildOfDragStart(View child) {
+ // The caller guarantees that the child is not in mChildrenInterestedInDrag yet.
+
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(View.VIEW_LOG_TAG, "Sending drag-started to view: " + child);
+ }
+
+ final float tx = mCurrentDragStartEvent.mX;
+ final float ty = mCurrentDragStartEvent.mY;
+
+ final float[] point = getTempPoint();
+ point[0] = tx;
+ point[1] = ty;
+ transformPointToViewLocal(point, child);
+
+ mCurrentDragStartEvent.mX = point[0];
+ mCurrentDragStartEvent.mY = point[1];
+ final boolean canAccept = child.dispatchDragEvent(mCurrentDragStartEvent);
+ mCurrentDragStartEvent.mX = tx;
+ mCurrentDragStartEvent.mY = ty;
+ mCurrentDragStartEvent.mEventHandlerWasCalled = false;
+ if (canAccept) {
+ mChildrenInterestedInDrag.add(child);
+ if (!child.canAcceptDrag()) {
+ child.mPrivateFlags2 |= View.PFLAG2_DRAG_CAN_ACCEPT;
+ child.refreshDrawableState();
+ }
+ }
+ return canAccept;
+ }
+
+ @Override
+ public void dispatchWindowSystemUiVisiblityChanged(int visible) {
+ super.dispatchWindowSystemUiVisiblityChanged(visible);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i=0; i <count; i++) {
+ final View child = children[i];
+ child.dispatchWindowSystemUiVisiblityChanged(visible);
+ }
+ }
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int visible) {
+ super.dispatchSystemUiVisibilityChanged(visible);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i=0; i <count; i++) {
+ final View child = children[i];
+ child.dispatchSystemUiVisibilityChanged(visible);
+ }
+ }
+
+ @Override
+ boolean updateLocalSystemUiVisibility(int localValue, int localChanges) {
+ boolean changed = super.updateLocalSystemUiVisibility(localValue, localChanges);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i=0; i <count; i++) {
+ final View child = children[i];
+ changed |= child.updateLocalSystemUiVisibility(localValue, localChanges);
+ }
+ return changed;
+ }
+
+ @Override
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ return super.dispatchKeyEventPreIme(event);
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ return mFocused.dispatchKeyEventPreIme(event);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 1);
+ }
+
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ if (mFocused.dispatchKeyEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ return super.dispatchKeyShortcutEvent(event);
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ return mFocused.dispatchKeyShortcutEvent(event);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 1);
+ }
+
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ if (mFocused.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ if (super.dispatchCapturedPointerEvent(event)) {
+ return true;
+ }
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ if (mFocused.dispatchCapturedPointerEvent(event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ exitHoverTargets();
+
+ super.dispatchPointerCaptureChanged(hasCapture);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
+ @Override
+ public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
+ return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+ }
+ // Check what the child under the pointer says about the pointer.
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+ final PointerIcon pointerIcon =
+ dispatchResolvePointerIcon(event, pointerIndex, child);
+ if (pointerIcon != null) {
+ if (preorderedList != null) preorderedList.clear();
+ return pointerIcon;
+ }
+ }
+ if (preorderedList != null) preorderedList.clear();
+ }
+
+ // The pointer is not a child or the child has no preferences, returning the default
+ // implementation.
+ return super.onResolvePointerIcon(event, pointerIndex);
+ }
+
+ private PointerIcon dispatchResolvePointerIcon(MotionEvent event, int pointerIndex,
+ View child) {
+ final PointerIcon pointerIcon;
+ if (!child.hasIdentityMatrix()) {
+ MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+ pointerIcon = child.onResolvePointerIcon(transformedEvent, pointerIndex);
+ transformedEvent.recycle();
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ event.offsetLocation(offsetX, offsetY);
+ pointerIcon = child.onResolvePointerIcon(event, pointerIndex);
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return pointerIcon;
+ }
+
+ private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
+ final int childIndex;
+ if (customOrder) {
+ final int childIndex1 = getChildDrawingOrder(childrenCount, i);
+ if (childIndex1 >= childrenCount) {
+ throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+ + "returned invalid index " + childIndex1
+ + " (child count is " + childrenCount + ")");
+ }
+ childIndex = childIndex1;
+ } else {
+ childIndex = i;
+ }
+ return childIndex;
+ }
+
+ @SuppressWarnings({"ConstantConditions"})
+ @Override
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ final int action = event.getAction();
+
+ // First check whether the view group wants to intercept the hover event.
+ final boolean interceptHover = onInterceptHoverEvent(event);
+ event.setAction(action); // restore action in case it was changed
+
+ MotionEvent eventNoHistory = event;
+ boolean handled = false;
+
+ // Send events to the hovered children and build a new list of hover targets until
+ // one is found that handles the event.
+ HoverTarget firstOldHoverTarget = mFirstHoverTarget;
+ mFirstHoverTarget = null;
+ if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) {
+ final float x = event.getX();
+ final float y = event.getY();
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ HoverTarget lastHoverTarget = null;
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final int childIndex = getAndVerifyPreorderedIndex(
+ childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(
+ preorderedList, children, childIndex);
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+
+ // Obtain a hover target for this child. Dequeue it from the
+ // old hover target list if the child was previously hovered.
+ HoverTarget hoverTarget = firstOldHoverTarget;
+ final boolean wasHovered;
+ for (HoverTarget predecessor = null; ;) {
+ if (hoverTarget == null) {
+ hoverTarget = HoverTarget.obtain(child);
+ wasHovered = false;
+ break;
+ }
+
+ if (hoverTarget.child == child) {
+ if (predecessor != null) {
+ predecessor.next = hoverTarget.next;
+ } else {
+ firstOldHoverTarget = hoverTarget.next;
+ }
+ hoverTarget.next = null;
+ wasHovered = true;
+ break;
+ }
+
+ predecessor = hoverTarget;
+ hoverTarget = hoverTarget.next;
+ }
+
+ // Enqueue the hover target onto the new hover target list.
+ if (lastHoverTarget != null) {
+ lastHoverTarget.next = hoverTarget;
+ } else {
+ mFirstHoverTarget = hoverTarget;
+ }
+ lastHoverTarget = hoverTarget;
+
+ // Dispatch the event to the child.
+ if (action == MotionEvent.ACTION_HOVER_ENTER) {
+ if (!wasHovered) {
+ // Send the enter as is.
+ handled |= dispatchTransformedGenericPointerEvent(
+ event, child); // enter
+ }
+ } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ if (!wasHovered) {
+ // Synthesize an enter from a move.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled |= dispatchTransformedGenericPointerEvent(
+ eventNoHistory, child); // enter
+ eventNoHistory.setAction(action);
+
+ handled |= dispatchTransformedGenericPointerEvent(
+ eventNoHistory, child); // move
+ } else {
+ // Send the move as is.
+ handled |= dispatchTransformedGenericPointerEvent(event, child);
+ }
+ }
+ if (handled) {
+ break;
+ }
+ }
+ if (preorderedList != null) preorderedList.clear();
+ }
+ }
+
+ // Send exit events to all previously hovered children that are no longer hovered.
+ while (firstOldHoverTarget != null) {
+ final View child = firstOldHoverTarget.child;
+
+ // Exit the old hovered child.
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ // Send the exit as is.
+ handled |= dispatchTransformedGenericPointerEvent(
+ event, child); // exit
+ } else {
+ // Synthesize an exit from a move or enter.
+ // Ignore the result because hover focus has moved to a different view.
+ if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ final boolean hoverExitPending = event.isHoverExitPending();
+ event.setHoverExitPending(true);
+ dispatchTransformedGenericPointerEvent(
+ event, child); // move
+ event.setHoverExitPending(hoverExitPending);
+ }
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ dispatchTransformedGenericPointerEvent(
+ eventNoHistory, child); // exit
+ eventNoHistory.setAction(action);
+ }
+
+ final HoverTarget nextOldHoverTarget = firstOldHoverTarget.next;
+ firstOldHoverTarget.recycle();
+ firstOldHoverTarget = nextOldHoverTarget;
+ }
+
+ // Send events to the view group itself if no children have handled it and the view group
+ // itself is not currently being hover-exited.
+ boolean newHoveredSelf = !handled &&
+ (action != MotionEvent.ACTION_HOVER_EXIT) && !event.isHoverExitPending();
+ if (newHoveredSelf == mHoveredSelf) {
+ if (newHoveredSelf) {
+ // Send event to the view group as before.
+ handled |= super.dispatchHoverEvent(event);
+ }
+ } else {
+ if (mHoveredSelf) {
+ // Exit the view group.
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ // Send the exit as is.
+ handled |= super.dispatchHoverEvent(event); // exit
+ } else {
+ // Synthesize an exit from a move or enter.
+ // Ignore the result because hover focus is moving to a different view.
+ if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ super.dispatchHoverEvent(event); // move
+ }
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ super.dispatchHoverEvent(eventNoHistory); // exit
+ eventNoHistory.setAction(action);
+ }
+ mHoveredSelf = false;
+ }
+
+ if (newHoveredSelf) {
+ // Enter the view group.
+ if (action == MotionEvent.ACTION_HOVER_ENTER) {
+ // Send the enter as is.
+ handled |= super.dispatchHoverEvent(event); // enter
+ mHoveredSelf = true;
+ } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ // Synthesize an enter from a move.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled |= super.dispatchHoverEvent(eventNoHistory); // enter
+ eventNoHistory.setAction(action);
+
+ handled |= super.dispatchHoverEvent(eventNoHistory); // move
+ mHoveredSelf = true;
+ }
+ }
+ }
+
+ // Recycle the copy of the event that we made.
+ if (eventNoHistory != event) {
+ eventNoHistory.recycle();
+ }
+
+ // Done.
+ return handled;
+ }
+
+ private void exitHoverTargets() {
+ if (mHoveredSelf || mFirstHoverTarget != null) {
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ dispatchHoverEvent(event);
+ event.recycle();
+ }
+ }
+
+ private void cancelHoverTarget(View view) {
+ HoverTarget predecessor = null;
+ HoverTarget target = mFirstHoverTarget;
+ while (target != null) {
+ final HoverTarget next = target.next;
+ if (target.child == view) {
+ if (predecessor == null) {
+ mFirstHoverTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ view.dispatchHoverEvent(event);
+ event.recycle();
+ return;
+ }
+ predecessor = target;
+ target = next;
+ }
+ }
+
+ @Override
+ boolean dispatchTooltipHoverEvent(MotionEvent event) {
+ final int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ break;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ View newTarget = null;
+
+ // Check what the child under the pointer says about the tooltip.
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final int childIndex =
+ getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child =
+ getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+ if (dispatchTooltipHoverEvent(event, child)) {
+ newTarget = child;
+ break;
+ }
+ }
+ if (preorderedList != null) preorderedList.clear();
+ }
+
+ if (mTooltipHoverTarget != newTarget) {
+ if (mTooltipHoverTarget != null) {
+ event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+ event.setAction(action);
+ }
+ mTooltipHoverTarget = newTarget;
+ }
+
+ if (mTooltipHoverTarget != null) {
+ if (mTooltipHoveredSelf) {
+ mTooltipHoveredSelf = false;
+ event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ super.dispatchTooltipHoverEvent(event);
+ event.setAction(action);
+ }
+ return true;
+ }
+
+ mTooltipHoveredSelf = super.dispatchTooltipHoverEvent(event);
+ return mTooltipHoveredSelf;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (mTooltipHoverTarget != null) {
+ mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+ mTooltipHoverTarget = null;
+ } else if (mTooltipHoveredSelf) {
+ super.dispatchTooltipHoverEvent(event);
+ mTooltipHoveredSelf = false;
+ }
+ break;
+ }
+ return false;
+ }
+
+ private boolean dispatchTooltipHoverEvent(MotionEvent event, View child) {
+ final boolean result;
+ if (!child.hasIdentityMatrix()) {
+ MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+ result = child.dispatchTooltipHoverEvent(transformedEvent);
+ transformedEvent.recycle();
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ event.offsetLocation(offsetX, offsetY);
+ result = child.dispatchTooltipHoverEvent(event);
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return result;
+ }
+
+ private void exitTooltipHoverTargets() {
+ if (mTooltipHoveredSelf || mTooltipHoverTarget != null) {
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ dispatchTooltipHoverEvent(event);
+ event.recycle();
+ }
+ }
+
+ /** @hide */
+ @Override
+ protected boolean hasHoveredChild() {
+ return mFirstHoverTarget != null;
+ }
+
+ @Override
+ public void addChildrenForAccessibility(ArrayList<View> outChildren) {
+ if (getAccessibilityNodeProvider() != null) {
+ return;
+ }
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(this, true);
+ try {
+ final int childrenCount = children.getChildCount();
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children.getChildAt(i);
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ if (child.includeForAccessibility()) {
+ outChildren.add(child);
+ } else {
+ child.addChildrenForAccessibility(outChildren);
+ }
+ }
+ }
+ } finally {
+ children.recycle();
+ }
+ }
+
+ /**
+ * Implement this method to intercept hover events before they are handled
+ * by child views.
+ * <p>
+ * This method is called before dispatching a hover event to a child of
+ * the view group or to the view group's own {@link #onHoverEvent} to allow
+ * the view group a chance to intercept the hover event.
+ * This method can also be used to watch all pointer motions that occur within
+ * the bounds of the view group even when the pointer is hovering over
+ * a child of the view group rather than over the view group itself.
+ * </p><p>
+ * The view group can prevent its children from receiving hover events by
+ * implementing this method and returning <code>true</code> to indicate
+ * that it would like to intercept hover events. The view group must
+ * continuously return <code>true</code> from {@link #onInterceptHoverEvent}
+ * for as long as it wishes to continue intercepting hover events from
+ * its children.
+ * </p><p>
+ * Interception preserves the invariant that at most one view can be
+ * hovered at a time by transferring hover focus from the currently hovered
+ * child to the view group or vice-versa as needed.
+ * </p><p>
+ * If this method returns <code>true</code> and a child is already hovered, then the
+ * child view will first receive a hover exit event and then the view group
+ * itself will receive a hover enter event in {@link #onHoverEvent}.
+ * Likewise, if this method had previously returned <code>true</code> to intercept hover
+ * events and instead returns <code>false</code> while the pointer is hovering
+ * within the bounds of one of a child, then the view group will first receive a
+ * hover exit event in {@link #onHoverEvent} and then the hovered child will
+ * receive a hover enter event.
+ * </p><p>
+ * The default implementation handles mouse hover on the scroll bars.
+ * </p>
+ *
+ * @param event The motion event that describes the hover.
+ * @return True if the view group would like to intercept the hover event
+ * and prevent its children from receiving it.
+ */
+ public boolean onInterceptHoverEvent(MotionEvent event) {
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+ if ((action == MotionEvent.ACTION_HOVER_MOVE
+ || action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
+ if (event.getHistorySize() == 0) {
+ return event;
+ }
+ return MotionEvent.obtainNoHistory(event);
+ }
+
+ @Override
+ protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+ // Send the event to the child under the pointer.
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+
+ if (dispatchTransformedGenericPointerEvent(event, child)) {
+ if (preorderedList != null) preorderedList.clear();
+ return true;
+ }
+ }
+ if (preorderedList != null) preorderedList.clear();
+ }
+
+ // No child handled the event. Send it to this view group.
+ return super.dispatchGenericPointerEvent(event);
+ }
+
+ @Override
+ protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
+ // Send the event to the focused child or to this view group if it has focus.
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ return super.dispatchGenericFocusedEvent(event);
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ return mFocused.dispatchGenericMotionEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * Dispatches a generic pointer event to a child, taking into account
+ * transformations that apply to the child.
+ *
+ * @param event The event to send.
+ * @param child The view to send the event to.
+ * @return {@code true} if the child handled the event.
+ */
+ private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
+ boolean handled;
+ if (!child.hasIdentityMatrix()) {
+ MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+ handled = child.dispatchGenericMotionEvent(transformedEvent);
+ transformedEvent.recycle();
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ event.offsetLocation(offsetX, offsetY);
+ handled = child.dispatchGenericMotionEvent(event);
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return handled;
+ }
+
+ /**
+ * Returns a MotionEvent that's been transformed into the child's local coordinates.
+ *
+ * It's the responsibility of the caller to recycle it once they're finished with it.
+ * @param event The event to transform.
+ * @param child The view whose coordinate space is to be used.
+ * @return A copy of the the given MotionEvent, transformed into the given View's coordinate
+ * space.
+ */
+ private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ final MotionEvent transformedEvent = MotionEvent.obtain(event);
+ transformedEvent.offsetLocation(offsetX, offsetY);
+ if (!child.hasIdentityMatrix()) {
+ transformedEvent.transform(child.getInverseMatrix());
+ }
+ return transformedEvent;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
+ }
+
+ // If the event targets the accessibility focused view and this is it, start
+ // normal event dispatch. Maybe a descendant is what will handle the click.
+ if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
+ ev.setTargetAccessibilityFocus(false);
+ }
+
+ boolean handled = false;
+ if (onFilterTouchEventForSecurity(ev)) {
+ final int action = ev.getAction();
+ final int actionMasked = action & MotionEvent.ACTION_MASK;
+
+ // Handle an initial down.
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Throw away all previous state when starting a new touch gesture.
+ // The framework may have dropped the up or cancel event for the previous gesture
+ // due to an app switch, ANR, or some other state change.
+ cancelAndClearTouchTargets(ev);
+ resetTouchState();
+ }
+
+ // Check for interception.
+ final boolean intercepted;
+ if (actionMasked == MotionEvent.ACTION_DOWN
+ || mFirstTouchTarget != null) {
+ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+ if (!disallowIntercept) {
+ intercepted = onInterceptTouchEvent(ev);
+ ev.setAction(action); // restore action in case it was changed
+ } else {
+ intercepted = false;
+ }
+ } else {
+ // There are no touch targets and this action is not an initial down
+ // so this view group continues to intercept touches.
+ intercepted = true;
+ }
+
+ // If intercepted, start normal event dispatch. Also if there is already
+ // a view that is handling the gesture, do normal event dispatch.
+ if (intercepted || mFirstTouchTarget != null) {
+ ev.setTargetAccessibilityFocus(false);
+ }
+
+ // Check for cancelation.
+ final boolean canceled = resetCancelNextUpFlag(this)
+ || actionMasked == MotionEvent.ACTION_CANCEL;
+
+ // Update list of touch targets for pointer down, if needed.
+ final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+ TouchTarget newTouchTarget = null;
+ boolean alreadyDispatchedToNewTouchTarget = false;
+ if (!canceled && !intercepted) {
+
+ // If the event is targeting accessiiblity focus we give it to the
+ // view that has accessibility focus and if it does not handle it
+ // we clear the flag and dispatch the event to all children as usual.
+ // We are looking up the accessibility focused host to avoid keeping
+ // state since these events are very rare.
+ View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
+ ? findChildWithAccessibilityFocus() : null;
+
+ if (actionMasked == MotionEvent.ACTION_DOWN
+ || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ final int actionIndex = ev.getActionIndex(); // always 0 for down
+ final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+ : TouchTarget.ALL_POINTER_IDS;
+
+ // Clean up earlier touch targets for this pointer id in case they
+ // have become out of sync.
+ removePointersFromTouchTargets(idBitsToAssign);
+
+ final int childrenCount = mChildrenCount;
+ if (newTouchTarget == null && childrenCount != 0) {
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+ // Find a child that can receive the event.
+ // Scan children from front to back.
+ final ArrayList<View> preorderedList = buildTouchDispatchChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final int childIndex = getAndVerifyPreorderedIndex(
+ childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(
+ preorderedList, children, childIndex);
+
+ // If there is a view that has accessibility focus we want it
+ // to get the event first and if not handled we will perform a
+ // normal dispatch. We may do a double iteration but this is
+ // safer given the timeframe.
+ if (childWithAccessibilityFocus != null) {
+ if (childWithAccessibilityFocus != child) {
+ continue;
+ }
+ childWithAccessibilityFocus = null;
+ i = childrenCount - 1;
+ }
+
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ ev.setTargetAccessibilityFocus(false);
+ continue;
+ }
+
+ newTouchTarget = getTouchTarget(child);
+ if (newTouchTarget != null) {
+ // Child is already receiving touch within its bounds.
+ // Give it the new pointer in addition to the ones it is handling.
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ break;
+ }
+
+ resetCancelNextUpFlag(child);
+ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+ // Child wants to receive touch within its bounds.
+ mLastTouchDownTime = ev.getDownTime();
+ if (preorderedList != null) {
+ // childIndex points into presorted list, find original index
+ for (int j = 0; j < childrenCount; j++) {
+ if (children[childIndex] == mChildren[j]) {
+ mLastTouchDownIndex = j;
+ break;
+ }
+ }
+ } else {
+ mLastTouchDownIndex = childIndex;
+ }
+ mLastTouchDownX = ev.getX();
+ mLastTouchDownY = ev.getY();
+ newTouchTarget = addTouchTarget(child, idBitsToAssign);
+ alreadyDispatchedToNewTouchTarget = true;
+ break;
+ }
+
+ // The accessibility focus didn't handle the event, so clear
+ // the flag and do a normal dispatch to all children.
+ ev.setTargetAccessibilityFocus(false);
+ }
+ if (preorderedList != null) preorderedList.clear();
+ }
+
+ if (newTouchTarget == null && mFirstTouchTarget != null) {
+ // Did not find a child to receive the event.
+ // Assign the pointer to the least recently added target.
+ newTouchTarget = mFirstTouchTarget;
+ while (newTouchTarget.next != null) {
+ newTouchTarget = newTouchTarget.next;
+ }
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ }
+ }
+ }
+
+ // Dispatch to touch targets.
+ if (mFirstTouchTarget == null) {
+ // No touch targets so treat this as an ordinary view.
+ handled = dispatchTransformedTouchEvent(ev, canceled, null,
+ TouchTarget.ALL_POINTER_IDS);
+ } else {
+ // Dispatch to touch targets, excluding the new touch target if we already
+ // dispatched to it. Cancel touch targets if necessary.
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+ handled = true;
+ } else {
+ final boolean cancelChild = resetCancelNextUpFlag(target.child)
+ || intercepted;
+ if (dispatchTransformedTouchEvent(ev, cancelChild,
+ target.child, target.pointerIdBits)) {
+ handled = true;
+ }
+ if (cancelChild) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
+ continue;
+ }
+ }
+ predecessor = target;
+ target = next;
+ }
+ }
+
+ // Update list of touch targets for pointer up or cancel, if needed.
+ if (canceled
+ || actionMasked == MotionEvent.ACTION_UP
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ resetTouchState();
+ } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ final int actionIndex = ev.getActionIndex();
+ final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+ removePointersFromTouchTargets(idBitsToRemove);
+ }
+ }
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
+ }
+ return handled;
+ }
+
+ /**
+ * Provide custom ordering of views in which the touch will be dispatched.
+ *
+ * This is called within a tight loop, so you are not allowed to allocate objects, including
+ * the return array. Instead, you should return a pre-allocated list that will be cleared
+ * after the dispatch is finished.
+ * @hide
+ */
+ public ArrayList<View> buildTouchDispatchChildList() {
+ return buildOrderedChildList();
+ }
+
+ /**
+ * Finds the child which has accessibility focus.
+ *
+ * @return The child that has focus.
+ */
+ private View findChildWithAccessibilityFocus() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null) {
+ return null;
+ }
+
+ View current = viewRoot.getAccessibilityFocusedHost();
+ if (current == null) {
+ return null;
+ }
+
+ ViewParent parent = current.getParent();
+ while (parent instanceof View) {
+ if (parent == this) {
+ return current;
+ }
+ current = (View) parent;
+ parent = current.getParent();
+ }
+
+ return null;
+ }
+
+ /**
+ * Resets all touch state in preparation for a new cycle.
+ */
+ private void resetTouchState() {
+ clearTouchTargets();
+ resetCancelNextUpFlag(this);
+ mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+ mNestedScrollAxes = SCROLL_AXIS_NONE;
+ }
+
+ /**
+ * Resets the cancel next up flag.
+ * Returns true if the flag was previously set.
+ */
+ private static boolean resetCancelNextUpFlag(@NonNull View view) {
+ if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
+ view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Clears all touch targets.
+ */
+ private void clearTouchTargets() {
+ TouchTarget target = mFirstTouchTarget;
+ if (target != null) {
+ do {
+ TouchTarget next = target.next;
+ target.recycle();
+ target = next;
+ } while (target != null);
+ mFirstTouchTarget = null;
+ }
+ }
+
+ /**
+ * Cancels and clears all touch targets.
+ */
+ private void cancelAndClearTouchTargets(MotionEvent event) {
+ if (mFirstTouchTarget != null) {
+ boolean syntheticEvent = false;
+ if (event == null) {
+ final long now = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ syntheticEvent = true;
+ }
+
+ for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+ resetCancelNextUpFlag(target.child);
+ dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
+ }
+ clearTouchTargets();
+
+ if (syntheticEvent) {
+ event.recycle();
+ }
+ }
+ }
+
+ /**
+ * Gets the touch target for specified child view.
+ * Returns null if not found.
+ */
+ private TouchTarget getTouchTarget(@NonNull View child) {
+ for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+ if (target.child == child) {
+ return target;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a touch target for specified child to the beginning of the list.
+ * Assumes the target child is not already present.
+ */
+ private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
+ final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
+ target.next = mFirstTouchTarget;
+ mFirstTouchTarget = target;
+ return target;
+ }
+
+ /**
+ * Removes the pointer ids from consideration.
+ */
+ private void removePointersFromTouchTargets(int pointerIdBits) {
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if ((target.pointerIdBits & pointerIdBits) != 0) {
+ target.pointerIdBits &= ~pointerIdBits;
+ if (target.pointerIdBits == 0) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
+ continue;
+ }
+ }
+ predecessor = target;
+ target = next;
+ }
+ }
+
+ private void cancelTouchTarget(View view) {
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (target.child == view) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ view.dispatchTouchEvent(event);
+ event.recycle();
+ return;
+ }
+ predecessor = target;
+ target = next;
+ }
+ }
+
+ /**
+ * Returns true if a child view can receive pointer events.
+ * @hide
+ */
+ private static boolean canViewReceivePointerEvents(@NonNull View child) {
+ return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ || child.getAnimation() != null;
+ }
+
+ private float[] getTempPoint() {
+ if (mTempPoint == null) {
+ mTempPoint = new float[2];
+ }
+ return mTempPoint;
+ }
+
+ /**
+ * Returns true if a child view contains the specified point when transformed
+ * into its coordinate space.
+ * Child must not be null.
+ * @hide
+ */
+ protected boolean isTransformedTouchPointInView(float x, float y, View child,
+ PointF outLocalPoint) {
+ final float[] point = getTempPoint();
+ point[0] = x;
+ point[1] = y;
+ transformPointToViewLocal(point, child);
+ final boolean isInView = child.pointInView(point[0], point[1]);
+ if (isInView && outLocalPoint != null) {
+ outLocalPoint.set(point[0], point[1]);
+ }
+ return isInView;
+ }
+
+ /**
+ * @hide
+ */
+ public void transformPointToViewLocal(float[] point, View child) {
+ point[0] += mScrollX - child.mLeft;
+ point[1] += mScrollY - child.mTop;
+
+ if (!child.hasIdentityMatrix()) {
+ child.getInverseMatrix().mapPoints(point);
+ }
+ }
+
+ /**
+ * Transforms a motion event into the coordinate space of a particular child view,
+ * filters out irrelevant pointer ids, and overrides its action if necessary.
+ * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
+ */
+ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+ View child, int desiredPointerIdBits) {
+ final boolean handled;
+
+ // Canceling motions is a special case. We don't need to perform any transformations
+ // or filtering. The important part is the action, not the contents.
+ final int oldAction = event.getAction();
+ if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ if (child == null) {
+ handled = super.dispatchTouchEvent(event);
+ } else {
+ handled = child.dispatchTouchEvent(event);
+ }
+ event.setAction(oldAction);
+ return handled;
+ }
+
+ // Calculate the number of pointers to deliver.
+ final int oldPointerIdBits = event.getPointerIdBits();
+ final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
+
+ // If for some reason we ended up in an inconsistent state where it looks like we
+ // might produce a motion event with no pointers in it, then drop the event.
+ if (newPointerIdBits == 0) {
+ return false;
+ }
+
+ // If the number of pointers is the same and we don't need to perform any fancy
+ // irreversible transformations, then we can reuse the motion event for this
+ // dispatch as long as we are careful to revert any changes we make.
+ // Otherwise we need to make a copy.
+ final MotionEvent transformedEvent;
+ if (newPointerIdBits == oldPointerIdBits) {
+ if (child == null || child.hasIdentityMatrix()) {
+ if (child == null) {
+ handled = super.dispatchTouchEvent(event);
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ event.offsetLocation(offsetX, offsetY);
+
+ handled = child.dispatchTouchEvent(event);
+
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return handled;
+ }
+ transformedEvent = MotionEvent.obtain(event);
+ } else {
+ transformedEvent = event.split(newPointerIdBits);
+ }
+
+ // Perform any necessary transformations and dispatch.
+ if (child == null) {
+ handled = super.dispatchTouchEvent(transformedEvent);
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ transformedEvent.offsetLocation(offsetX, offsetY);
+ if (! child.hasIdentityMatrix()) {
+ transformedEvent.transform(child.getInverseMatrix());
+ }
+
+ handled = child.dispatchTouchEvent(transformedEvent);
+ }
+
+ // Done.
+ transformedEvent.recycle();
+ return handled;
+ }
+
+ /**
+ * Enable or disable the splitting of MotionEvents to multiple children during touch event
+ * dispatch. This behavior is enabled by default for applications that target an
+ * SDK version of {@link Build.VERSION_CODES#HONEYCOMB} or newer.
+ *
+ * <p>When this option is enabled MotionEvents may be split and dispatched to different child
+ * views depending on where each pointer initially went down. This allows for user interactions
+ * such as scrolling two panes of content independently, chording of buttons, and performing
+ * independent gestures on different pieces of content.
+ *
+ * @param split <code>true</code> to allow MotionEvents to be split and dispatched to multiple
+ * child views. <code>false</code> to only allow one child view to be the target of
+ * any MotionEvent received by this ViewGroup.
+ * @attr ref android.R.styleable#ViewGroup_splitMotionEvents
+ */
+ public void setMotionEventSplittingEnabled(boolean split) {
+ // TODO Applications really shouldn't change this setting mid-touch event,
+ // but perhaps this should handle that case and send ACTION_CANCELs to any child views
+ // with gestures in progress when this is changed.
+ if (split) {
+ mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
+ } else {
+ mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
+ }
+ }
+
+ /**
+ * Returns true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
+ * @return true if MotionEvents dispatched to this ViewGroup can be split to multiple children.
+ */
+ public boolean isMotionEventSplittingEnabled() {
+ return (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS;
+ }
+
+ /**
+ * Returns true if this ViewGroup should be considered as a single entity for removal
+ * when executing an Activity transition. If this is false, child elements will move
+ * individually during the transition.
+ *
+ * @return True if the ViewGroup should be acted on together during an Activity transition.
+ * The default value is true when there is a non-null background or if
+ * {@link #getTransitionName()} is not null or if a
+ * non-null {@link android.view.ViewOutlineProvider} other than
+ * {@link android.view.ViewOutlineProvider#BACKGROUND} was given to
+ * {@link #setOutlineProvider(ViewOutlineProvider)} and false otherwise.
+ */
+ public boolean isTransitionGroup() {
+ if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) {
+ return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0);
+ } else {
+ final ViewOutlineProvider outlineProvider = getOutlineProvider();
+ return getBackground() != null || getTransitionName() != null ||
+ (outlineProvider != null && outlineProvider != ViewOutlineProvider.BACKGROUND);
+ }
+ }
+
+ /**
+ * Changes whether or not this ViewGroup should be treated as a single entity during
+ * Activity Transitions.
+ * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit
+ * in Activity transitions. If false, the ViewGroup won't transition,
+ * only its children. If true, the entire ViewGroup will transition
+ * together.
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
+ * android.util.Pair[])
+ */
+ public void setTransitionGroup(boolean isTransitionGroup) {
+ mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET;
+ if (isTransitionGroup) {
+ mGroupFlags |= FLAG_IS_TRANSITION_GROUP;
+ } else {
+ mGroupFlags &= ~FLAG_IS_TRANSITION_GROUP;
+ }
+ }
+
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+
+ if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
+ // We're already in this state, assume our ancestors are too
+ return;
+ }
+
+ if (disallowIntercept) {
+ mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
+ } else {
+ mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+ }
+
+ // Pass it up to our parent
+ if (mParent != null) {
+ mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ }
+
+ /**
+ * Implement this method to intercept all touch screen motion events. This
+ * allows you to watch events as they are dispatched to your children, and
+ * take ownership of the current gesture at any point.
+ *
+ * <p>Using this function takes some care, as it has a fairly complicated
+ * interaction with {@link View#onTouchEvent(MotionEvent)
+ * View.onTouchEvent(MotionEvent)}, and using it requires implementing
+ * that method as well as this one in the correct way. Events will be
+ * received in the following order:
+ *
+ * <ol>
+ * <li> You will receive the down event here.
+ * <li> The down event will be handled either by a child of this view
+ * group, or given to your own onTouchEvent() method to handle; this means
+ * you should implement onTouchEvent() to return true, so you will
+ * continue to see the rest of the gesture (instead of looking for
+ * a parent view to handle it). Also, by returning true from
+ * onTouchEvent(), you will not receive any following
+ * events in onInterceptTouchEvent() and all touch processing must
+ * happen in onTouchEvent() like normal.
+ * <li> For as long as you return false from this function, each following
+ * event (up to and including the final up) will be delivered first here
+ * and then to the target's onTouchEvent().
+ * <li> If you return true from here, you will not receive any
+ * following events: the target view will receive the same event but
+ * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
+ * events will be delivered to your onTouchEvent() method and no longer
+ * appear here.
+ * </ol>
+ *
+ * @param ev The motion event being dispatched down the hierarchy.
+ * @return Return true to steal motion events from the children and have
+ * them dispatched to this ViewGroup through onTouchEvent().
+ * The current target will receive an ACTION_CANCEL event, and no further
+ * messages will be delivered here.
+ */
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
+ && ev.getAction() == MotionEvent.ACTION_DOWN
+ && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
+ && isOnScrollbarThumb(ev.getX(), ev.getY())) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Looks for a view to give focus to respecting the setting specified by
+ * {@link #getDescendantFocusability()}.
+ *
+ * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to
+ * find focus within the children of this group when appropriate.
+ *
+ * @see #FOCUS_BEFORE_DESCENDANTS
+ * @see #FOCUS_AFTER_DESCENDANTS
+ * @see #FOCUS_BLOCK_DESCENDANTS
+ * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
+ */
+ @Override
+ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ if (DBG) {
+ System.out.println(this + " ViewGroup.requestFocus direction="
+ + direction);
+ }
+ int descendantFocusability = getDescendantFocusability();
+
+ switch (descendantFocusability) {
+ case FOCUS_BLOCK_DESCENDANTS:
+ return super.requestFocus(direction, previouslyFocusedRect);
+ case FOCUS_BEFORE_DESCENDANTS: {
+ final boolean took = super.requestFocus(direction, previouslyFocusedRect);
+ return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+ case FOCUS_AFTER_DESCENDANTS: {
+ final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ return took ? took : super.requestFocus(direction, previouslyFocusedRect);
+ }
+ default:
+ throw new IllegalStateException("descendant focusability must be "
+ + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+ + "but is " + descendantFocusability);
+ }
+ }
+
+ /**
+ * Look for a descendant to call {@link View#requestFocus} on.
+ * Called by {@link ViewGroup#requestFocus(int, android.graphics.Rect)}
+ * when it wants to request focus within its children. Override this to
+ * customize how your {@link ViewGroup} requests focus within its children.
+ * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+ * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
+ * to give a finer grained hint about where focus is coming from. May be null
+ * if there is no hint.
+ * @return Whether focus was taken.
+ */
+ @SuppressWarnings({"ConstantConditions"})
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+ int index;
+ int increment;
+ int end;
+ int count = mChildrenCount;
+ if ((direction & FOCUS_FORWARD) != 0) {
+ index = 0;
+ increment = 1;
+ end = count;
+ } else {
+ index = count - 1;
+ increment = -1;
+ end = -1;
+ }
+ final View[] children = mChildren;
+ for (int i = index; i != end; i += increment) {
+ View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ if (child.requestFocus(direction, previouslyFocusedRect)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean restoreDefaultFocus() {
+ if (mDefaultFocus != null
+ && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+ && (mDefaultFocus.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && mDefaultFocus.restoreDefaultFocus()) {
+ return true;
+ }
+ return super.restoreDefaultFocus();
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @Override
+ public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+ // Allow cluster-navigation to enter touchscreenBlocksFocus ViewGroups.
+ if (isKeyboardNavigationCluster()) {
+ final boolean blockedFocus = getTouchscreenBlocksFocus();
+ try {
+ setTouchscreenBlocksFocusNoRefocus(false);
+ return restoreFocusInClusterInternal(direction);
+ } finally {
+ setTouchscreenBlocksFocusNoRefocus(blockedFocus);
+ }
+ } else {
+ return restoreFocusInClusterInternal(direction);
+ }
+ }
+
+ private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
+ if (mFocusedInCluster != null && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+ && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && mFocusedInCluster.restoreFocusInCluster(direction)) {
+ return true;
+ }
+ return super.restoreFocusInCluster(direction);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean restoreFocusNotInCluster() {
+ if (mFocusedInCluster != null) {
+ // since clusters don't nest; we can assume that a non-null mFocusedInCluster
+ // will refer to a view not-in a cluster.
+ return restoreFocusInCluster(View.FOCUS_DOWN);
+ }
+ if (isKeyboardNavigationCluster() || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+ return false;
+ }
+ int descendentFocusability = getDescendantFocusability();
+ if (descendentFocusability == FOCUS_BLOCK_DESCENDANTS) {
+ return super.requestFocus(FOCUS_DOWN, null);
+ }
+ if (descendentFocusability == FOCUS_BEFORE_DESCENDANTS
+ && super.requestFocus(FOCUS_DOWN, null)) {
+ return true;
+ }
+ for (int i = 0; i < mChildrenCount; ++i) {
+ View child = mChildren[i];
+ if (!child.isKeyboardNavigationCluster()
+ && child.restoreFocusNotInCluster()) {
+ return true;
+ }
+ }
+ if (descendentFocusability == FOCUS_AFTER_DESCENDANTS && !hasFocusableChild(false)) {
+ return super.requestFocus(FOCUS_DOWN, null);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ super.dispatchStartTemporaryDetach();
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchStartTemporaryDetach();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ super.dispatchFinishTemporaryDetach();
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchFinishTemporaryDetach();
+ }
+ }
+
+ @Override
+ void dispatchAttachedToWindow(AttachInfo info, int visibility) {
+ mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
+ super.dispatchAttachedToWindow(info, visibility);
+ mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ child.dispatchAttachedToWindow(info,
+ combineVisibility(visibility, child.getVisibility()));
+ }
+ final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
+ for (int i = 0; i < transientCount; ++i) {
+ View view = mTransientViews.get(i);
+ view.dispatchAttachedToWindow(info,
+ combineVisibility(visibility, view.getVisibility()));
+ }
+ }
+
+ @Override
+ void dispatchScreenStateChanged(int screenState) {
+ super.dispatchScreenStateChanged(screenState);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchScreenStateChanged(screenState);
+ }
+ }
+
+ @Override
+ void dispatchMovedToDisplay(Display display, Configuration config) {
+ super.dispatchMovedToDisplay(display, config);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchMovedToDisplay(display, config);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ boolean handled = false;
+ if (includeForAccessibility()) {
+ handled = super.dispatchPopulateAccessibilityEventInternal(event);
+ if (handled) {
+ return handled;
+ }
+ }
+ // Let our children have a shot in populating the event.
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(this, true);
+ try {
+ final int childCount = children.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = children.getChildAt(i);
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ handled = child.dispatchPopulateAccessibilityEvent(event);
+ if (handled) {
+ return handled;
+ }
+ }
+ }
+ } finally {
+ children.recycle();
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch creation of {@link ViewStructure} down the hierarchy. This implementation
+ * adds in all child views of the view group, in addition to calling the default View
+ * implementation.
+ */
+ @Override
+ public void dispatchProvideStructure(ViewStructure structure) {
+ super.dispatchProvideStructure(structure);
+ if (isAssistBlocked() || structure.getChildCount() != 0) {
+ return;
+ }
+ final int childrenCount = mChildrenCount;
+ if (childrenCount <= 0) {
+ return;
+ }
+
+ if (!isLaidOut()) {
+ Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring "
+ + childrenCount + " children of " + getAccessibilityViewId());
+ return;
+ }
+
+ structure.setChildCount(childrenCount);
+ ArrayList<View> preorderedList = buildOrderedChildList();
+ boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < childrenCount; i++) {
+ int childIndex;
+ try {
+ childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ } catch (IndexOutOfBoundsException e) {
+ childIndex = i;
+ if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.M) {
+ Log.w(TAG, "Bad getChildDrawingOrder while collecting assist @ "
+ + i + " of " + childrenCount, e);
+ // At least one app is failing when we call getChildDrawingOrder
+ // at this point, so deal semi-gracefully with it by falling back
+ // on the basic order.
+ customOrder = false;
+ if (i > 0) {
+ // If we failed at the first index, there really isn't
+ // anything to do -- we will just proceed with the simple
+ // sequence order.
+ // Otherwise, we failed in the middle, so need to come up
+ // with an order for the remaining indices and use that.
+ // Failed at the first one, easy peasy.
+ int[] permutation = new int[childrenCount];
+ SparseBooleanArray usedIndices = new SparseBooleanArray();
+ // Go back and collected the indices we have done so far.
+ for (int j = 0; j < i; j++) {
+ permutation[j] = getChildDrawingOrder(childrenCount, j);
+ usedIndices.put(permutation[j], true);
+ }
+ // Fill in the remaining indices with indices that have not
+ // yet been used.
+ int nextIndex = 0;
+ for (int j = i; j < childrenCount; j++) {
+ while (usedIndices.get(nextIndex, false)) {
+ nextIndex++;
+ }
+ permutation[j] = nextIndex;
+ nextIndex++;
+ }
+ // Build the final view list.
+ preorderedList = new ArrayList<>(childrenCount);
+ for (int j = 0; j < childrenCount; j++) {
+ final int index = permutation[j];
+ final View child = mChildren[index];
+ preorderedList.add(child);
+ }
+ }
+ } else {
+ throw e;
+ }
+ }
+ final View child = getAndVerifyPreorderedView(preorderedList, mChildren,
+ childIndex);
+ final ViewStructure cstructure = structure.newChild(i);
+ child.dispatchProvideStructure(cstructure);
+ }
+ if (preorderedList != null) {
+ preorderedList.clear();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation adds in all child views of the view group, in addition to calling the
+ * default {@link View} implementation.
+ */
+ @Override
+ public void dispatchProvideAutofillStructure(ViewStructure structure,
+ @AutofillFlags int flags) {
+ super.dispatchProvideAutofillStructure(structure, flags);
+ if (structure.getChildCount() != 0) {
+ return;
+ }
+
+ if (!isLaidOut()) {
+ Log.v(VIEW_LOG_TAG, "dispatchProvideAutofillStructure(): not laid out, ignoring "
+ + mChildrenCount + " children of " + getAutofillId());
+ return;
+ }
+
+ final ChildListForAutoFill children = getChildrenForAutofill(flags);
+ final int childrenCount = children.size();
+ structure.setChildCount(childrenCount);
+ for (int i = 0; i < childrenCount; i++) {
+ final View child = children.get(i);
+ final ViewStructure cstructure = structure.newChild(i);
+ child.dispatchProvideAutofillStructure(cstructure, flags);
+ }
+ children.recycle();
+ }
+
+ /**
+ * Gets the children for autofill. Children for autofill are the first
+ * level descendants that are important for autofill. The returned
+ * child list object is pooled and the caller must recycle it once done.
+ * @hide */
+ private @NonNull ChildListForAutoFill getChildrenForAutofill(@AutofillFlags int flags) {
+ final ChildListForAutoFill children = ChildListForAutoFill.obtain();
+ populateChildrenForAutofill(children, flags);
+ return children;
+ }
+
+ /** @hide */
+ private void populateChildrenForAutofill(ArrayList<View> list, @AutofillFlags int flags) {
+ final int childrenCount = mChildrenCount;
+ if (childrenCount <= 0) {
+ return;
+ }
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < childrenCount; i++) {
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = (preorderedList == null)
+ ? mChildren[childIndex] : preorderedList.get(childIndex);
+ if ((flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ || child.isImportantForAutofill()) {
+ list.add(child);
+ } else if (child instanceof ViewGroup) {
+ ((ViewGroup) child).populateChildrenForAutofill(list, flags);
+ }
+ }
+ }
+
+ private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
+ int childIndex) {
+ final View child;
+ if (preorderedList != null) {
+ child = preorderedList.get(childIndex);
+ if (child == null) {
+ throw new RuntimeException("Invalid preorderedList contained null child at index "
+ + childIndex);
+ }
+ } else {
+ child = children[childIndex];
+ }
+ return child;
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
+ if (getAccessibilityNodeProvider() != null) {
+ return;
+ }
+ if (mAttachInfo != null) {
+ final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
+ childrenForAccessibility.clear();
+ addChildrenForAccessibility(childrenForAccessibility);
+ final int childrenForAccessibilityCount = childrenForAccessibility.size();
+ for (int i = 0; i < childrenForAccessibilityCount; i++) {
+ final View child = childrenForAccessibility.get(i);
+ info.addChildUnchecked(child);
+ }
+ childrenForAccessibility.clear();
+ }
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return ViewGroup.class.getName();
+ }
+
+ @Override
+ public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+ // If this is a live region, we should send a subtree change event
+ // from this view. Otherwise, we can let it propagate up.
+ if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ } else if (mParent != null) {
+ try {
+ mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+ return;
+ }
+ // If something important for a11y is happening in this subtree, make sure it's dispatched
+ // from a view that is important for a11y so it doesn't get lost.
+ if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+ && !isImportantForAccessibility() && (getChildCount() > 0)) {
+ ViewParent a11yParent = getParentForAccessibility();
+ if (a11yParent instanceof View) {
+ ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded();
+ return;
+ }
+ }
+ super.notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+
+ @Override
+ void resetSubtreeAccessibilityStateChanged() {
+ super.resetSubtreeAccessibilityStateChanged();
+ View[] children = mChildren;
+ final int childCount = mChildrenCount;
+ for (int i = 0; i < childCount; i++) {
+ children[i].resetSubtreeAccessibilityStateChanged();
+ }
+ }
+
+ /**
+ * Counts the number of children of this View that will be sent to an accessibility service.
+ *
+ * @return The number of children an {@code AccessibilityNodeInfo} rooted at this View
+ * would have.
+ */
+ int getNumChildrenForAccessibility() {
+ int numChildrenForAccessibility = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child.includeForAccessibility()) {
+ numChildrenForAccessibility++;
+ } else if (child instanceof ViewGroup) {
+ numChildrenForAccessibility += ((ViewGroup) child)
+ .getNumChildrenForAccessibility();
+ }
+ }
+ return numChildrenForAccessibility;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Subclasses should always call <code>super.onNestedPrePerformAccessibilityAction</code></p>
+ *
+ * @param target The target view dispatching this action
+ * @param action Action being performed; see
+ * {@link android.view.accessibility.AccessibilityNodeInfo}
+ * @param args Optional action arguments
+ * @return false by default. Subclasses should return true if they handle the event.
+ */
+ @Override
+ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
+ return false;
+ }
+
+ @Override
+ void dispatchDetachedFromWindow() {
+ // If we still have a touch target, we are still in the process of
+ // dispatching motion events to a child; we need to get rid of that
+ // child to avoid dispatching events to it after the window is torn
+ // down. To make sure we keep the child in a consistent state, we
+ // first send it an ACTION_CANCEL motion event.
+ cancelAndClearTouchTargets(null);
+
+ // Similarly, set ACTION_EXIT to all hover targets and clear them.
+ exitHoverTargets();
+ exitTooltipHoverTargets();
+
+ // In case view is detached while transition is running
+ mLayoutCalledWhileSuppressed = false;
+
+ // Tear down our drag tracking
+ mChildrenInterestedInDrag = null;
+ mIsInterestedInDrag = false;
+ if (mCurrentDragStartEvent != null) {
+ mCurrentDragStartEvent.recycle();
+ mCurrentDragStartEvent = null;
+ }
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchDetachedFromWindow();
+ }
+ clearDisappearingChildren();
+ final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
+ for (int i = 0; i < transientCount; ++i) {
+ View view = mTransientViews.get(i);
+ view.dispatchDetachedFromWindow();
+ }
+ super.dispatchDetachedFromWindow();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void internalSetPadding(int left, int top, int right, int bottom) {
+ super.internalSetPadding(left, top, right, bottom);
+
+ if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingBottom) != 0) {
+ mGroupFlags |= FLAG_PADDING_NOT_NULL;
+ } else {
+ mGroupFlags &= ~FLAG_PADDING_NOT_NULL;
+ }
+ }
+
+ @Override
+ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+ super.dispatchSaveInstanceState(container);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ View c = children[i];
+ if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
+ c.dispatchSaveInstanceState(container);
+ }
+ }
+ }
+
+ /**
+ * Perform dispatching of a {@link #saveHierarchyState(android.util.SparseArray)} freeze()}
+ * to only this view, not to its children. For use when overriding
+ * {@link #dispatchSaveInstanceState(android.util.SparseArray)} dispatchFreeze()} to allow
+ * subclasses to freeze their own state but not the state of their children.
+ *
+ * @param container the container
+ */
+ protected void dispatchFreezeSelfOnly(SparseArray<Parcelable> container) {
+ super.dispatchSaveInstanceState(container);
+ }
+
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ super.dispatchRestoreInstanceState(container);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ View c = children[i];
+ if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
+ c.dispatchRestoreInstanceState(container);
+ }
+ }
+ }
+
+ /**
+ * Perform dispatching of a {@link #restoreHierarchyState(android.util.SparseArray)}
+ * to only this view, not to its children. For use when overriding
+ * {@link #dispatchRestoreInstanceState(android.util.SparseArray)} to allow
+ * subclasses to thaw their own state but not the state of their children.
+ *
+ * @param container the container
+ */
+ protected void dispatchThawSelfOnly(SparseArray<Parcelable> container) {
+ super.dispatchRestoreInstanceState(container);
+ }
+
+ /**
+ * Enables or disables the drawing cache for each child of this view group.
+ *
+ * @param enabled true to enable the cache, false to dispose of it
+ */
+ protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+ if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) {
+ final View[] children = mChildren;
+ final int count = mChildrenCount;
+ for (int i = 0; i < count; i++) {
+ children[i].setDrawingCacheEnabled(enabled);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
+ int count = mChildrenCount;
+ int[] visibilities = null;
+
+ if (skipChildren) {
+ visibilities = new int[count];
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ visibilities[i] = child.getVisibility();
+ if (visibilities[i] == View.VISIBLE) {
+ child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
+ | (View.INVISIBLE & View.VISIBILITY_MASK);
+ }
+ }
+ }
+
+ Bitmap b = super.createSnapshot(quality, backgroundColor, skipChildren);
+
+ if (skipChildren) {
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
+ | (visibilities[i] & View.VISIBILITY_MASK);
+ }
+ }
+
+ return b;
+ }
+
+ /** Return true if this ViewGroup is laying out using optical bounds. */
+ boolean isLayoutModeOptical() {
+ return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
+ }
+
+ @Override
+ Insets computeOpticalInsets() {
+ if (isLayoutModeOptical()) {
+ int left = 0;
+ int top = 0;
+ int right = 0;
+ int bottom = 0;
+ for (int i = 0; i < mChildrenCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ Insets insets = child.getOpticalInsets();
+ left = Math.max(left, insets.left);
+ top = Math.max(top, insets.top);
+ right = Math.max(right, insets.right);
+ bottom = Math.max(bottom, insets.bottom);
+ }
+ }
+ return Insets.of(left, top, right, bottom);
+ } else {
+ return Insets.NONE;
+ }
+ }
+
+ private static void fillRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+ if (x1 != x2 && y1 != y2) {
+ if (x1 > x2) {
+ int tmp = x1; x1 = x2; x2 = tmp;
+ }
+ if (y1 > y2) {
+ int tmp = y1; y1 = y2; y2 = tmp;
+ }
+ canvas.drawRect(x1, y1, x2, y2, paint);
+ }
+ }
+
+ private static int sign(int x) {
+ return (x >= 0) ? 1 : -1;
+ }
+
+ private static void drawCorner(Canvas c, Paint paint, int x1, int y1, int dx, int dy, int lw) {
+ fillRect(c, paint, x1, y1, x1 + dx, y1 + lw * sign(dy));
+ fillRect(c, paint, x1, y1, x1 + lw * sign(dx), y1 + dy);
+ }
+
+ private static void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint,
+ int lineLength, int lineWidth) {
+ drawCorner(canvas, paint, x1, y1, lineLength, lineLength, lineWidth);
+ drawCorner(canvas, paint, x1, y2, lineLength, -lineLength, lineWidth);
+ drawCorner(canvas, paint, x2, y1, -lineLength, lineLength, lineWidth);
+ drawCorner(canvas, paint, x2, y2, -lineLength, -lineLength, lineWidth);
+ }
+
+ private static void fillDifference(Canvas canvas,
+ int x2, int y2, int x3, int y3,
+ int dx1, int dy1, int dx2, int dy2, Paint paint) {
+ int x1 = x2 - dx1;
+ int y1 = y2 - dy1;
+
+ int x4 = x3 + dx2;
+ int y4 = y3 + dy2;
+
+ fillRect(canvas, paint, x1, y1, x4, y2);
+ fillRect(canvas, paint, x1, y2, x2, y3);
+ fillRect(canvas, paint, x3, y2, x4, y3);
+ fillRect(canvas, paint, x1, y3, x4, y4);
+ }
+
+ /**
+ * Layout debugging code which draws rectangles around layout params.
+ *
+ * <p>This function is called automatically when the developer setting is enabled.<p/>
+ *
+ * <p>It is strongly advised to only call this function from debug builds as there is
+ * a risk of leaking unwanted layout information.<p/>
+ *
+ * @param canvas the canvas on which to draw
+ * @param paint the paint used to draw through
+ */
+ protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View c = getChildAt(i);
+ c.getLayoutParams().onDebugDraw(c, canvas, paint);
+ }
+ }
+
+ /**
+ * Layout debugging code which draws rectangles around:
+ * <ul>
+ * <li>optical bounds<li/>
+ * <li>margins<li/>
+ * <li>clip bounds<li/>
+ * <ul/>
+ *
+ * <p>This function is called automatically when the developer setting is enabled.<p/>
+ *
+ * <p>It is strongly advised to only call this function from debug builds as there is
+ * a risk of leaking unwanted layout information.<p/>
+ *
+ * @param canvas the canvas on which to draw
+ */
+ protected void onDebugDraw(Canvas canvas) {
+ Paint paint = getDebugPaint();
+
+ // Draw optical bounds
+ {
+ paint.setColor(Color.RED);
+ paint.setStyle(Paint.Style.STROKE);
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View c = getChildAt(i);
+ if (c.getVisibility() != View.GONE) {
+ Insets insets = c.getOpticalInsets();
+
+ drawRect(canvas, paint,
+ c.getLeft() + insets.left,
+ c.getTop() + insets.top,
+ c.getRight() - insets.right - 1,
+ c.getBottom() - insets.bottom - 1);
+ }
+ }
+ }
+
+ // Draw margins
+ {
+ paint.setColor(Color.argb(63, 255, 0, 255));
+ paint.setStyle(Paint.Style.FILL);
+
+ onDebugDrawMargins(canvas, paint);
+ }
+
+ // Draw clip bounds
+ {
+ paint.setColor(DEBUG_CORNERS_COLOR);
+ paint.setStyle(Paint.Style.FILL);
+
+ int lineLength = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
+ int lineWidth = dipsToPixels(1);
+ for (int i = 0; i < getChildCount(); i++) {
+ View c = getChildAt(i);
+ if (c.getVisibility() != View.GONE) {
+ drawRectCorners(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(),
+ paint, lineLength, lineWidth);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ int flags = mGroupFlags;
+
+ if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
+ final boolean buildCache = !isHardwareAccelerated();
+ for (int i = 0; i < childrenCount; i++) {
+ final View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ final LayoutParams params = child.getLayoutParams();
+ attachLayoutAnimationParameters(child, params, i, childrenCount);
+ bindLayoutAnimation(child);
+ }
+ }
+
+ final LayoutAnimationController controller = mLayoutAnimationController;
+ if (controller.willOverlap()) {
+ mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
+ }
+
+ controller.start();
+
+ mGroupFlags &= ~FLAG_RUN_ANIMATION;
+ mGroupFlags &= ~FLAG_ANIMATION_DONE;
+
+ if (mAnimationListener != null) {
+ mAnimationListener.onAnimationStart(controller.getAnimation());
+ }
+ }
+
+ int clipSaveCount = 0;
+ final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+ if (clipToPadding) {
+ clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
+ mScrollX + mRight - mLeft - mPaddingRight,
+ mScrollY + mBottom - mTop - mPaddingBottom);
+ }
+
+ // We will draw our child's animation, let's reset the flag
+ mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
+ mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
+
+ boolean more = false;
+ final long drawingTime = getDrawingTime();
+
+ if (usingRenderNodeProperties) canvas.insertReorderBarrier();
+ final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
+ int transientIndex = transientCount != 0 ? 0 : -1;
+ // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
+ // draw reordering internally
+ final ArrayList<View> preorderedList = usingRenderNodeProperties
+ ? null : buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < childrenCount; i++) {
+ while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
+ final View transientChild = mTransientViews.get(transientIndex);
+ if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
+ transientChild.getAnimation() != null) {
+ more |= drawChild(canvas, transientChild, drawingTime);
+ }
+ transientIndex++;
+ if (transientIndex >= transientCount) {
+ transientIndex = -1;
+ }
+ }
+
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+ more |= drawChild(canvas, child, drawingTime);
+ }
+ }
+ while (transientIndex >= 0) {
+ // there may be additional transient views after the normal views
+ final View transientChild = mTransientViews.get(transientIndex);
+ if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
+ transientChild.getAnimation() != null) {
+ more |= drawChild(canvas, transientChild, drawingTime);
+ }
+ transientIndex++;
+ if (transientIndex >= transientCount) {
+ break;
+ }
+ }
+ if (preorderedList != null) preorderedList.clear();
+
+ // Draw any disappearing views that have animations
+ if (mDisappearingChildren != null) {
+ final ArrayList<View> disappearingChildren = mDisappearingChildren;
+ final int disappearingCount = disappearingChildren.size() - 1;
+ // Go backwards -- we may delete as animations finish
+ for (int i = disappearingCount; i >= 0; i--) {
+ final View child = disappearingChildren.get(i);
+ more |= drawChild(canvas, child, drawingTime);
+ }
+ }
+ if (usingRenderNodeProperties) canvas.insertInorderBarrier();
+
+ if (debugDraw()) {
+ onDebugDraw(canvas);
+ }
+
+ if (clipToPadding) {
+ canvas.restoreToCount(clipSaveCount);
+ }
+
+ // mGroupFlags might have been updated by drawChild()
+ flags = mGroupFlags;
+
+ if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
+ invalidate(true);
+ }
+
+ if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
+ mLayoutAnimationController.isDone() && !more) {
+ // We want to erase the drawing cache and notify the listener after the
+ // next frame is drawn because one extra invalidate() is caused by
+ // drawChild() after the animation is over
+ mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
+ final Runnable end = new Runnable() {
+ @Override
+ public void run() {
+ notifyAnimationListener();
+ }
+ };
+ post(end);
+ }
+ }
+
+ /**
+ * Returns the ViewGroupOverlay for this view group, creating it if it does
+ * not yet exist. In addition to {@link ViewOverlay}'s support for drawables,
+ * {@link ViewGroupOverlay} allows views to be added to the overlay. These
+ * views, like overlay drawables, are visual-only; they do not receive input
+ * events and should not be used as anything other than a temporary
+ * representation of a view in a parent container, such as might be used
+ * by an animation effect.
+ *
+ * <p>Note: Overlays do not currently work correctly with {@link
+ * SurfaceView} or {@link TextureView}; contents in overlays for these
+ * types of views may not display correctly.</p>
+ *
+ * @return The ViewGroupOverlay object for this view.
+ * @see ViewGroupOverlay
+ */
+ @Override
+ public ViewGroupOverlay getOverlay() {
+ if (mOverlay == null) {
+ mOverlay = new ViewGroupOverlay(mContext, this);
+ }
+ return (ViewGroupOverlay) mOverlay;
+ }
+
+ /**
+ * Returns the index of the child to draw for this iteration. Override this
+ * if you want to change the drawing order of children. By default, it
+ * returns i.
+ * <p>
+ * NOTE: In order for this method to be called, you must enable child ordering
+ * first by calling {@link #setChildrenDrawingOrderEnabled(boolean)}.
+ *
+ * @param i The current iteration.
+ * @return The index of the child to draw this iteration.
+ *
+ * @see #setChildrenDrawingOrderEnabled(boolean)
+ * @see #isChildrenDrawingOrderEnabled()
+ */
+ protected int getChildDrawingOrder(int childCount, int i) {
+ return i;
+ }
+
+ private boolean hasChildWithZ() {
+ for (int i = 0; i < mChildrenCount; i++) {
+ if (mChildren[i].getZ() != 0) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,
+ * sorted first by Z, then by child drawing order (if applicable). This list must be cleared
+ * after use to avoid leaking child Views.
+ *
+ * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
+ * children.
+ */
+ ArrayList<View> buildOrderedChildList() {
+ final int childrenCount = mChildrenCount;
+ if (childrenCount <= 1 || !hasChildWithZ()) return null;
+
+ if (mPreSortedChildren == null) {
+ mPreSortedChildren = new ArrayList<>(childrenCount);
+ } else {
+ // callers should clear, so clear shouldn't be necessary, but for safety...
+ mPreSortedChildren.clear();
+ mPreSortedChildren.ensureCapacity(childrenCount);
+ }
+
+ final boolean customOrder = isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < childrenCount; i++) {
+ // add next child (in child order) to end of list
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View nextChild = mChildren[childIndex];
+ final float currentZ = nextChild.getZ();
+
+ // insert ahead of any Views with greater Z
+ int insertIndex = i;
+ while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
+ insertIndex--;
+ }
+ mPreSortedChildren.add(insertIndex, nextChild);
+ }
+ return mPreSortedChildren;
+ }
+
+ private void notifyAnimationListener() {
+ mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
+ mGroupFlags |= FLAG_ANIMATION_DONE;
+
+ if (mAnimationListener != null) {
+ final Runnable end = new Runnable() {
+ @Override
+ public void run() {
+ mAnimationListener.onAnimationEnd(mLayoutAnimationController.getAnimation());
+ }
+ };
+ post(end);
+ }
+
+ invalidate(true);
+ }
+
+ /**
+ * This method is used to cause children of this ViewGroup to restore or recreate their
+ * display lists. It is called by getDisplayList() when the parent ViewGroup does not need
+ * to recreate its own display list, which would happen if it went through the normal
+ * draw/dispatchDraw mechanisms.
+ *
+ * @hide
+ */
+ @Override
+ protected void dispatchGetDisplayList() {
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
+ recreateChildDisplayList(child);
+ }
+ }
+ if (mOverlay != null) {
+ View overlayView = mOverlay.getOverlayView();
+ recreateChildDisplayList(overlayView);
+ }
+ if (mDisappearingChildren != null) {
+ final ArrayList<View> disappearingChildren = mDisappearingChildren;
+ final int disappearingCount = disappearingChildren.size();
+ for (int i = 0; i < disappearingCount; ++i) {
+ final View child = disappearingChildren.get(i);
+ recreateChildDisplayList(child);
+ }
+ }
+ }
+
+ private void recreateChildDisplayList(View child) {
+ child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
+ child.mPrivateFlags &= ~PFLAG_INVALIDATED;
+ child.updateDisplayListIfDirty();
+ child.mRecreateDisplayList = false;
+ }
+
+ /**
+ * Draw one child of this View Group. This method is responsible for getting
+ * the canvas in the right state. This includes clipping, translating so
+ * that the child's scrolled origin is at 0, 0, and applying any animation
+ * transformations.
+ *
+ * @param canvas The canvas on which to draw the child
+ * @param child Who to draw
+ * @param drawingTime The time at which draw is occurring
+ * @return True if an invalidate() was issued
+ */
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ return child.draw(canvas, this, drawingTime);
+ }
+
+ @Override
+ void getScrollIndicatorBounds(@NonNull Rect out) {
+ super.getScrollIndicatorBounds(out);
+
+ // If we have padding and we're supposed to clip children to that
+ // padding, offset the scroll indicators to match our clip bounds.
+ final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+ if (clipToPadding) {
+ out.left += mPaddingLeft;
+ out.right -= mPaddingRight;
+ out.top += mPaddingTop;
+ out.bottom -= mPaddingBottom;
+ }
+ }
+
+ /**
+ * Returns whether this group's children are clipped to their bounds before drawing.
+ * The default value is true.
+ * @see #setClipChildren(boolean)
+ *
+ * @return True if the group's children will be clipped to their bounds,
+ * false otherwise.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean getClipChildren() {
+ return ((mGroupFlags & FLAG_CLIP_CHILDREN) != 0);
+ }
+
+ /**
+ * By default, children are clipped to their bounds before drawing. This
+ * allows view groups to override this behavior for animations, etc.
+ *
+ * @param clipChildren true to clip children to their bounds,
+ * false otherwise
+ * @attr ref android.R.styleable#ViewGroup_clipChildren
+ */
+ public void setClipChildren(boolean clipChildren) {
+ boolean previousValue = (mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN;
+ if (clipChildren != previousValue) {
+ setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
+ for (int i = 0; i < mChildrenCount; ++i) {
+ View child = getChildAt(i);
+ if (child.mRenderNode != null) {
+ child.mRenderNode.setClipToBounds(clipChildren);
+ }
+ }
+ invalidate(true);
+ }
+ }
+
+ /**
+ * Sets whether this ViewGroup will clip its children to its padding and resize (but not
+ * clip) any EdgeEffect to the padded region, if padding is present.
+ * <p>
+ * By default, children are clipped to the padding of their parent
+ * ViewGroup. This clipping behavior is only enabled if padding is non-zero.
+ *
+ * @param clipToPadding true to clip children to the padding of the group, and resize (but
+ * not clip) any EdgeEffect to the padded region. False otherwise.
+ * @attr ref android.R.styleable#ViewGroup_clipToPadding
+ */
+ public void setClipToPadding(boolean clipToPadding) {
+ if (hasBooleanFlag(FLAG_CLIP_TO_PADDING) != clipToPadding) {
+ setBooleanFlag(FLAG_CLIP_TO_PADDING, clipToPadding);
+ invalidate(true);
+ }
+ }
+
+ /**
+ * Returns whether this ViewGroup will clip its children to its padding, and resize (but
+ * not clip) any EdgeEffect to the padded region, if padding is present.
+ * <p>
+ * By default, children are clipped to the padding of their parent
+ * Viewgroup. This clipping behavior is only enabled if padding is non-zero.
+ *
+ * @return true if this ViewGroup clips children to its padding and resizes (but doesn't
+ * clip) any EdgeEffect to the padded region, false otherwise.
+ *
+ * @attr ref android.R.styleable#ViewGroup_clipToPadding
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public boolean getClipToPadding() {
+ return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
+ }
+
+ @Override
+ public void dispatchSetSelected(boolean selected) {
+ final View[] children = mChildren;
+ final int count = mChildrenCount;
+ for (int i = 0; i < count; i++) {
+ children[i].setSelected(selected);
+ }
+ }
+
+ @Override
+ public void dispatchSetActivated(boolean activated) {
+ final View[] children = mChildren;
+ final int count = mChildrenCount;
+ for (int i = 0; i < count; i++) {
+ children[i].setActivated(activated);
+ }
+ }
+
+ @Override
+ protected void dispatchSetPressed(boolean pressed) {
+ final View[] children = mChildren;
+ final int count = mChildrenCount;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ // Children that are clickable on their own should not
+ // show a pressed state when their parent view does.
+ // Clearing a pressed state always propagates.
+ if (!pressed || (!child.isClickable() && !child.isLongClickable())) {
+ child.setPressed(pressed);
+ }
+ }
+ }
+
+ /**
+ * Dispatches drawable hotspot changes to child views that meet at least
+ * one of the following criteria:
+ * <ul>
+ * <li>Returns {@code false} from both {@link View#isClickable()} and
+ * {@link View#isLongClickable()}</li>
+ * <li>Requests duplication of parent state via
+ * {@link View#setDuplicateParentStateEnabled(boolean)}</li>
+ * </ul>
+ *
+ * @param x hotspot x coordinate
+ * @param y hotspot y coordinate
+ * @see #drawableHotspotChanged(float, float)
+ */
+ @Override
+ public void dispatchDrawableHotspotChanged(float x, float y) {
+ final int count = mChildrenCount;
+ if (count == 0) {
+ return;
+ }
+
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ // Children that are clickable on their own should not
+ // receive hotspots when their parent view does.
+ final boolean nonActionable = !child.isClickable() && !child.isLongClickable();
+ final boolean duplicatesState = (child.mViewFlags & DUPLICATE_PARENT_STATE) != 0;
+ if (nonActionable || duplicatesState) {
+ final float[] point = getTempPoint();
+ point[0] = x;
+ point[1] = y;
+ transformPointToViewLocal(point, child);
+ child.drawableHotspotChanged(point[0], point[1]);
+ }
+ }
+ }
+
+ @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
+ * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
+ * invoked when a child is drawn.
+ *
+ * Any subclass overriding
+ * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
+ * set this property to true.
+ *
+ * @param enabled True to enable static transformations on children, false otherwise.
+ *
+ * @see #getChildStaticTransformation(View, android.view.animation.Transformation)
+ */
+ protected void setStaticTransformationsEnabled(boolean enabled) {
+ setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled);
+ }
+
+ /**
+ * Sets <code>t</code> to be the static transformation of the child, if set, returning a
+ * boolean to indicate whether a static transform was set. The default implementation
+ * simply returns <code>false</code>; subclasses may override this method for different
+ * behavior. {@link #setStaticTransformationsEnabled(boolean)} must be set to true
+ * for this method to be called.
+ *
+ * @param child The child view whose static transform is being requested
+ * @param t The Transformation which will hold the result
+ * @return true if the transformation was set, false otherwise
+ * @see #setStaticTransformationsEnabled(boolean)
+ */
+ protected boolean getChildStaticTransformation(View child, Transformation t) {
+ return false;
+ }
+
+ Transformation getChildTransformation() {
+ if (mChildTransformation == null) {
+ mChildTransformation = new Transformation();
+ }
+ return mChildTransformation;
+ }
+
+ /**
+ * {@hide}
+ */
+ @Override
+ protected <T extends View> T findViewTraversal(@IdRes int id) {
+ if (id == mID) {
+ return (T) this;
+ }
+
+ final View[] where = mChildren;
+ final int len = mChildrenCount;
+
+ for (int i = 0; i < len; i++) {
+ View v = where[i];
+
+ if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+ v = v.findViewById(id);
+
+ if (v != null) {
+ return (T) v;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * {@hide}
+ */
+ @Override
+ protected <T extends View> T findViewWithTagTraversal(Object tag) {
+ if (tag != null && tag.equals(mTag)) {
+ return (T) this;
+ }
+
+ final View[] where = mChildren;
+ final int len = mChildrenCount;
+
+ for (int i = 0; i < len; i++) {
+ View v = where[i];
+
+ if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+ v = v.findViewWithTag(tag);
+
+ if (v != null) {
+ return (T) v;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * {@hide}
+ */
+ @Override
+ protected <T extends View> T findViewByPredicateTraversal(Predicate<View> predicate,
+ View childToSkip) {
+ if (predicate.test(this)) {
+ return (T) this;
+ }
+
+ final View[] where = mChildren;
+ final int len = mChildrenCount;
+
+ for (int i = 0; i < len; i++) {
+ View v = where[i];
+
+ if (v != childToSkip && (v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
+ v = v.findViewByPredicate(predicate);
+
+ if (v != null) {
+ return (T) v;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * This method adds a view to this container at the specified index purely for the
+ * purposes of allowing that view to draw even though it is not a normal child of
+ * the container. That is, the view does not participate in layout, focus, accessibility,
+ * input, or other normal view operations; it is purely an item to be drawn during the normal
+ * rendering operation of this container. The index that it is added at is the order
+ * in which it will be drawn, with respect to the other views in the container.
+ * For example, a transient view added at index 0 will be drawn before all other views
+ * in the container because it will be drawn first (including before any real view
+ * at index 0). There can be more than one transient view at any particular index;
+ * these views will be drawn in the order in which they were added to the list of
+ * transient views. The index of transient views can also be greater than the number
+ * of normal views in the container; that just means that they will be drawn after all
+ * other views are drawn.
+ *
+ * <p>Note that since transient views do not participate in layout, they must be sized
+ * manually or, more typically, they should just use the size that they had before they
+ * were removed from their container.</p>
+ *
+ * <p>Transient views are useful for handling animations of views that have been removed
+ * from the container, but which should be animated out after the removal. Adding these
+ * views as transient views allows them to participate in drawing without side-effecting
+ * the layout of the container.</p>
+ *
+ * <p>Transient views must always be explicitly {@link #removeTransientView(View) removed}
+ * from the container when they are no longer needed. For example, a transient view
+ * which is added in order to fade it out in its old location should be removed
+ * once the animation is complete.</p>
+ *
+ * @param view The view to be added
+ * @param index The index at which this view should be drawn, must be >= 0.
+ * This value is relative to the {@link #getChildAt(int) index} values in the normal
+ * child list of this container, where any transient view at a particular index will
+ * be drawn before any normal child at that same index.
+ *
+ * @hide
+ */
+ public void addTransientView(View view, int index) {
+ if (index < 0) {
+ return;
+ }
+ if (mTransientIndices == null) {
+ mTransientIndices = new ArrayList<Integer>();
+ mTransientViews = new ArrayList<View>();
+ }
+ final int oldSize = mTransientIndices.size();
+ if (oldSize > 0) {
+ int insertionIndex;
+ for (insertionIndex = 0; insertionIndex < oldSize; ++insertionIndex) {
+ if (index < mTransientIndices.get(insertionIndex)) {
+ break;
+ }
+ }
+ mTransientIndices.add(insertionIndex, index);
+ mTransientViews.add(insertionIndex, view);
+ } else {
+ mTransientIndices.add(index);
+ mTransientViews.add(view);
+ }
+ view.mParent = this;
+ view.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
+ invalidate(true);
+ }
+
+ /**
+ * Removes a view from the list of transient views in this container. If there is no
+ * such transient view, this method does nothing.
+ *
+ * @param view The transient view to be removed
+ *
+ * @hide
+ */
+ public void removeTransientView(View view) {
+ if (mTransientViews == null) {
+ return;
+ }
+ final int size = mTransientViews.size();
+ for (int i = 0; i < size; ++i) {
+ if (view == mTransientViews.get(i)) {
+ mTransientViews.remove(i);
+ mTransientIndices.remove(i);
+ view.mParent = null;
+ view.dispatchDetachedFromWindow();
+ invalidate(true);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns the number of transient views in this container. Specific transient
+ * views and the index at which they were added can be retrieved via
+ * {@link #getTransientView(int)} and {@link #getTransientViewIndex(int)}.
+ *
+ * @see #addTransientView(View, int)
+ * @return The number of transient views in this container
+ *
+ * @hide
+ */
+ public int getTransientViewCount() {
+ return mTransientIndices == null ? 0 : mTransientIndices.size();
+ }
+
+ /**
+ * Given a valid position within the list of transient views, returns the index of
+ * the transient view at that position.
+ *
+ * @param position The position of the index being queried. Must be at least 0
+ * and less than the value returned by {@link #getTransientViewCount()}.
+ * @return The index of the transient view stored in the given position if the
+ * position is valid, otherwise -1
+ *
+ * @hide
+ */
+ public int getTransientViewIndex(int position) {
+ if (position < 0 || mTransientIndices == null || position >= mTransientIndices.size()) {
+ return -1;
+ }
+ return mTransientIndices.get(position);
+ }
+
+ /**
+ * Given a valid position within the list of transient views, returns the
+ * transient view at that position.
+ *
+ * @param position The position of the view being queried. Must be at least 0
+ * and less than the value returned by {@link #getTransientViewCount()}.
+ * @return The transient view stored in the given position if the
+ * position is valid, otherwise null
+ *
+ * @hide
+ */
+ public View getTransientView(int position) {
+ if (mTransientViews == null || position >= mTransientViews.size()) {
+ return null;
+ }
+ return mTransientViews.get(position);
+ }
+
+ /**
+ * <p>Adds a child view. If no layout parameters are already set on the child, the
+ * default parameters for this ViewGroup are set on the child.</p>
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param child the child view to add
+ *
+ * @see #generateDefaultLayoutParams()
+ */
+ public void addView(View child) {
+ addView(child, -1);
+ }
+
+ /**
+ * Adds a child view. If no layout parameters are already set on the child, the
+ * default parameters for this ViewGroup are set on the child.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param child the child view to add
+ * @param index the position at which to add the child
+ *
+ * @see #generateDefaultLayoutParams()
+ */
+ public void addView(View child, int index) {
+ if (child == null) {
+ throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+ }
+ LayoutParams params = child.getLayoutParams();
+ if (params == null) {
+ params = generateDefaultLayoutParams();
+ if (params == null) {
+ throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
+ }
+ }
+ addView(child, index, params);
+ }
+
+ /**
+ * Adds a child view with this ViewGroup's default layout parameters and the
+ * specified width and height.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param child the child view to add
+ */
+ public void addView(View child, int width, int height) {
+ final LayoutParams params = generateDefaultLayoutParams();
+ params.width = width;
+ params.height = height;
+ addView(child, -1, params);
+ }
+
+ /**
+ * Adds a child view with the specified layout parameters.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param child the child view to add
+ * @param params the layout parameters to set on the child
+ */
+ @Override
+ public void addView(View child, LayoutParams params) {
+ addView(child, -1, params);
+ }
+
+ /**
+ * Adds a child view with the specified layout parameters.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param child the child view to add
+ * @param index the position at which to add the child or -1 to add last
+ * @param params the layout parameters to set on the child
+ */
+ public void addView(View child, int index, LayoutParams params) {
+ if (DBG) {
+ System.out.println(this + " addView");
+ }
+
+ if (child == null) {
+ throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+ }
+
+ // addViewInner() will call child.requestLayout() when setting the new LayoutParams
+ // therefore, we call requestLayout() on ourselves before, so that the child's request
+ // will be blocked at our level
+ requestLayout();
+ invalidate(true);
+ addViewInner(child, index, params, false);
+ }
+
+ @Override
+ public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+ if (!checkLayoutParams(params)) {
+ throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this);
+ }
+ if (view.mParent != this) {
+ throw new IllegalArgumentException("Given view not a child of " + this);
+ }
+ view.setLayoutParams(params);
+ }
+
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p != null;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the hierarchy
+ * within this view changed. The hierarchy changes whenever a child is added
+ * to or removed from this view.
+ */
+ public interface OnHierarchyChangeListener {
+ /**
+ * Called when a new child is added to a parent view.
+ *
+ * @param parent the view in which a child was added
+ * @param child the new child view added in the hierarchy
+ */
+ void onChildViewAdded(View parent, View child);
+
+ /**
+ * Called when a child is removed from a parent view.
+ *
+ * @param parent the view from which the child was removed
+ * @param child the child removed from the hierarchy
+ */
+ void onChildViewRemoved(View parent, View child);
+ }
+
+ /**
+ * Register a callback to be invoked when a child is added to or removed
+ * from this view.
+ *
+ * @param listener the callback to invoke on hierarchy change
+ */
+ public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+ mOnHierarchyChangeListener = listener;
+ }
+
+ void dispatchViewAdded(View child) {
+ onViewAdded(child);
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewAdded(this, child);
+ }
+ }
+
+ /**
+ * Called when a new child is added to this ViewGroup. Overrides should always
+ * call super.onViewAdded.
+ *
+ * @param child the added child view
+ */
+ public void onViewAdded(View child) {
+ }
+
+ void dispatchViewRemoved(View child) {
+ onViewRemoved(child);
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewRemoved(this, child);
+ }
+ }
+
+ /**
+ * Called when a child view is removed from this ViewGroup. Overrides should always
+ * call super.onViewRemoved.
+ *
+ * @param child the removed child view
+ */
+ public void onViewRemoved(View child) {
+ }
+
+ private void clearCachedLayoutMode() {
+ if (!hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) {
+ mLayoutMode = LAYOUT_MODE_UNDEFINED;
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ clearCachedLayoutMode();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ clearCachedLayoutMode();
+ }
+
+ /** @hide */
+ @Override
+ protected void destroyHardwareResources() {
+ super.destroyHardwareResources();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).destroyHardwareResources();
+ }
+ }
+
+ /**
+ * Adds a view during layout. This is useful if in your onLayout() method,
+ * you need to add more views (as does the list view for example).
+ *
+ * If index is negative, it means put it at the end of the list.
+ *
+ * @param child the view to add to the group
+ * @param index the index at which the child must be added or -1 to add last
+ * @param params the layout parameters to associate with the child
+ * @return true if the child was added, false otherwise
+ */
+ protected boolean addViewInLayout(View child, int index, LayoutParams params) {
+ return addViewInLayout(child, index, params, false);
+ }
+
+ /**
+ * Adds a view during layout. This is useful if in your onLayout() method,
+ * you need to add more views (as does the list view for example).
+ *
+ * If index is negative, it means put it at the end of the list.
+ *
+ * @param child the view to add to the group
+ * @param index the index at which the child must be added or -1 to add last
+ * @param params the layout parameters to associate with the child
+ * @param preventRequestLayout if true, calling this method will not trigger a
+ * layout request on child
+ * @return true if the child was added, false otherwise
+ */
+ protected boolean addViewInLayout(View child, int index, LayoutParams params,
+ boolean preventRequestLayout) {
+ if (child == null) {
+ throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
+ }
+ child.mParent = null;
+ addViewInner(child, index, params, preventRequestLayout);
+ child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
+ return true;
+ }
+
+ /**
+ * Prevents the specified child to be laid out during the next layout pass.
+ *
+ * @param child the child on which to perform the cleanup
+ */
+ protected void cleanupLayoutState(View child) {
+ child.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+ }
+
+ private void addViewInner(View child, int index, LayoutParams params,
+ boolean preventRequestLayout) {
+
+ if (mTransition != null) {
+ // Don't prevent other add transitions from completing, but cancel remove
+ // transitions to let them complete the process before we add to the container
+ mTransition.cancel(LayoutTransition.DISAPPEARING);
+ }
+
+ if (child.getParent() != null) {
+ throw new IllegalStateException("The specified child already has a parent. " +
+ "You must call removeView() on the child's parent first.");
+ }
+
+ if (mTransition != null) {
+ mTransition.addChild(this, child);
+ }
+
+ if (!checkLayoutParams(params)) {
+ params = generateLayoutParams(params);
+ }
+
+ if (preventRequestLayout) {
+ child.mLayoutParams = params;
+ } else {
+ child.setLayoutParams(params);
+ }
+
+ if (index < 0) {
+ index = mChildrenCount;
+ }
+
+ addInArray(child, index);
+
+ // tell our children
+ if (preventRequestLayout) {
+ child.assignParent(this);
+ } else {
+ child.mParent = this;
+ }
+
+ final boolean childHasFocus = child.hasFocus();
+ if (childHasFocus) {
+ requestChildFocus(child, child.findFocus());
+ }
+
+ AttachInfo ai = mAttachInfo;
+ if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
+ boolean lastKeepOn = ai.mKeepScreenOn;
+ ai.mKeepScreenOn = false;
+ child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
+ if (ai.mKeepScreenOn) {
+ needGlobalAttributesUpdate(true);
+ }
+ ai.mKeepScreenOn = lastKeepOn;
+ }
+
+ if (child.isLayoutDirectionInherited()) {
+ child.resetRtlProperties();
+ }
+
+ dispatchViewAdded(child);
+
+ if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
+ mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
+ }
+
+ if (child.hasTransientState()) {
+ childHasTransientStateChanged(child, true);
+ }
+
+ if (child.getVisibility() != View.GONE) {
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+
+ if (mTransientIndices != null) {
+ final int transientCount = mTransientIndices.size();
+ for (int i = 0; i < transientCount; ++i) {
+ final int oldIndex = mTransientIndices.get(i);
+ if (index <= oldIndex) {
+ mTransientIndices.set(i, oldIndex + 1);
+ }
+ }
+ }
+
+ if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
+ notifyChildOfDragStart(child);
+ }
+
+ if (child.hasDefaultFocus()) {
+ // When adding a child that contains default focus, either during inflation or while
+ // manually assembling the hierarchy, update the ancestor default-focus chain.
+ setDefaultFocus(child);
+ }
+ }
+
+ private void addInArray(View child, int index) {
+ View[] children = mChildren;
+ final int count = mChildrenCount;
+ final int size = children.length;
+ if (index == count) {
+ if (size == count) {
+ mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+ System.arraycopy(children, 0, mChildren, 0, size);
+ children = mChildren;
+ }
+ children[mChildrenCount++] = child;
+ } else if (index < count) {
+ if (size == count) {
+ mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+ System.arraycopy(children, 0, mChildren, 0, index);
+ System.arraycopy(children, index, mChildren, index + 1, count - index);
+ children = mChildren;
+ } else {
+ System.arraycopy(children, index, children, index + 1, count - index);
+ }
+ children[index] = child;
+ mChildrenCount++;
+ if (mLastTouchDownIndex >= index) {
+ mLastTouchDownIndex++;
+ }
+ } else {
+ throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
+ }
+ }
+
+ // This method also sets the child's mParent to null
+ private void removeFromArray(int index) {
+ final View[] children = mChildren;
+ if (!(mTransitioningViews != null && mTransitioningViews.contains(children[index]))) {
+ children[index].mParent = null;
+ }
+ final int count = mChildrenCount;
+ if (index == count - 1) {
+ children[--mChildrenCount] = null;
+ } else if (index >= 0 && index < count) {
+ System.arraycopy(children, index + 1, children, index, count - index - 1);
+ children[--mChildrenCount] = null;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ if (mLastTouchDownIndex == index) {
+ mLastTouchDownTime = 0;
+ mLastTouchDownIndex = -1;
+ } else if (mLastTouchDownIndex > index) {
+ mLastTouchDownIndex--;
+ }
+ }
+
+ // This method also sets the children's mParent to null
+ private void removeFromArray(int start, int count) {
+ final View[] children = mChildren;
+ final int childrenCount = mChildrenCount;
+
+ start = Math.max(0, start);
+ final int end = Math.min(childrenCount, start + count);
+
+ if (start == end) {
+ return;
+ }
+
+ if (end == childrenCount) {
+ for (int i = start; i < end; i++) {
+ children[i].mParent = null;
+ children[i] = null;
+ }
+ } else {
+ for (int i = start; i < end; i++) {
+ children[i].mParent = null;
+ }
+
+ // Since we're looping above, we might as well do the copy, but is arraycopy()
+ // faster than the extra 2 bounds checks we would do in the loop?
+ System.arraycopy(children, end, children, start, childrenCount - end);
+
+ for (int i = childrenCount - (end - start); i < childrenCount; i++) {
+ children[i] = null;
+ }
+ }
+
+ mChildrenCount -= (end - start);
+ }
+
+ private void bindLayoutAnimation(View child) {
+ Animation a = mLayoutAnimationController.getAnimationForView(child);
+ child.setAnimation(a);
+ }
+
+ /**
+ * Subclasses should override this method to set layout animation
+ * parameters on the supplied child.
+ *
+ * @param child the child to associate with animation parameters
+ * @param params the child's layout parameters which hold the animation
+ * parameters
+ * @param index the index of the child in the view group
+ * @param count the number of children in the view group
+ */
+ protected void attachLayoutAnimationParameters(View child,
+ LayoutParams params, int index, int count) {
+ LayoutAnimationController.AnimationParameters animationParams =
+ params.layoutAnimationParameters;
+ if (animationParams == null) {
+ animationParams = new LayoutAnimationController.AnimationParameters();
+ params.layoutAnimationParameters = animationParams;
+ }
+
+ animationParams.count = count;
+ animationParams.index = index;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ */
+ @Override
+ public void removeView(View view) {
+ if (removeViewInternal(view)) {
+ requestLayout();
+ invalidate(true);
+ }
+ }
+
+ /**
+ * Removes a view during layout. This is useful if in your onLayout() method,
+ * you need to remove more views.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param view the view to remove from the group
+ */
+ public void removeViewInLayout(View view) {
+ removeViewInternal(view);
+ }
+
+ /**
+ * Removes a range of views during layout. This is useful if in your onLayout() method,
+ * you need to remove more views.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param start the index of the first view to remove from the group
+ * @param count the number of views to remove from the group
+ */
+ public void removeViewsInLayout(int start, int count) {
+ removeViewsInternal(start, count);
+ }
+
+ /**
+ * Removes the view at the specified position in the group.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param index the position in the group of the view to remove
+ */
+ public void removeViewAt(int index) {
+ removeViewInternal(index, getChildAt(index));
+ requestLayout();
+ invalidate(true);
+ }
+
+ /**
+ * Removes the specified range of views from the group.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ *
+ * @param start the first position in the group of the range of views to remove
+ * @param count the number of views to remove
+ */
+ public void removeViews(int start, int count) {
+ removeViewsInternal(start, count);
+ requestLayout();
+ invalidate(true);
+ }
+
+ private boolean removeViewInternal(View view) {
+ final int index = indexOfChild(view);
+ if (index >= 0) {
+ removeViewInternal(index, view);
+ return true;
+ }
+ return false;
+ }
+
+ private void removeViewInternal(int index, View view) {
+ if (mTransition != null) {
+ mTransition.removeChild(this, view);
+ }
+
+ boolean clearChildFocus = false;
+ if (view == mFocused) {
+ view.unFocus(null);
+ clearChildFocus = true;
+ }
+ if (view == mFocusedInCluster) {
+ clearFocusedInCluster(view);
+ }
+
+ view.clearAccessibilityFocus();
+
+ cancelTouchTarget(view);
+ cancelHoverTarget(view);
+
+ if (view.getAnimation() != null ||
+ (mTransitioningViews != null && mTransitioningViews.contains(view))) {
+ addDisappearingView(view);
+ } else if (view.mAttachInfo != null) {
+ view.dispatchDetachedFromWindow();
+ }
+
+ if (view.hasTransientState()) {
+ childHasTransientStateChanged(view, false);
+ }
+
+ needGlobalAttributesUpdate(false);
+
+ removeFromArray(index);
+
+ if (view == mDefaultFocus) {
+ clearDefaultFocus(view);
+ }
+ if (clearChildFocus) {
+ clearChildFocus(view);
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(this);
+ }
+ }
+
+ dispatchViewRemoved(view);
+
+ if (view.getVisibility() != View.GONE) {
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+
+ int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
+ for (int i = 0; i < transientCount; ++i) {
+ final int oldIndex = mTransientIndices.get(i);
+ if (index < oldIndex) {
+ mTransientIndices.set(i, oldIndex - 1);
+ }
+ }
+
+ if (mCurrentDragStartEvent != null) {
+ mChildrenInterestedInDrag.remove(view);
+ }
+ }
+
+ /**
+ * Sets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is
+ * not null, changes in layout which occur because of children being added to or removed from
+ * the ViewGroup will be animated according to the animations defined in that LayoutTransition
+ * object. By default, the transition object is null (so layout changes are not animated).
+ *
+ * <p>Replacing a non-null transition will cause that previous transition to be
+ * canceled, if it is currently running, to restore this container to
+ * its correct post-transition state.</p>
+ *
+ * @param transition The LayoutTransition object that will animated changes in layout. A value
+ * of <code>null</code> means no transition will run on layout changes.
+ * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
+ */
+ public void setLayoutTransition(LayoutTransition transition) {
+ if (mTransition != null) {
+ LayoutTransition previousTransition = mTransition;
+ previousTransition.cancel();
+ previousTransition.removeTransitionListener(mLayoutTransitionListener);
+ }
+ mTransition = transition;
+ if (mTransition != null) {
+ mTransition.addTransitionListener(mLayoutTransitionListener);
+ }
+ }
+
+ /**
+ * Gets the LayoutTransition object for this ViewGroup. If the LayoutTransition object is
+ * not null, changes in layout which occur because of children being added to or removed from
+ * the ViewGroup will be animated according to the animations defined in that LayoutTransition
+ * object. By default, the transition object is null (so layout changes are not animated).
+ *
+ * @return LayoutTranstion The LayoutTransition object that will animated changes in layout.
+ * A value of <code>null</code> means no transition will run on layout changes.
+ */
+ public LayoutTransition getLayoutTransition() {
+ return mTransition;
+ }
+
+ private void removeViewsInternal(int start, int count) {
+ final int end = start + count;
+
+ if (start < 0 || count < 0 || end > mChildrenCount) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ final View focused = mFocused;
+ final boolean detach = mAttachInfo != null;
+ boolean clearChildFocus = false;
+ View clearDefaultFocus = null;
+
+ final View[] children = mChildren;
+
+ for (int i = start; i < end; i++) {
+ final View view = children[i];
+
+ if (mTransition != null) {
+ mTransition.removeChild(this, view);
+ }
+
+ if (view == focused) {
+ view.unFocus(null);
+ clearChildFocus = true;
+ }
+ if (view == mDefaultFocus) {
+ clearDefaultFocus = view;
+ }
+ if (view == mFocusedInCluster) {
+ clearFocusedInCluster(view);
+ }
+
+ view.clearAccessibilityFocus();
+
+ cancelTouchTarget(view);
+ cancelHoverTarget(view);
+
+ if (view.getAnimation() != null ||
+ (mTransitioningViews != null && mTransitioningViews.contains(view))) {
+ addDisappearingView(view);
+ } else if (detach) {
+ view.dispatchDetachedFromWindow();
+ }
+
+ if (view.hasTransientState()) {
+ childHasTransientStateChanged(view, false);
+ }
+
+ needGlobalAttributesUpdate(false);
+
+ dispatchViewRemoved(view);
+ }
+
+ removeFromArray(start, count);
+
+ if (clearDefaultFocus != null) {
+ clearDefaultFocus(clearDefaultFocus);
+ }
+ if (clearChildFocus) {
+ clearChildFocus(focused);
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(focused);
+ }
+ }
+ }
+
+ /**
+ * Call this method to remove all child views from the
+ * ViewGroup.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ */
+ public void removeAllViews() {
+ removeAllViewsInLayout();
+ requestLayout();
+ invalidate(true);
+ }
+
+ /**
+ * Called by a ViewGroup subclass to remove child views from itself,
+ * when it must first know its size on screen before it can calculate how many
+ * child views it will render. An example is a Gallery or a ListView, which
+ * may "have" 50 children, but actually only render the number of children
+ * that can currently fit inside the object on screen. Do not call
+ * this method unless you are extending ViewGroup and understand the
+ * view measuring and layout pipeline.
+ *
+ * <p><strong>Note:</strong> do not invoke this method from
+ * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
+ * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
+ */
+ public void removeAllViewsInLayout() {
+ final int count = mChildrenCount;
+ if (count <= 0) {
+ return;
+ }
+
+ final View[] children = mChildren;
+ mChildrenCount = 0;
+
+ final View focused = mFocused;
+ final boolean detach = mAttachInfo != null;
+ boolean clearChildFocus = false;
+
+ needGlobalAttributesUpdate(false);
+
+ for (int i = count - 1; i >= 0; i--) {
+ final View view = children[i];
+
+ if (mTransition != null) {
+ mTransition.removeChild(this, view);
+ }
+
+ if (view == focused) {
+ view.unFocus(null);
+ clearChildFocus = true;
+ }
+
+ view.clearAccessibilityFocus();
+
+ cancelTouchTarget(view);
+ cancelHoverTarget(view);
+
+ if (view.getAnimation() != null ||
+ (mTransitioningViews != null && mTransitioningViews.contains(view))) {
+ addDisappearingView(view);
+ } else if (detach) {
+ view.dispatchDetachedFromWindow();
+ }
+
+ if (view.hasTransientState()) {
+ childHasTransientStateChanged(view, false);
+ }
+
+ dispatchViewRemoved(view);
+
+ view.mParent = null;
+ children[i] = null;
+ }
+
+ if (mDefaultFocus != null) {
+ clearDefaultFocus(mDefaultFocus);
+ }
+ if (mFocusedInCluster != null) {
+ clearFocusedInCluster(mFocusedInCluster);
+ }
+ if (clearChildFocus) {
+ clearChildFocus(focused);
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(focused);
+ }
+ }
+ }
+
+ /**
+ * Finishes the removal of a detached view. This method will dispatch the detached from
+ * window event and notify the hierarchy change listener.
+ * <p>
+ * This method is intended to be lightweight and makes no assumptions about whether the
+ * parent or child should be redrawn. Proper use of this method will include also making
+ * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
+ * For example, callers can {@link #post(Runnable) post} a {@link Runnable}
+ * which performs a {@link #requestLayout()} on the next frame, after all detach/remove
+ * calls are finished, causing layout to be run prior to redrawing the view hierarchy.
+ *
+ * @param child the child to be definitely removed from the view hierarchy
+ * @param animate if true and the view has an animation, the view is placed in the
+ * disappearing views list, otherwise, it is detached from the window
+ *
+ * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+ * @see #detachAllViewsFromParent()
+ * @see #detachViewFromParent(View)
+ * @see #detachViewFromParent(int)
+ */
+ protected void removeDetachedView(View child, boolean animate) {
+ if (mTransition != null) {
+ mTransition.removeChild(this, child);
+ }
+
+ if (child == mFocused) {
+ child.clearFocus();
+ }
+ if (child == mDefaultFocus) {
+ clearDefaultFocus(child);
+ }
+ if (child == mFocusedInCluster) {
+ clearFocusedInCluster(child);
+ }
+
+ child.clearAccessibilityFocus();
+
+ cancelTouchTarget(child);
+ cancelHoverTarget(child);
+
+ if ((animate && child.getAnimation() != null) ||
+ (mTransitioningViews != null && mTransitioningViews.contains(child))) {
+ addDisappearingView(child);
+ } else if (child.mAttachInfo != null) {
+ child.dispatchDetachedFromWindow();
+ }
+
+ if (child.hasTransientState()) {
+ childHasTransientStateChanged(child, false);
+ }
+
+ dispatchViewRemoved(child);
+ }
+
+ /**
+ * Attaches a view to this view group. Attaching a view assigns this group as the parent,
+ * sets the layout parameters and puts the view in the list of children so that
+ * it can be retrieved by calling {@link #getChildAt(int)}.
+ * <p>
+ * This method is intended to be lightweight and makes no assumptions about whether the
+ * parent or child should be redrawn. Proper use of this method will include also making
+ * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
+ * For example, callers can {@link #post(Runnable) post} a {@link Runnable}
+ * which performs a {@link #requestLayout()} on the next frame, after all detach/attach
+ * calls are finished, causing layout to be run prior to redrawing the view hierarchy.
+ * <p>
+ * This method should be called only for views which were detached from their parent.
+ *
+ * @param child the child to attach
+ * @param index the index at which the child should be attached
+ * @param params the layout parameters of the child
+ *
+ * @see #removeDetachedView(View, boolean)
+ * @see #detachAllViewsFromParent()
+ * @see #detachViewFromParent(View)
+ * @see #detachViewFromParent(int)
+ */
+ protected void attachViewToParent(View child, int index, LayoutParams params) {
+ child.mLayoutParams = params;
+
+ if (index < 0) {
+ index = mChildrenCount;
+ }
+
+ addInArray(child, index);
+
+ child.mParent = this;
+ child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK
+ & ~PFLAG_DRAWING_CACHE_VALID)
+ | PFLAG_DRAWN | PFLAG_INVALIDATED;
+ this.mPrivateFlags |= PFLAG_INVALIDATED;
+
+ if (child.hasFocus()) {
+ requestChildFocus(child, child.findFocus());
+ }
+ dispatchVisibilityAggregated(isAttachedToWindow() && getWindowVisibility() == VISIBLE
+ && isShown());
+ }
+
+ /**
+ * Detaches a view from its parent. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
+ *
+ * @param child the child to detach
+ *
+ * @see #detachViewFromParent(int)
+ * @see #detachViewsFromParent(int, int)
+ * @see #detachAllViewsFromParent()
+ * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+ * @see #removeDetachedView(View, boolean)
+ */
+ protected void detachViewFromParent(View child) {
+ removeFromArray(indexOfChild(child));
+ }
+
+ /**
+ * Detaches a view from its parent. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
+ *
+ * @param index the index of the child to detach
+ *
+ * @see #detachViewFromParent(View)
+ * @see #detachAllViewsFromParent()
+ * @see #detachViewsFromParent(int, int)
+ * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+ * @see #removeDetachedView(View, boolean)
+ */
+ protected void detachViewFromParent(int index) {
+ removeFromArray(index);
+ }
+
+ /**
+ * Detaches a range of views from their parents. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
+ *
+ * @param start the first index of the childrend range to detach
+ * @param count the number of children to detach
+ *
+ * @see #detachViewFromParent(View)
+ * @see #detachViewFromParent(int)
+ * @see #detachAllViewsFromParent()
+ * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+ * @see #removeDetachedView(View, boolean)
+ */
+ protected void detachViewsFromParent(int start, int count) {
+ removeFromArray(start, count);
+ }
+
+ /**
+ * Detaches all views from the parent. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
+ *
+ * @see #detachViewFromParent(View)
+ * @see #detachViewFromParent(int)
+ * @see #detachViewsFromParent(int, int)
+ * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
+ * @see #removeDetachedView(View, boolean)
+ */
+ protected void detachAllViewsFromParent() {
+ final int count = mChildrenCount;
+ if (count <= 0) {
+ return;
+ }
+
+ final View[] children = mChildren;
+ mChildrenCount = 0;
+
+ for (int i = count - 1; i >= 0; i--) {
+ children[i].mParent = null;
+ children[i] = null;
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+ /*
+ * HW-only, Rect-ignoring damage codepath
+ *
+ * We don't deal with rectangles here, since RenderThread native code computes damage for
+ * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
+ */
+
+ // if set, combine the animation flag into the parent
+ mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
+
+ if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
+ // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
+ // optimization in provides in a DisplayList world.
+ mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
+
+ // simplified invalidateChildInParent behavior: clear cache validity to be safe...
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ }
+
+ // ... and mark inval if in software layer that needs to repaint (hw handled in native)
+ if (mLayerType == LAYER_TYPE_SOFTWARE) {
+ // Layered parents should be invalidated. Escalate to a full invalidate (and note that
+ // we do this after consuming any relevant flags from the originating descendant)
+ mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
+ target = this;
+ }
+
+ if (mParent != null) {
+ mParent.onDescendantInvalidated(this, target);
+ }
+ }
+
+
+ /**
+ * Don't call or override this method. It is used for the implementation of
+ * the view hierarchy.
+ *
+ * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
+ * draw state in descendants.
+ */
+ @Deprecated
+ @Override
+ public final void invalidateChild(View child, final Rect dirty) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null && attachInfo.mHardwareAccelerated) {
+ // HW accelerated fast path
+ onDescendantInvalidated(child, child);
+ return;
+ }
+
+ ViewParent parent = this;
+ if (attachInfo != null) {
+ // If the child is drawing an animation, we want to copy this flag onto
+ // ourselves and the parent to make sure the invalidate request goes
+ // through
+ final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
+
+ // Check whether the child that requests the invalidate is fully opaque
+ // Views being animated or transformed are not considered opaque because we may
+ // be invalidating their old position and need the parent to paint behind them.
+ Matrix childMatrix = child.getMatrix();
+ final boolean isOpaque = child.isOpaque() && !drawAnimation &&
+ child.getAnimation() == null && childMatrix.isIdentity();
+ // Mark the child as dirty, using the appropriate flag
+ // Make sure we do not set both flags at the same time
+ int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
+
+ if (child.mLayerType != LAYER_TYPE_NONE) {
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ }
+
+ final int[] location = attachInfo.mInvalidateChildLocation;
+ location[CHILD_LEFT_INDEX] = child.mLeft;
+ location[CHILD_TOP_INDEX] = child.mTop;
+ if (!childMatrix.isIdentity() ||
+ (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+ RectF boundingRect = attachInfo.mTmpTransformRect;
+ boundingRect.set(dirty);
+ Matrix transformMatrix;
+ if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+ Transformation t = attachInfo.mTmpTransformation;
+ boolean transformed = getChildStaticTransformation(child, t);
+ if (transformed) {
+ transformMatrix = attachInfo.mTmpMatrix;
+ transformMatrix.set(t.getMatrix());
+ if (!childMatrix.isIdentity()) {
+ transformMatrix.preConcat(childMatrix);
+ }
+ } else {
+ transformMatrix = childMatrix;
+ }
+ } else {
+ transformMatrix = childMatrix;
+ }
+ transformMatrix.mapRect(boundingRect);
+ dirty.set((int) Math.floor(boundingRect.left),
+ (int) Math.floor(boundingRect.top),
+ (int) Math.ceil(boundingRect.right),
+ (int) Math.ceil(boundingRect.bottom));
+ }
+
+ do {
+ View view = null;
+ if (parent instanceof View) {
+ view = (View) parent;
+ }
+
+ if (drawAnimation) {
+ if (view != null) {
+ view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
+ } else if (parent instanceof ViewRootImpl) {
+ ((ViewRootImpl) parent).mIsAnimating = true;
+ }
+ }
+
+ // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
+ // flag coming from the child that initiated the invalidate
+ if (view != null) {
+ if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
+ view.getSolidColor() == 0) {
+ opaqueFlag = PFLAG_DIRTY;
+ }
+ if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
+ view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
+ }
+ }
+
+ parent = parent.invalidateChildInParent(location, dirty);
+ if (view != null) {
+ // Account for transform on current parent
+ Matrix m = view.getMatrix();
+ if (!m.isIdentity()) {
+ RectF boundingRect = attachInfo.mTmpTransformRect;
+ boundingRect.set(dirty);
+ m.mapRect(boundingRect);
+ dirty.set((int) Math.floor(boundingRect.left),
+ (int) Math.floor(boundingRect.top),
+ (int) Math.ceil(boundingRect.right),
+ (int) Math.ceil(boundingRect.bottom));
+ }
+ }
+ } while (parent != null);
+ }
+ }
+
+ /**
+ * Don't call or override this method. It is used for the implementation of
+ * the view hierarchy.
+ *
+ * This implementation returns null if this ViewGroup does not have a parent,
+ * if this ViewGroup is already fully invalidated or if the dirty rectangle
+ * does not intersect with this ViewGroup's bounds.
+ *
+ * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
+ * draw state in descendants.
+ */
+ @Deprecated
+ @Override
+ public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
+ if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
+ // either DRAWN, or DRAWING_CACHE_VALID
+ if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
+ != FLAG_OPTIMIZE_INVALIDATE) {
+ dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
+ location[CHILD_TOP_INDEX] - mScrollY);
+ if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
+ dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
+ }
+
+ final int left = mLeft;
+ final int top = mTop;
+
+ if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
+ if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
+ dirty.setEmpty();
+ }
+ }
+
+ location[CHILD_LEFT_INDEX] = left;
+ location[CHILD_TOP_INDEX] = top;
+ } else {
+
+ if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
+ dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
+ } else {
+ // in case the dirty rect extends outside the bounds of this container
+ dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
+ }
+ location[CHILD_LEFT_INDEX] = mLeft;
+ location[CHILD_TOP_INDEX] = mTop;
+
+ mPrivateFlags &= ~PFLAG_DRAWN;
+ }
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ if (mLayerType != LAYER_TYPE_NONE) {
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ }
+
+ return mParent;
+ }
+
+ return null;
+ }
+
+ /**
+ * Offset a rectangle that is in a descendant's coordinate
+ * space into our coordinate space.
+ * @param descendant A descendant of this view
+ * @param rect A rectangle defined in descendant's coordinate space.
+ */
+ public final void offsetDescendantRectToMyCoords(View descendant, Rect rect) {
+ offsetRectBetweenParentAndChild(descendant, rect, true, false);
+ }
+
+ /**
+ * Offset a rectangle that is in our coordinate space into an ancestor's
+ * coordinate space.
+ * @param descendant A descendant of this view
+ * @param rect A rectangle defined in descendant's coordinate space.
+ */
+ public final void offsetRectIntoDescendantCoords(View descendant, Rect rect) {
+ offsetRectBetweenParentAndChild(descendant, rect, false, false);
+ }
+
+ /**
+ * Helper method that offsets a rect either from parent to descendant or
+ * descendant to parent.
+ */
+ void offsetRectBetweenParentAndChild(View descendant, Rect rect,
+ boolean offsetFromChildToParent, boolean clipToBounds) {
+
+ // already in the same coord system :)
+ if (descendant == this) {
+ return;
+ }
+
+ ViewParent theParent = descendant.mParent;
+
+ // search and offset up to the parent
+ while ((theParent != null)
+ && (theParent instanceof View)
+ && (theParent != this)) {
+
+ if (offsetFromChildToParent) {
+ rect.offset(descendant.mLeft - descendant.mScrollX,
+ descendant.mTop - descendant.mScrollY);
+ if (clipToBounds) {
+ View p = (View) theParent;
+ boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
+ p.mBottom - p.mTop);
+ if (!intersected) {
+ rect.setEmpty();
+ }
+ }
+ } else {
+ if (clipToBounds) {
+ View p = (View) theParent;
+ boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
+ p.mBottom - p.mTop);
+ if (!intersected) {
+ rect.setEmpty();
+ }
+ }
+ rect.offset(descendant.mScrollX - descendant.mLeft,
+ descendant.mScrollY - descendant.mTop);
+ }
+
+ descendant = (View) theParent;
+ theParent = descendant.mParent;
+ }
+
+ // now that we are up to this view, need to offset one more time
+ // to get into our coordinate space
+ if (theParent == this) {
+ if (offsetFromChildToParent) {
+ rect.offset(descendant.mLeft - descendant.mScrollX,
+ descendant.mTop - descendant.mScrollY);
+ } else {
+ rect.offset(descendant.mScrollX - descendant.mLeft,
+ descendant.mScrollY - descendant.mTop);
+ }
+ } else {
+ throw new IllegalArgumentException("parameter must be a descendant of this view");
+ }
+ }
+
+ /**
+ * Offset the vertical location of all children of this view by the specified number of pixels.
+ *
+ * @param offset the number of pixels to offset
+ *
+ * @hide
+ */
+ public void offsetChildrenTopAndBottom(int offset) {
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ boolean invalidate = false;
+
+ for (int i = 0; i < count; i++) {
+ final View v = children[i];
+ v.mTop += offset;
+ v.mBottom += offset;
+ if (v.mRenderNode != null) {
+ invalidate = true;
+ v.mRenderNode.offsetTopAndBottom(offset);
+ }
+ }
+
+ if (invalidate) {
+ invalidateViewProperty(false, false);
+ }
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+
+ @Override
+ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+ return getChildVisibleRect(child, r, offset, false);
+ }
+
+ /**
+ * @param forceParentCheck true to guarantee that this call will propagate to all ancestors,
+ * false otherwise
+ *
+ * @hide
+ */
+ public boolean getChildVisibleRect(
+ View child, Rect r, android.graphics.Point offset, boolean forceParentCheck) {
+ // It doesn't make a whole lot of sense to call this on a view that isn't attached,
+ // but for some simple tests it can be useful. If we don't have attach info this
+ // will allocate memory.
+ final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
+ rect.set(r);
+
+ if (!child.hasIdentityMatrix()) {
+ child.getMatrix().mapRect(rect);
+ }
+
+ final int dx = child.mLeft - mScrollX;
+ final int dy = child.mTop - mScrollY;
+
+ rect.offset(dx, dy);
+
+ if (offset != null) {
+ if (!child.hasIdentityMatrix()) {
+ float[] position = mAttachInfo != null ? mAttachInfo.mTmpTransformLocation
+ : new float[2];
+ position[0] = offset.x;
+ position[1] = offset.y;
+ child.getMatrix().mapPoints(position);
+ offset.x = Math.round(position[0]);
+ offset.y = Math.round(position[1]);
+ }
+ offset.x += dx;
+ offset.y += dy;
+ }
+
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ boolean rectIsVisible = true;
+ if (mParent == null ||
+ (mParent instanceof ViewGroup && ((ViewGroup) mParent).getClipChildren())) {
+ // Clip to bounds.
+ rectIsVisible = rect.intersect(0, 0, width, height);
+ }
+
+ if ((forceParentCheck || rectIsVisible)
+ && (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
+ // Clip to padding.
+ rectIsVisible = rect.intersect(mPaddingLeft, mPaddingTop,
+ width - mPaddingRight, height - mPaddingBottom);
+ }
+
+ if ((forceParentCheck || rectIsVisible) && mClipBounds != null) {
+ // Clip to clipBounds.
+ rectIsVisible = rect.intersect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
+ mClipBounds.bottom);
+ }
+ r.set((int) Math.floor(rect.left), (int) Math.floor(rect.top),
+ (int) Math.ceil(rect.right), (int) Math.ceil(rect.bottom));
+
+ if ((forceParentCheck || rectIsVisible) && mParent != null) {
+ if (mParent instanceof ViewGroup) {
+ rectIsVisible = ((ViewGroup) mParent)
+ .getChildVisibleRect(this, r, offset, forceParentCheck);
+ } else {
+ rectIsVisible = mParent.getChildVisibleRect(this, r, offset);
+ }
+ }
+ return rectIsVisible;
+ }
+
+ @Override
+ public final void layout(int l, int t, int r, int b) {
+ if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
+ if (mTransition != null) {
+ mTransition.layoutChange(this);
+ }
+ super.layout(l, t, r, b);
+ } else {
+ // record the fact that we noop'd it; request layout when transition finishes
+ mLayoutCalledWhileSuppressed = true;
+ }
+ }
+
+ @Override
+ protected abstract void onLayout(boolean changed,
+ int l, int t, int r, int b);
+
+ /**
+ * Indicates whether the view group has the ability to animate its children
+ * after the first layout.
+ *
+ * @return true if the children can be animated, false otherwise
+ */
+ protected boolean canAnimate() {
+ return mLayoutAnimationController != null;
+ }
+
+ /**
+ * Runs the layout animation. Calling this method triggers a relayout of
+ * this view group.
+ */
+ public void startLayoutAnimation() {
+ if (mLayoutAnimationController != null) {
+ mGroupFlags |= FLAG_RUN_ANIMATION;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Schedules the layout animation to be played after the next layout pass
+ * of this view group. This can be used to restart the layout animation
+ * when the content of the view group changes or when the activity is
+ * paused and resumed.
+ */
+ public void scheduleLayoutAnimation() {
+ mGroupFlags |= FLAG_RUN_ANIMATION;
+ }
+
+ /**
+ * Sets the layout animation controller used to animate the group's
+ * children after the first layout.
+ *
+ * @param controller the animation controller
+ */
+ public void setLayoutAnimation(LayoutAnimationController controller) {
+ mLayoutAnimationController = controller;
+ if (mLayoutAnimationController != null) {
+ mGroupFlags |= FLAG_RUN_ANIMATION;
+ }
+ }
+
+ /**
+ * Returns the layout animation controller used to animate the group's
+ * children.
+ *
+ * @return the current animation controller
+ */
+ public LayoutAnimationController getLayoutAnimation() {
+ return mLayoutAnimationController;
+ }
+
+ /**
+ * Indicates whether the children's drawing cache is used during a layout
+ * animation. By default, the drawing cache is enabled but this will prevent
+ * nested layout animations from working. To nest animations, you must disable
+ * the cache.
+ *
+ * @return true if the animation cache is enabled, false otherwise
+ *
+ * @see #setAnimationCacheEnabled(boolean)
+ * @see View#setDrawingCacheEnabled(boolean)
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+ * Caching behavior of children may be controlled through {@link View#setLayerType(int, Paint)}.
+ */
+ @Deprecated
+ public boolean isAnimationCacheEnabled() {
+ return (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
+ }
+
+ /**
+ * Enables or disables the children's drawing cache during a layout animation.
+ * By default, the drawing cache is enabled but this will prevent nested
+ * layout animations from working. To nest animations, you must disable the
+ * cache.
+ *
+ * @param enabled true to enable the animation cache, false otherwise
+ *
+ * @see #isAnimationCacheEnabled()
+ * @see View#setDrawingCacheEnabled(boolean)
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+ * Caching behavior of children may be controlled through {@link View#setLayerType(int, Paint)}.
+ */
+ @Deprecated
+ public void setAnimationCacheEnabled(boolean enabled) {
+ setBooleanFlag(FLAG_ANIMATION_CACHE, enabled);
+ }
+
+ /**
+ * Indicates whether this ViewGroup will always try to draw its children using their
+ * drawing cache. By default this property is enabled.
+ *
+ * @return true if the animation cache is enabled, false otherwise
+ *
+ * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+ * @see #setChildrenDrawnWithCacheEnabled(boolean)
+ * @see View#setDrawingCacheEnabled(boolean)
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+ * Child views may no longer have their caching behavior disabled by parents.
+ */
+ @Deprecated
+ public boolean isAlwaysDrawnWithCacheEnabled() {
+ return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE;
+ }
+
+ /**
+ * Indicates whether this ViewGroup will always try to draw its children using their
+ * drawing cache. This property can be set to true when the cache rendering is
+ * slightly different from the children's normal rendering. Renderings can be different,
+ * for instance, when the cache's quality is set to low.
+ *
+ * When this property is disabled, the ViewGroup will use the drawing cache of its
+ * children only when asked to. It's usually the task of subclasses to tell ViewGroup
+ * when to start using the drawing cache and when to stop using it.
+ *
+ * @param always true to always draw with the drawing cache, false otherwise
+ *
+ * @see #isAlwaysDrawnWithCacheEnabled()
+ * @see #setChildrenDrawnWithCacheEnabled(boolean)
+ * @see View#setDrawingCacheEnabled(boolean)
+ * @see View#setDrawingCacheQuality(int)
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+ * Child views may no longer have their caching behavior disabled by parents.
+ */
+ @Deprecated
+ public void setAlwaysDrawnWithCacheEnabled(boolean always) {
+ setBooleanFlag(FLAG_ALWAYS_DRAWN_WITH_CACHE, always);
+ }
+
+ /**
+ * Indicates whether the ViewGroup is currently drawing its children using
+ * their drawing cache.
+ *
+ * @return true if children should be drawn with their cache, false otherwise
+ *
+ * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+ * @see #setChildrenDrawnWithCacheEnabled(boolean)
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+ * Child views may no longer be forced to cache their rendering state by their parents.
+ * Use {@link View#setLayerType(int, Paint)} on individual Views instead.
+ */
+ @Deprecated
+ protected boolean isChildrenDrawnWithCacheEnabled() {
+ return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE;
+ }
+
+ /**
+ * Tells the ViewGroup to draw its children using their drawing cache. This property
+ * is ignored when {@link #isAlwaysDrawnWithCacheEnabled()} is true. A child's drawing cache
+ * will be used only if it has been enabled.
+ *
+ * Subclasses should call this method to start and stop using the drawing cache when
+ * they perform performance sensitive operations, like scrolling or animating.
+ *
+ * @param enabled true if children should be drawn with their cache, false otherwise
+ *
+ * @see #setAlwaysDrawnWithCacheEnabled(boolean)
+ * @see #isChildrenDrawnWithCacheEnabled()
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#M}, this property is ignored.
+ * Child views may no longer be forced to cache their rendering state by their parents.
+ * Use {@link View#setLayerType(int, Paint)} on individual Views instead.
+ */
+ @Deprecated
+ protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+ setBooleanFlag(FLAG_CHILDREN_DRAWN_WITH_CACHE, enabled);
+ }
+
+ /**
+ * Indicates whether the ViewGroup is drawing its children in the order defined by
+ * {@link #getChildDrawingOrder(int, int)}.
+ *
+ * @return true if children drawing order is defined by {@link #getChildDrawingOrder(int, int)},
+ * false otherwise
+ *
+ * @see #setChildrenDrawingOrderEnabled(boolean)
+ * @see #getChildDrawingOrder(int, int)
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ protected boolean isChildrenDrawingOrderEnabled() {
+ return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER;
+ }
+
+ /**
+ * Tells the ViewGroup whether to draw its children in the order defined by the method
+ * {@link #getChildDrawingOrder(int, int)}.
+ * <p>
+ * Note that {@link View#getZ() Z} reordering, done by {@link #dispatchDraw(Canvas)},
+ * will override custom child ordering done via this method.
+ *
+ * @param enabled true if the order of the children when drawing is determined by
+ * {@link #getChildDrawingOrder(int, int)}, false otherwise
+ *
+ * @see #isChildrenDrawingOrderEnabled()
+ * @see #getChildDrawingOrder(int, int)
+ */
+ protected void setChildrenDrawingOrderEnabled(boolean enabled) {
+ setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled);
+ }
+
+ private boolean hasBooleanFlag(int flag) {
+ return (mGroupFlags & flag) == flag;
+ }
+
+ private void setBooleanFlag(int flag, boolean value) {
+ if (value) {
+ mGroupFlags |= flag;
+ } else {
+ mGroupFlags &= ~flag;
+ }
+ }
+
+ /**
+ * Returns an integer indicating what types of drawing caches are kept in memory.
+ *
+ * @see #setPersistentDrawingCache(int)
+ * @see #setAnimationCacheEnabled(boolean)
+ *
+ * @return one or a combination of {@link #PERSISTENT_NO_CACHE},
+ * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
+ * and {@link #PERSISTENT_ALL_CACHES}
+ */
+ @ViewDebug.ExportedProperty(category = "drawing", mapping = {
+ @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"),
+ @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
+ @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"),
+ @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES, to = "ALL")
+ })
+ public int getPersistentDrawingCache() {
+ return mPersistentDrawingCache;
+ }
+
+ /**
+ * Indicates what types of drawing caches should be kept in memory after
+ * they have been created.
+ *
+ * @see #getPersistentDrawingCache()
+ * @see #setAnimationCacheEnabled(boolean)
+ *
+ * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
+ * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
+ * and {@link #PERSISTENT_ALL_CACHES}
+ */
+ public void setPersistentDrawingCache(int drawingCacheToKeep) {
+ mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
+ }
+
+ private void setLayoutMode(int layoutMode, boolean explicitly) {
+ mLayoutMode = layoutMode;
+ setBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET, explicitly);
+ }
+
+ /**
+ * Recursively traverse the view hierarchy, resetting the layoutMode of any
+ * descendants that had inherited a different layoutMode from a previous parent.
+ * Recursion terminates when a descendant's mode is:
+ * <ul>
+ * <li>Undefined</li>
+ * <li>The same as the root node's</li>
+ * <li>A mode that had been explicitly set</li>
+ * <ul/>
+ * The first two clauses are optimizations.
+ * @param layoutModeOfRoot
+ */
+ @Override
+ void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
+ if (mLayoutMode == LAYOUT_MODE_UNDEFINED ||
+ mLayoutMode == layoutModeOfRoot ||
+ hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) {
+ return;
+ }
+ setLayoutMode(LAYOUT_MODE_UNDEFINED, false);
+
+ // apply recursively
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ getChildAt(i).invalidateInheritedLayoutMode(layoutModeOfRoot);
+ }
+ }
+
+ /**
+ * Returns the basis of alignment during layout operations on this ViewGroup:
+ * either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
+ * <p>
+ * If no layoutMode was explicitly set, either programmatically or in an XML resource,
+ * the method returns the layoutMode of the view's parent ViewGroup if such a parent exists,
+ * otherwise the method returns a default value of {@link #LAYOUT_MODE_CLIP_BOUNDS}.
+ *
+ * @return the layout mode to use during layout operations
+ *
+ * @see #setLayoutMode(int)
+ */
+ public int getLayoutMode() {
+ if (mLayoutMode == LAYOUT_MODE_UNDEFINED) {
+ int inheritedLayoutMode = (mParent instanceof ViewGroup) ?
+ ((ViewGroup) mParent).getLayoutMode() : LAYOUT_MODE_DEFAULT;
+ setLayoutMode(inheritedLayoutMode, false);
+ }
+ return mLayoutMode;
+ }
+
+ /**
+ * Sets the basis of alignment during the layout of this ViewGroup.
+ * Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or
+ * {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
+ *
+ * @param layoutMode the layout mode to use during layout operations
+ *
+ * @see #getLayoutMode()
+ * @attr ref android.R.styleable#ViewGroup_layoutMode
+ */
+ public void setLayoutMode(int layoutMode) {
+ if (mLayoutMode != layoutMode) {
+ invalidateInheritedLayoutMode(layoutMode);
+ setLayoutMode(layoutMode, layoutMode != LAYOUT_MODE_UNDEFINED);
+ requestLayout();
+ }
+ }
+
+ /**
+ * Returns a new set of layout parameters based on the supplied attributes set.
+ *
+ * @param attrs the attributes to build the layout parameters from
+ *
+ * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
+ * of its descendants
+ */
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ /**
+ * Returns a safe set of layout parameters based on the supplied layout params.
+ * When a ViewGroup is passed a View whose layout params do not pass the test of
+ * {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method
+ * is invoked. This method should return a new set of layout params suitable for
+ * this ViewGroup, possibly by copying the appropriate attributes from the
+ * specified set of layout params.
+ *
+ * @param p The layout parameters to convert into a suitable set of layout parameters
+ * for this ViewGroup.
+ *
+ * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
+ * of its descendants
+ */
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return p;
+ }
+
+ /**
+ * Returns a set of default layout parameters. These parameters are requested
+ * when the View passed to {@link #addView(View)} has no layout parameters
+ * already set. If null is returned, an exception is thrown from addView.
+ *
+ * @return a set of default layout parameters or null
+ */
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected void debug(int depth) {
+ super.debug(depth);
+ String output;
+
+ if (mFocused != null) {
+ output = debugIndent(depth);
+ output += "mFocused";
+ Log.d(VIEW_LOG_TAG, output);
+ mFocused.debug(depth + 1);
+ }
+ if (mDefaultFocus != null) {
+ output = debugIndent(depth);
+ output += "mDefaultFocus";
+ Log.d(VIEW_LOG_TAG, output);
+ mDefaultFocus.debug(depth + 1);
+ }
+ if (mFocusedInCluster != null) {
+ output = debugIndent(depth);
+ output += "mFocusedInCluster";
+ Log.d(VIEW_LOG_TAG, output);
+ mFocusedInCluster.debug(depth + 1);
+ }
+ if (mChildrenCount != 0) {
+ output = debugIndent(depth);
+ output += "{";
+ Log.d(VIEW_LOG_TAG, output);
+ }
+ int count = mChildrenCount;
+ for (int i = 0; i < count; i++) {
+ View child = mChildren[i];
+ child.debug(depth + 1);
+ }
+
+ if (mChildrenCount != 0) {
+ output = debugIndent(depth);
+ output += "}";
+ Log.d(VIEW_LOG_TAG, output);
+ }
+ }
+
+ /**
+ * Returns the position in the group of the specified child view.
+ *
+ * @param child the view for which to get the position
+ * @return a positive integer representing the position of the view in the
+ * group, or -1 if the view does not exist in the group
+ */
+ public int indexOfChild(View child) {
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ if (children[i] == child) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the number of children in the group.
+ *
+ * @return a positive integer representing the number of children in
+ * the group
+ */
+ public int getChildCount() {
+ return mChildrenCount;
+ }
+
+ /**
+ * Returns the view at the specified position in the group.
+ *
+ * @param index the position at which to get the view from
+ * @return the view at the specified position or null if the position
+ * does not exist within the group
+ */
+ public View getChildAt(int index) {
+ if (index < 0 || index >= mChildrenCount) {
+ return null;
+ }
+ return mChildren[index];
+ }
+
+ /**
+ * Ask all of the children of this view to measure themselves, taking into
+ * account both the MeasureSpec requirements for this view and its padding.
+ * We skip children that are in the GONE state The heavy lifting is done in
+ * getChildMeasureSpec.
+ *
+ * @param widthMeasureSpec The width requirements for this view
+ * @param heightMeasureSpec The height requirements for this view
+ */
+ protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
+ final int size = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < size; ++i) {
+ final View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
+ measureChild(child, widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ }
+
+ /**
+ * Ask one of the children of this view to measure itself, taking into
+ * account both the MeasureSpec requirements for this view and its padding.
+ * The heavy lifting is done in getChildMeasureSpec.
+ *
+ * @param child The child to measure
+ * @param parentWidthMeasureSpec The width requirements for this view
+ * @param parentHeightMeasureSpec The height requirements for this view
+ */
+ protected void measureChild(View child, int parentWidthMeasureSpec,
+ int parentHeightMeasureSpec) {
+ final LayoutParams lp = child.getLayoutParams();
+
+ final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+ mPaddingLeft + mPaddingRight, lp.width);
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+ mPaddingTop + mPaddingBottom, lp.height);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ /**
+ * Ask one of the children of this view to measure itself, taking into
+ * account both the MeasureSpec requirements for this view and its padding
+ * and margins. The child must have MarginLayoutParams The heavy lifting is
+ * done in getChildMeasureSpec.
+ *
+ * @param child The child to measure
+ * @param parentWidthMeasureSpec The width requirements for this view
+ * @param widthUsed Extra space that has been used up by the parent
+ * horizontally (possibly by other children of the parent)
+ * @param parentHeightMeasureSpec The height requirements for this view
+ * @param heightUsed Extra space that has been used up by the parent
+ * vertically (possibly by other children of the parent)
+ */
+ protected void measureChildWithMargins(View child,
+ int parentWidthMeasureSpec, int widthUsed,
+ int parentHeightMeasureSpec, int heightUsed) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+ mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ + widthUsed, lp.width);
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+ mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ + heightUsed, lp.height);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ /**
+ * Does the hard part of measureChildren: figuring out the MeasureSpec to
+ * pass to a particular child. This method figures out the right MeasureSpec
+ * for one dimension (height or width) of one child view.
+ *
+ * The goal is to combine information from our MeasureSpec with the
+ * LayoutParams of the child to get the best possible results. For example,
+ * if the this view knows its size (because its MeasureSpec has a mode of
+ * EXACTLY), and the child has indicated in its LayoutParams that it wants
+ * to be the same size as the parent, the parent should ask the child to
+ * layout given an exact size.
+ *
+ * @param spec The requirements for this view
+ * @param padding The padding of this view for the current dimension and
+ * margins, if applicable
+ * @param childDimension How big the child wants to be in the current
+ * dimension
+ * @return a MeasureSpec integer for the child
+ */
+ public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
+ int specMode = MeasureSpec.getMode(spec);
+ int specSize = MeasureSpec.getSize(spec);
+
+ int size = Math.max(0, specSize - padding);
+
+ int resultSize = 0;
+ int resultMode = 0;
+
+ switch (specMode) {
+ // Parent has imposed an exact size on us
+ case MeasureSpec.EXACTLY:
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ // Child wants to be our size. So be it.
+ resultSize = size;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ // Child wants to determine its own size. It can't be
+ // bigger than us.
+ resultSize = size;
+ resultMode = MeasureSpec.AT_MOST;
+ }
+ break;
+
+ // Parent has imposed a maximum size on us
+ case MeasureSpec.AT_MOST:
+ if (childDimension >= 0) {
+ // Child wants a specific size... so be it
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ // Child wants to be our size, but our size is not fixed.
+ // Constrain child to not be bigger than us.
+ resultSize = size;
+ resultMode = MeasureSpec.AT_MOST;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ // Child wants to determine its own size. It can't be
+ // bigger than us.
+ resultSize = size;
+ resultMode = MeasureSpec.AT_MOST;
+ }
+ break;
+
+ // Parent asked to see how big we want to be
+ case MeasureSpec.UNSPECIFIED:
+ if (childDimension >= 0) {
+ // Child wants a specific size... let him have it
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ // Child wants to be our size... find out how big it should
+ // be
+ resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ // Child wants to determine its own size.... find out how
+ // big it should be
+ resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+ break;
+ }
+ //noinspection ResourceType
+ return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+ }
+
+
+ /**
+ * Removes any pending animations for views that have been removed. Call
+ * this if you don't want animations for exiting views to stack up.
+ */
+ public void clearDisappearingChildren() {
+ final ArrayList<View> disappearingChildren = mDisappearingChildren;
+ if (disappearingChildren != null) {
+ final int count = disappearingChildren.size();
+ for (int i = 0; i < count; i++) {
+ final View view = disappearingChildren.get(i);
+ if (view.mAttachInfo != null) {
+ view.dispatchDetachedFromWindow();
+ }
+ view.clearAnimation();
+ }
+ disappearingChildren.clear();
+ invalidate();
+ }
+ }
+
+ /**
+ * Add a view which is removed from mChildren but still needs animation
+ *
+ * @param v View to add
+ */
+ private void addDisappearingView(View v) {
+ ArrayList<View> disappearingChildren = mDisappearingChildren;
+
+ if (disappearingChildren == null) {
+ disappearingChildren = mDisappearingChildren = new ArrayList<View>();
+ }
+
+ disappearingChildren.add(v);
+ }
+
+ /**
+ * Cleanup a view when its animation is done. This may mean removing it from
+ * the list of disappearing views.
+ *
+ * @param view The view whose animation has finished
+ * @param animation The animation, cannot be null
+ */
+ void finishAnimatingView(final View view, Animation animation) {
+ final ArrayList<View> disappearingChildren = mDisappearingChildren;
+ if (disappearingChildren != null) {
+ if (disappearingChildren.contains(view)) {
+ disappearingChildren.remove(view);
+
+ if (view.mAttachInfo != null) {
+ view.dispatchDetachedFromWindow();
+ }
+
+ view.clearAnimation();
+ mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+ }
+ }
+
+ if (animation != null && !animation.getFillAfter()) {
+ view.clearAnimation();
+ }
+
+ if ((view.mPrivateFlags & PFLAG_ANIMATION_STARTED) == PFLAG_ANIMATION_STARTED) {
+ view.onAnimationEnd();
+ // Should be performed by onAnimationEnd() but this avoid an infinite loop,
+ // so we'd rather be safe than sorry
+ view.mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
+ // Draw one more frame after the animation is done
+ mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+ }
+ }
+
+ /**
+ * Utility function called by View during invalidation to determine whether a view that
+ * is invisible or gone should still be invalidated because it is being transitioned (and
+ * therefore still needs to be drawn).
+ */
+ boolean isViewTransitioning(View view) {
+ return (mTransitioningViews != null && mTransitioningViews.contains(view));
+ }
+
+ /**
+ * This method tells the ViewGroup that the given View object, which should have this
+ * ViewGroup as its parent,
+ * should be kept around (re-displayed when the ViewGroup draws its children) even if it
+ * is removed from its parent. This allows animations, such as those used by
+ * {@link android.app.Fragment} and {@link android.animation.LayoutTransition} to animate
+ * the removal of views. A call to this method should always be accompanied by a later call
+ * to {@link #endViewTransition(View)}, such as after an animation on the View has finished,
+ * so that the View finally gets removed.
+ *
+ * @param view The View object to be kept visible even if it gets removed from its parent.
+ */
+ public void startViewTransition(View view) {
+ if (view.mParent == this) {
+ if (mTransitioningViews == null) {
+ mTransitioningViews = new ArrayList<View>();
+ }
+ mTransitioningViews.add(view);
+ }
+ }
+
+ /**
+ * This method should always be called following an earlier call to
+ * {@link #startViewTransition(View)}. The given View is finally removed from its parent
+ * and will no longer be displayed. Note that this method does not perform the functionality
+ * of removing a view from its parent; it just discontinues the display of a View that
+ * has previously been removed.
+ *
+ * @return view The View object that has been removed but is being kept around in the visible
+ * hierarchy by an earlier call to {@link #startViewTransition(View)}.
+ */
+ public void endViewTransition(View view) {
+ if (mTransitioningViews != null) {
+ mTransitioningViews.remove(view);
+ final ArrayList<View> disappearingChildren = mDisappearingChildren;
+ if (disappearingChildren != null && disappearingChildren.contains(view)) {
+ disappearingChildren.remove(view);
+ if (mVisibilityChangingChildren != null &&
+ mVisibilityChangingChildren.contains(view)) {
+ mVisibilityChangingChildren.remove(view);
+ } else {
+ if (view.mAttachInfo != null) {
+ view.dispatchDetachedFromWindow();
+ }
+ if (view.mParent != null) {
+ view.mParent = null;
+ }
+ }
+ invalidate();
+ }
+ }
+ }
+
+ private LayoutTransition.TransitionListener mLayoutTransitionListener =
+ new LayoutTransition.TransitionListener() {
+ @Override
+ public void startTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType) {
+ // We only care about disappearing items, since we need special logic to keep
+ // those items visible after they've been 'removed'
+ if (transitionType == LayoutTransition.DISAPPEARING) {
+ startViewTransition(view);
+ }
+ }
+
+ @Override
+ public void endTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType) {
+ if (mLayoutCalledWhileSuppressed && !transition.isChangingLayout()) {
+ requestLayout();
+ mLayoutCalledWhileSuppressed = false;
+ }
+ if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) {
+ endViewTransition(view);
+ }
+ }
+ };
+
+ /**
+ * Tells this ViewGroup to suppress all layout() calls until layout
+ * suppression is disabled with a later call to suppressLayout(false).
+ * When layout suppression is disabled, a requestLayout() call is sent
+ * if layout() was attempted while layout was being suppressed.
+ *
+ * @hide
+ */
+ public void suppressLayout(boolean suppress) {
+ mSuppressLayout = suppress;
+ if (!suppress) {
+ if (mLayoutCalledWhileSuppressed) {
+ requestLayout();
+ mLayoutCalledWhileSuppressed = false;
+ }
+ }
+ }
+
+ /**
+ * Returns whether layout calls on this container are currently being
+ * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}.
+ *
+ * @return true if layout calls are currently suppressed, false otherwise.
+ *
+ * @hide
+ */
+ public boolean isLayoutSuppressed() {
+ return mSuppressLayout;
+ }
+
+ @Override
+ public boolean gatherTransparentRegion(Region region) {
+ // If no transparent regions requested, we are always opaque.
+ final boolean meOpaque = (mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0;
+ if (meOpaque && region == null) {
+ // The caller doesn't care about the region, so stop now.
+ return true;
+ }
+ super.gatherTransparentRegion(region);
+ // Instead of naively traversing the view tree, we have to traverse according to the Z
+ // order here. We need to go with the same order as dispatchDraw().
+ // One example is that after surfaceView punch a hole, we will still allow other views drawn
+ // on top of that hole. In this case, those other views should be able to cut the
+ // transparent region into smaller area.
+ final int childrenCount = mChildrenCount;
+ boolean noneOfTheChildrenAreTransparent = true;
+ if (childrenCount > 0) {
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+ if (!child.gatherTransparentRegion(region)) {
+ noneOfTheChildrenAreTransparent = false;
+ }
+ }
+ }
+ if (preorderedList != null) preorderedList.clear();
+ }
+ return meOpaque || noneOfTheChildrenAreTransparent;
+ }
+
+ @Override
+ public void requestTransparentRegion(View child) {
+ if (child != null) {
+ child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
+ if (mParent != null) {
+ mParent.requestTransparentRegion(this);
+ }
+ }
+ }
+
+ @Override
+ public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+ insets = super.dispatchApplyWindowInsets(insets);
+ if (!insets.isConsumed()) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ insets = getChildAt(i).dispatchApplyWindowInsets(insets);
+ if (insets.isConsumed()) {
+ break;
+ }
+ }
+ }
+ return insets;
+ }
+
+ /**
+ * Returns the animation listener to which layout animation events are
+ * sent.
+ *
+ * @return an {@link android.view.animation.Animation.AnimationListener}
+ */
+ public Animation.AnimationListener getLayoutAnimationListener() {
+ return mAnimationListener;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ if ((mGroupFlags & FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE) != 0) {
+ if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
+ throw new IllegalStateException("addStateFromChildren cannot be enabled if a"
+ + " child has duplicateParentState set to true");
+ }
+
+ final View[] children = mChildren;
+ final int count = mChildrenCount;
+
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ if ((child.mViewFlags & DUPLICATE_PARENT_STATE) != 0) {
+ child.refreshDrawableState();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ final View[] children = mChildren;
+ final int count = mChildrenCount;
+ for (int i = 0; i < count; i++) {
+ children[i].jumpDrawablesToCurrentState();
+ }
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) == 0) {
+ return super.onCreateDrawableState(extraSpace);
+ }
+
+ int need = 0;
+ int n = getChildCount();
+ for (int i = 0; i < n; i++) {
+ int[] childState = getChildAt(i).getDrawableState();
+
+ if (childState != null) {
+ need += childState.length;
+ }
+ }
+
+ int[] state = super.onCreateDrawableState(extraSpace + need);
+
+ for (int i = 0; i < n; i++) {
+ int[] childState = getChildAt(i).getDrawableState();
+
+ if (childState != null) {
+ state = mergeDrawableStates(state, childState);
+ }
+ }
+
+ return state;
+ }
+
+ /**
+ * Sets whether this ViewGroup's drawable states also include
+ * its children's drawable states. This is used, for example, to
+ * make a group appear to be focused when its child EditText or button
+ * is focused.
+ */
+ public void setAddStatesFromChildren(boolean addsStates) {
+ if (addsStates) {
+ mGroupFlags |= FLAG_ADD_STATES_FROM_CHILDREN;
+ } else {
+ mGroupFlags &= ~FLAG_ADD_STATES_FROM_CHILDREN;
+ }
+
+ refreshDrawableState();
+ }
+
+ /**
+ * Returns whether this ViewGroup's drawable states also include
+ * its children's drawable states. This is used, for example, to
+ * make a group appear to be focused when its child EditText or button
+ * is focused.
+ */
+ public boolean addStatesFromChildren() {
+ return (mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0;
+ }
+
+ /**
+ * If {@link #addStatesFromChildren} is true, refreshes this group's
+ * drawable state (to include the states from its children).
+ */
+ @Override
+ public void childDrawableStateChanged(View child) {
+ if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
+ refreshDrawableState();
+ }
+ }
+
+ /**
+ * Specifies the animation listener to which layout animation events must
+ * be sent. Only
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationStart(Animation)}
+ * and
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationEnd(Animation)}
+ * are invoked.
+ *
+ * @param animationListener the layout animation listener
+ */
+ public void setLayoutAnimationListener(Animation.AnimationListener animationListener) {
+ mAnimationListener = animationListener;
+ }
+
+ /**
+ * This method is called by LayoutTransition when there are 'changing' animations that need
+ * to start after the layout/setup phase. The request is forwarded to the ViewAncestor, who
+ * starts all pending transitions prior to the drawing phase in the current traversal.
+ *
+ * @param transition The LayoutTransition to be started on the next traversal.
+ *
+ * @hide
+ */
+ public void requestTransitionStart(LayoutTransition transition) {
+ ViewRootImpl viewAncestor = getViewRootImpl();
+ if (viewAncestor != null) {
+ viewAncestor.requestTransitionStart(transition);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean resolveRtlPropertiesIfNeeded() {
+ final boolean result = super.resolveRtlPropertiesIfNeeded();
+ // We dont need to resolve the children RTL properties if nothing has changed for the parent
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resolveRtlPropertiesIfNeeded();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean resolveLayoutDirection() {
+ final boolean result = super.resolveLayoutDirection();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resolveLayoutDirection();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean resolveTextDirection() {
+ final boolean result = super.resolveTextDirection();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextDirectionInherited()) {
+ child.resolveTextDirection();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean resolveTextAlignment() {
+ final boolean result = super.resolveTextAlignment();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextAlignmentInherited()) {
+ child.resolveTextAlignment();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resolvePadding() {
+ super.resolvePadding();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited() && !child.isPaddingResolved()) {
+ child.resolvePadding();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void resolveDrawables() {
+ super.resolveDrawables();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited() && !child.areDrawablesResolved()) {
+ child.resolveDrawables();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resolveLayoutParams() {
+ super.resolveLayoutParams();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.resolveLayoutParams();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resetResolvedLayoutDirection() {
+ super.resetResolvedLayoutDirection();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resetResolvedLayoutDirection();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resetResolvedTextDirection() {
+ super.resetResolvedTextDirection();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextDirectionInherited()) {
+ child.resetResolvedTextDirection();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resetResolvedTextAlignment() {
+ super.resetResolvedTextAlignment();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextAlignmentInherited()) {
+ child.resetResolvedTextAlignment();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resetResolvedPadding() {
+ super.resetResolvedPadding();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resetResolvedPadding();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void resetResolvedDrawables() {
+ super.resetResolvedDrawables();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resetResolvedDrawables();
+ }
+ }
+ }
+
+ /**
+ * Return true if the pressed state should be delayed for children or descendants of this
+ * ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
+ * This prevents the pressed state from appearing when the user is actually trying to scroll
+ * the content.
+ *
+ * The default implementation returns true for compatibility reasons. Subclasses that do
+ * not scroll should generally override this method and return false.
+ */
+ public boolean shouldDelayChildPressedState() {
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ mNestedScrollAxes = axes;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * <p>The default implementation of onStopNestedScroll calls
+ * {@link #stopNestedScroll()} to halt any recursive nested scrolling in progress.</p>
+ */
+ @Override
+ public void onStopNestedScroll(View child) {
+ // Stop any recursive nested scrolling.
+ stopNestedScroll();
+ mNestedScrollAxes = 0;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ // Re-dispatch up the tree by default
+ dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ // Re-dispatch up the tree by default
+ dispatchNestedPreScroll(dx, dy, consumed, null);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ // Re-dispatch up the tree by default
+ return dispatchNestedFling(velocityX, velocityY, consumed);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+ // Re-dispatch up the tree by default
+ return dispatchNestedPreFling(velocityX, velocityY);
+ }
+
+ /**
+ * Return the current axes of nested scrolling for this ViewGroup.
+ *
+ * <p>A ViewGroup returning something other than {@link #SCROLL_AXIS_NONE} is currently
+ * acting as a nested scrolling parent for one or more descendant views in the hierarchy.</p>
+ *
+ * @return Flags indicating the current axes of nested scrolling
+ * @see #SCROLL_AXIS_HORIZONTAL
+ * @see #SCROLL_AXIS_VERTICAL
+ * @see #SCROLL_AXIS_NONE
+ */
+ public int getNestedScrollAxes() {
+ return mNestedScrollAxes;
+ }
+
+ /** @hide */
+ protected void onSetLayoutParams(View child, LayoutParams layoutParams) {
+ requestLayout();
+ }
+
+ /** @hide */
+ @Override
+ public void captureTransitioningViews(List<View> transitioningViews) {
+ if (getVisibility() != View.VISIBLE) {
+ return;
+ }
+ if (isTransitionGroup()) {
+ transitioningViews.add(this);
+ } else {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ child.captureTransitioningViews(transitioningViews);
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void findNamedViews(Map<String, View> namedElements) {
+ if (getVisibility() != VISIBLE && mGhostView == null) {
+ return;
+ }
+ super.findNamedViews(namedElements);
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ child.findNamedViews(namedElements);
+ }
+ }
+
+ /**
+ * LayoutParams are used by views to tell their parents how they want to be
+ * laid out. See
+ * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
+ * for a list of all child view attributes that this class supports.
+ *
+ * <p>
+ * The base LayoutParams class just describes how big the view wants to be
+ * for both width and height. For each dimension, it can specify one of:
+ * <ul>
+ * <li>FILL_PARENT (renamed MATCH_PARENT in API Level 8 and higher), which
+ * means that the view wants to be as big as its parent (minus padding)
+ * <li> WRAP_CONTENT, which means that the view wants to be just big enough
+ * to enclose its content (plus padding)
+ * <li> an exact number
+ * </ul>
+ * There are subclasses of LayoutParams for different subclasses of
+ * ViewGroup. For example, AbsoluteLayout has its own subclass of
+ * LayoutParams which adds an X and Y value.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating user interface layouts, read the
+ * <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> developer
+ * guide.</p></div>
+ *
+ * @attr ref android.R.styleable#ViewGroup_Layout_layout_height
+ * @attr ref android.R.styleable#ViewGroup_Layout_layout_width
+ */
+ public static class LayoutParams {
+ /**
+ * Special value for the height or width requested by a View.
+ * FILL_PARENT means that the view wants to be as big as its parent,
+ * minus the parent's padding, if any. This value is deprecated
+ * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ @Deprecated
+ public static final int FILL_PARENT = -1;
+
+ /**
+ * Special value for the height or width requested by a View.
+ * MATCH_PARENT means that the view wants to be as big as its parent,
+ * minus the parent's padding, if any. Introduced in API Level 8.
+ */
+ public static final int MATCH_PARENT = -1;
+
+ /**
+ * Special value for the height or width requested by a View.
+ * WRAP_CONTENT means that the view wants to be just large enough to fit
+ * its own internal content, taking its own padding into account.
+ */
+ public static final int WRAP_CONTENT = -2;
+
+ /**
+ * Information about how wide the view wants to be. Can be one of the
+ * constants FILL_PARENT (replaced by MATCH_PARENT
+ * in API Level 8) or WRAP_CONTENT, or an exact size.
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
+ @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
+ })
+ public int width;
+
+ /**
+ * Information about how tall the view wants to be. Can be one of the
+ * constants FILL_PARENT (replaced by MATCH_PARENT
+ * in API Level 8) or WRAP_CONTENT, or an exact size.
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
+ @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
+ })
+ public int height;
+
+ /**
+ * Used to animate layouts.
+ */
+ public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
+
+ /**
+ * Creates a new set of layout parameters. The values are extracted from
+ * the supplied attributes set and context. The XML attributes mapped
+ * to this set of layout parameters are:
+ *
+ * <ul>
+ * <li><code>layout_width</code>: the width, either an exact value,
+ * {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
+ * {@link #MATCH_PARENT} in API Level 8)</li>
+ * <li><code>layout_height</code>: the height, either an exact value,
+ * {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
+ * {@link #MATCH_PARENT} in API Level 8)</li>
+ * </ul>
+ *
+ * @param c the application environment
+ * @param attrs the set of attributes from which to extract the layout
+ * parameters' values
+ */
+ public LayoutParams(Context c, AttributeSet attrs) {
+ TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
+ setBaseAttributes(a,
+ R.styleable.ViewGroup_Layout_layout_width,
+ R.styleable.ViewGroup_Layout_layout_height);
+ a.recycle();
+ }
+
+ /**
+ * Creates a new set of layout parameters with the specified width
+ * and height.
+ *
+ * @param width the width, either {@link #WRAP_CONTENT},
+ * {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
+ * API Level 8), or a fixed size in pixels
+ * @param height the height, either {@link #WRAP_CONTENT},
+ * {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
+ * API Level 8), or a fixed size in pixels
+ */
+ public LayoutParams(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Copy constructor. Clones the width and height values of the source.
+ *
+ * @param source The layout params to copy from.
+ */
+ public LayoutParams(LayoutParams source) {
+ this.width = source.width;
+ this.height = source.height;
+ }
+
+ /**
+ * Used internally by MarginLayoutParams.
+ * @hide
+ */
+ LayoutParams() {
+ }
+
+ /**
+ * Extracts the layout parameters from the supplied attributes.
+ *
+ * @param a the style attributes to extract the parameters from
+ * @param widthAttr the identifier of the width attribute
+ * @param heightAttr the identifier of the height attribute
+ */
+ protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
+ width = a.getLayoutDimension(widthAttr, "layout_width");
+ height = a.getLayoutDimension(heightAttr, "layout_height");
+ }
+
+ /**
+ * Resolve layout parameters depending on the layout direction. Subclasses that care about
+ * layoutDirection changes should override this method. The default implementation does
+ * nothing.
+ *
+ * @param layoutDirection the direction of the layout
+ *
+ * {@link View#LAYOUT_DIRECTION_LTR}
+ * {@link View#LAYOUT_DIRECTION_RTL}
+ */
+ public void resolveLayoutDirection(int layoutDirection) {
+ }
+
+ /**
+ * Returns a String representation of this set of layout parameters.
+ *
+ * @param output the String to prepend to the internal representation
+ * @return a String with the following format: output +
+ * "ViewGroup.LayoutParams={ width=WIDTH, height=HEIGHT }"
+ *
+ * @hide
+ */
+ public String debug(String output) {
+ return output + "ViewGroup.LayoutParams={ width="
+ + sizeToString(width) + ", height=" + sizeToString(height) + " }";
+ }
+
+ /**
+ * Use {@code canvas} to draw suitable debugging annotations for these LayoutParameters.
+ *
+ * <p>This function is called automatically when the developer setting is enabled.<p/>
+ *
+ * <p>It is strongly advised to only call this function from debug builds as there is
+ * a risk of leaking unwanted layout information.<p/>
+ *
+ * @param view the view that contains these layout parameters
+ * @param canvas the canvas on which to draw
+ * @param paint the paint used to draw through
+ */
+ public void onDebugDraw(View view, Canvas canvas, Paint paint) {
+ }
+
+ /**
+ * Converts the specified size to a readable String.
+ *
+ * @param size the size to convert
+ * @return a String instance representing the supplied size
+ *
+ * @hide
+ */
+ protected static String sizeToString(int size) {
+ if (size == WRAP_CONTENT) {
+ return "wrap-content";
+ }
+ if (size == MATCH_PARENT) {
+ return "match-parent";
+ }
+ return String.valueOf(size);
+ }
+
+ /** @hide */
+ void encode(@NonNull ViewHierarchyEncoder encoder) {
+ encoder.beginObject(this);
+ encodeProperties(encoder);
+ encoder.endObject();
+ }
+
+ /** @hide */
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ encoder.addProperty("width", width);
+ encoder.addProperty("height", height);
+ }
+ }
+
+ /**
+ * Per-child layout information for layouts that support margins.
+ * See
+ * {@link android.R.styleable#ViewGroup_MarginLayout ViewGroup Margin Layout Attributes}
+ * for a list of all child view attributes that this class supports.
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_margin
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginHorizontal
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginVertical
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ */
+ public static class MarginLayoutParams extends ViewGroup.LayoutParams {
+ /**
+ * The left margin in pixels of the child. Margin values should be positive.
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int leftMargin;
+
+ /**
+ * The top margin in pixels of the child. Margin values should be positive.
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int topMargin;
+
+ /**
+ * The right margin in pixels of the child. Margin values should be positive.
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int rightMargin;
+
+ /**
+ * The bottom margin in pixels of the child. Margin values should be positive.
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int bottomMargin;
+
+ /**
+ * The start margin in pixels of the child. Margin values should be positive.
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ private int startMargin = DEFAULT_MARGIN_RELATIVE;
+
+ /**
+ * The end margin in pixels of the child. Margin values should be positive.
+ * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
+ * to this field.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ private int endMargin = DEFAULT_MARGIN_RELATIVE;
+
+ /**
+ * The default start and end margin.
+ * @hide
+ */
+ public static final int DEFAULT_MARGIN_RELATIVE = Integer.MIN_VALUE;
+
+ /**
+ * Bit 0: layout direction
+ * Bit 1: layout direction
+ * Bit 2: left margin undefined
+ * Bit 3: right margin undefined
+ * Bit 4: is RTL compatibility mode
+ * Bit 5: need resolution
+ *
+ * Bit 6 to 7 not used
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "layout", flagMapping = {
+ @ViewDebug.FlagToString(mask = LAYOUT_DIRECTION_MASK,
+ equals = LAYOUT_DIRECTION_MASK, name = "LAYOUT_DIRECTION"),
+ @ViewDebug.FlagToString(mask = LEFT_MARGIN_UNDEFINED_MASK,
+ equals = LEFT_MARGIN_UNDEFINED_MASK, name = "LEFT_MARGIN_UNDEFINED_MASK"),
+ @ViewDebug.FlagToString(mask = RIGHT_MARGIN_UNDEFINED_MASK,
+ equals = RIGHT_MARGIN_UNDEFINED_MASK, name = "RIGHT_MARGIN_UNDEFINED_MASK"),
+ @ViewDebug.FlagToString(mask = RTL_COMPATIBILITY_MODE_MASK,
+ equals = RTL_COMPATIBILITY_MODE_MASK, name = "RTL_COMPATIBILITY_MODE_MASK"),
+ @ViewDebug.FlagToString(mask = NEED_RESOLUTION_MASK,
+ equals = NEED_RESOLUTION_MASK, name = "NEED_RESOLUTION_MASK")
+ }, formatToHexString = true)
+ byte mMarginFlags;
+
+ private static final int LAYOUT_DIRECTION_MASK = 0x00000003;
+ private static final int LEFT_MARGIN_UNDEFINED_MASK = 0x00000004;
+ private static final int RIGHT_MARGIN_UNDEFINED_MASK = 0x00000008;
+ private static final int RTL_COMPATIBILITY_MODE_MASK = 0x00000010;
+ private static final int NEED_RESOLUTION_MASK = 0x00000020;
+
+ private static final int DEFAULT_MARGIN_RESOLVED = 0;
+ private static final int UNDEFINED_MARGIN = DEFAULT_MARGIN_RELATIVE;
+
+ /**
+ * Creates a new set of layout parameters. The values are extracted from
+ * the supplied attributes set and context.
+ *
+ * @param c the application environment
+ * @param attrs the set of attributes from which to extract the layout
+ * parameters' values
+ */
+ public MarginLayoutParams(Context c, AttributeSet attrs) {
+ super();
+
+ TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
+ setBaseAttributes(a,
+ R.styleable.ViewGroup_MarginLayout_layout_width,
+ R.styleable.ViewGroup_MarginLayout_layout_height);
+
+ int margin = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
+ if (margin >= 0) {
+ leftMargin = margin;
+ topMargin = margin;
+ rightMargin= margin;
+ bottomMargin = margin;
+ } else {
+ int horizontalMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
+ int verticalMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);
+
+ if (horizontalMargin >= 0) {
+ leftMargin = horizontalMargin;
+ rightMargin = horizontalMargin;
+ } else {
+ leftMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
+ UNDEFINED_MARGIN);
+ if (leftMargin == UNDEFINED_MARGIN) {
+ mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+ leftMargin = DEFAULT_MARGIN_RESOLVED;
+ }
+ rightMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginRight,
+ UNDEFINED_MARGIN);
+ if (rightMargin == UNDEFINED_MARGIN) {
+ mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+ rightMargin = DEFAULT_MARGIN_RESOLVED;
+ }
+ }
+
+ startMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginStart,
+ DEFAULT_MARGIN_RELATIVE);
+ endMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
+ DEFAULT_MARGIN_RELATIVE);
+
+ if (verticalMargin >= 0) {
+ topMargin = verticalMargin;
+ bottomMargin = verticalMargin;
+ } else {
+ topMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginTop,
+ DEFAULT_MARGIN_RESOLVED);
+ bottomMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
+ DEFAULT_MARGIN_RESOLVED);
+ }
+
+ if (isMarginRelative()) {
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ }
+ }
+
+ final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
+ final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
+ mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
+ }
+
+ // Layout direction is LTR by default
+ mMarginFlags |= LAYOUT_DIRECTION_LTR;
+
+ a.recycle();
+ }
+
+ public MarginLayoutParams(int width, int height) {
+ super(width, height);
+
+ mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+ mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ mMarginFlags &= ~RTL_COMPATIBILITY_MODE_MASK;
+ }
+
+ /**
+ * Copy constructor. Clones the width, height and margin values of the source.
+ *
+ * @param source The layout params to copy from.
+ */
+ public MarginLayoutParams(MarginLayoutParams source) {
+ this.width = source.width;
+ this.height = source.height;
+
+ this.leftMargin = source.leftMargin;
+ this.topMargin = source.topMargin;
+ this.rightMargin = source.rightMargin;
+ this.bottomMargin = source.bottomMargin;
+ this.startMargin = source.startMargin;
+ this.endMargin = source.endMargin;
+
+ this.mMarginFlags = source.mMarginFlags;
+ }
+
+ public MarginLayoutParams(LayoutParams source) {
+ super(source);
+
+ mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+ mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ mMarginFlags &= ~RTL_COMPATIBILITY_MODE_MASK;
+ }
+
+ /**
+ * @hide Used internally.
+ */
+ public final void copyMarginsFrom(MarginLayoutParams source) {
+ this.leftMargin = source.leftMargin;
+ this.topMargin = source.topMargin;
+ this.rightMargin = source.rightMargin;
+ this.bottomMargin = source.bottomMargin;
+ this.startMargin = source.startMargin;
+ this.endMargin = source.endMargin;
+
+ this.mMarginFlags = source.mMarginFlags;
+ }
+
+ /**
+ * Sets the margins, in pixels. A call to {@link android.view.View#requestLayout()} needs
+ * to be done so that the new margins are taken into account. Left and right margins may be
+ * overriden by {@link android.view.View#requestLayout()} depending on layout direction.
+ * Margin values should be positive.
+ *
+ * @param left the left margin size
+ * @param top the top margin size
+ * @param right the right margin size
+ * @param bottom the bottom margin size
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+ */
+ public void setMargins(int left, int top, int right, int bottom) {
+ leftMargin = left;
+ topMargin = top;
+ rightMargin = right;
+ bottomMargin = bottom;
+ mMarginFlags &= ~LEFT_MARGIN_UNDEFINED_MASK;
+ mMarginFlags &= ~RIGHT_MARGIN_UNDEFINED_MASK;
+ if (isMarginRelative()) {
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ } else {
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ }
+ }
+
+ /**
+ * Sets the relative margins, in pixels. A call to {@link android.view.View#requestLayout()}
+ * needs to be done so that the new relative margins are taken into account. Left and right
+ * margins may be overriden by {@link android.view.View#requestLayout()} depending on layout
+ * direction. Margin values should be positive.
+ *
+ * @param start the start margin size
+ * @param top the top margin size
+ * @param end the right margin size
+ * @param bottom the bottom margin size
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+ *
+ * @hide
+ */
+ public void setMarginsRelative(int start, int top, int end, int bottom) {
+ startMargin = start;
+ topMargin = top;
+ endMargin = end;
+ bottomMargin = bottom;
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ }
+
+ /**
+ * Sets the relative start margin. Margin values should be positive.
+ *
+ * @param start the start margin size
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ */
+ public void setMarginStart(int start) {
+ startMargin = start;
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ }
+
+ /**
+ * Returns the start margin in pixels.
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ *
+ * @return the start margin in pixels.
+ */
+ public int getMarginStart() {
+ if (startMargin != DEFAULT_MARGIN_RELATIVE) return startMargin;
+ if ((mMarginFlags & NEED_RESOLUTION_MASK) == NEED_RESOLUTION_MASK) {
+ doResolveMargins();
+ }
+ switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
+ case View.LAYOUT_DIRECTION_RTL:
+ return rightMargin;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ return leftMargin;
+ }
+ }
+
+ /**
+ * Sets the relative end margin. Margin values should be positive.
+ *
+ * @param end the end margin size
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ */
+ public void setMarginEnd(int end) {
+ endMargin = end;
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ }
+
+ /**
+ * Returns the end margin in pixels.
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ *
+ * @return the end margin in pixels.
+ */
+ public int getMarginEnd() {
+ if (endMargin != DEFAULT_MARGIN_RELATIVE) return endMargin;
+ if ((mMarginFlags & NEED_RESOLUTION_MASK) == NEED_RESOLUTION_MASK) {
+ doResolveMargins();
+ }
+ switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
+ case View.LAYOUT_DIRECTION_RTL:
+ return leftMargin;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ return rightMargin;
+ }
+ }
+
+ /**
+ * Check if margins are relative.
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ *
+ * @return true if either marginStart or marginEnd has been set.
+ */
+ public boolean isMarginRelative() {
+ return (startMargin != DEFAULT_MARGIN_RELATIVE || endMargin != DEFAULT_MARGIN_RELATIVE);
+ }
+
+ /**
+ * Set the layout direction
+ * @param layoutDirection the layout direction.
+ * Should be either {@link View#LAYOUT_DIRECTION_LTR}
+ * or {@link View#LAYOUT_DIRECTION_RTL}.
+ */
+ public void setLayoutDirection(int layoutDirection) {
+ if (layoutDirection != View.LAYOUT_DIRECTION_LTR &&
+ layoutDirection != View.LAYOUT_DIRECTION_RTL) return;
+ if (layoutDirection != (mMarginFlags & LAYOUT_DIRECTION_MASK)) {
+ mMarginFlags &= ~LAYOUT_DIRECTION_MASK;
+ mMarginFlags |= (layoutDirection & LAYOUT_DIRECTION_MASK);
+ if (isMarginRelative()) {
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ } else {
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ }
+ }
+ }
+
+ /**
+ * Retuns the layout direction. Can be either {@link View#LAYOUT_DIRECTION_LTR} or
+ * {@link View#LAYOUT_DIRECTION_RTL}.
+ *
+ * @return the layout direction.
+ */
+ public int getLayoutDirection() {
+ return (mMarginFlags & LAYOUT_DIRECTION_MASK);
+ }
+
+ /**
+ * This will be called by {@link android.view.View#requestLayout()}. Left and Right margins
+ * may be overridden depending on layout direction.
+ */
+ @Override
+ public void resolveLayoutDirection(int layoutDirection) {
+ setLayoutDirection(layoutDirection);
+
+ // No relative margin or pre JB-MR1 case or no need to resolve, just dont do anything
+ // Will use the left and right margins if no relative margin is defined.
+ if (!isMarginRelative() ||
+ (mMarginFlags & NEED_RESOLUTION_MASK) != NEED_RESOLUTION_MASK) return;
+
+ // Proceed with resolution
+ doResolveMargins();
+ }
+
+ private void doResolveMargins() {
+ if ((mMarginFlags & RTL_COMPATIBILITY_MODE_MASK) == RTL_COMPATIBILITY_MODE_MASK) {
+ // if left or right margins are not defined and if we have some start or end margin
+ // defined then use those start and end margins.
+ if ((mMarginFlags & LEFT_MARGIN_UNDEFINED_MASK) == LEFT_MARGIN_UNDEFINED_MASK
+ && startMargin > DEFAULT_MARGIN_RELATIVE) {
+ leftMargin = startMargin;
+ }
+ if ((mMarginFlags & RIGHT_MARGIN_UNDEFINED_MASK) == RIGHT_MARGIN_UNDEFINED_MASK
+ && endMargin > DEFAULT_MARGIN_RELATIVE) {
+ rightMargin = endMargin;
+ }
+ } else {
+ // We have some relative margins (either the start one or the end one or both). So use
+ // them and override what has been defined for left and right margins. If either start
+ // or end margin is not defined, just set it to default "0".
+ switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
+ case View.LAYOUT_DIRECTION_RTL:
+ leftMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
+ endMargin : DEFAULT_MARGIN_RESOLVED;
+ rightMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
+ startMargin : DEFAULT_MARGIN_RESOLVED;
+ break;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ leftMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
+ startMargin : DEFAULT_MARGIN_RESOLVED;
+ rightMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
+ endMargin : DEFAULT_MARGIN_RESOLVED;
+ break;
+ }
+ }
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isLayoutRtl() {
+ return ((mMarginFlags & LAYOUT_DIRECTION_MASK) == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ @Override
+ public void onDebugDraw(View view, Canvas canvas, Paint paint) {
+ Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE;
+
+ fillDifference(canvas,
+ view.getLeft() + oi.left,
+ view.getTop() + oi.top,
+ view.getRight() - oi.right,
+ view.getBottom() - oi.bottom,
+ leftMargin,
+ topMargin,
+ rightMargin,
+ bottomMargin,
+ paint);
+ }
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("leftMargin", leftMargin);
+ encoder.addProperty("topMargin", topMargin);
+ encoder.addProperty("rightMargin", rightMargin);
+ encoder.addProperty("bottomMargin", bottomMargin);
+ encoder.addProperty("startMargin", startMargin);
+ encoder.addProperty("endMargin", endMargin);
+ }
+ }
+
+ /* Describes a touched view and the ids of the pointers that it has captured.
+ *
+ * This code assumes that pointer ids are always in the range 0..31 such that
+ * it can use a bitfield to track which pointer ids are present.
+ * As it happens, the lower layers of the input dispatch pipeline also use the
+ * same trick so the assumption should be safe here...
+ */
+ private static final class TouchTarget {
+ private static final int MAX_RECYCLED = 32;
+ private static final Object sRecycleLock = new Object[0];
+ private static TouchTarget sRecycleBin;
+ private static int sRecycledCount;
+
+ public static final int ALL_POINTER_IDS = -1; // all ones
+
+ // The touched child view.
+ public View child;
+
+ // The combined bit mask of pointer ids for all pointers captured by the target.
+ public int pointerIdBits;
+
+ // The next target in the target list.
+ public TouchTarget next;
+
+ private TouchTarget() {
+ }
+
+ public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
+ if (child == null) {
+ throw new IllegalArgumentException("child must be non-null");
+ }
+
+ final TouchTarget target;
+ synchronized (sRecycleLock) {
+ if (sRecycleBin == null) {
+ target = new TouchTarget();
+ } else {
+ target = sRecycleBin;
+ sRecycleBin = target.next;
+ sRecycledCount--;
+ target.next = null;
+ }
+ }
+ target.child = child;
+ target.pointerIdBits = pointerIdBits;
+ return target;
+ }
+
+ public void recycle() {
+ if (child == null) {
+ throw new IllegalStateException("already recycled once");
+ }
+
+ synchronized (sRecycleLock) {
+ if (sRecycledCount < MAX_RECYCLED) {
+ next = sRecycleBin;
+ sRecycleBin = this;
+ sRecycledCount += 1;
+ } else {
+ next = null;
+ }
+ child = null;
+ }
+ }
+ }
+
+ /* Describes a hovered view. */
+ private static final class HoverTarget {
+ private static final int MAX_RECYCLED = 32;
+ private static final Object sRecycleLock = new Object[0];
+ private static HoverTarget sRecycleBin;
+ private static int sRecycledCount;
+
+ // The hovered child view.
+ public View child;
+
+ // The next target in the target list.
+ public HoverTarget next;
+
+ private HoverTarget() {
+ }
+
+ public static HoverTarget obtain(@NonNull View child) {
+ if (child == null) {
+ throw new IllegalArgumentException("child must be non-null");
+ }
+
+ final HoverTarget target;
+ synchronized (sRecycleLock) {
+ if (sRecycleBin == null) {
+ target = new HoverTarget();
+ } else {
+ target = sRecycleBin;
+ sRecycleBin = target.next;
+ sRecycledCount--;
+ target.next = null;
+ }
+ }
+ target.child = child;
+ return target;
+ }
+
+ public void recycle() {
+ if (child == null) {
+ throw new IllegalStateException("already recycled once");
+ }
+
+ synchronized (sRecycleLock) {
+ if (sRecycledCount < MAX_RECYCLED) {
+ next = sRecycleBin;
+ sRecycleBin = this;
+ sRecycledCount += 1;
+ } else {
+ next = null;
+ }
+ child = null;
+ }
+ }
+ }
+
+ /**
+ * Pooled class that to hold the children for autifill.
+ */
+ static class ChildListForAutoFill extends ArrayList<View> {
+ private static final int MAX_POOL_SIZE = 32;
+
+ private static final Pools.SimplePool<ChildListForAutoFill> sPool =
+ new Pools.SimplePool<>(MAX_POOL_SIZE);
+
+ public static ChildListForAutoFill obtain() {
+ ChildListForAutoFill list = sPool.acquire();
+ if (list == null) {
+ list = new ChildListForAutoFill();
+ }
+ return list;
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+ }
+
+ /**
+ * Pooled class that orderes the children of a ViewGroup from start
+ * to end based on how they are laid out and the layout direction.
+ */
+ static class ChildListForAccessibility {
+
+ private static final int MAX_POOL_SIZE = 32;
+
+ private static final SynchronizedPool<ChildListForAccessibility> sPool =
+ new SynchronizedPool<ChildListForAccessibility>(MAX_POOL_SIZE);
+
+ private final ArrayList<View> mChildren = new ArrayList<View>();
+
+ private final ArrayList<ViewLocationHolder> mHolders = new ArrayList<ViewLocationHolder>();
+
+ public static ChildListForAccessibility obtain(ViewGroup parent, boolean sort) {
+ ChildListForAccessibility list = sPool.acquire();
+ if (list == null) {
+ list = new ChildListForAccessibility();
+ }
+ list.init(parent, sort);
+ return list;
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ public int getChildCount() {
+ return mChildren.size();
+ }
+
+ public View getChildAt(int index) {
+ return mChildren.get(index);
+ }
+
+ private void init(ViewGroup parent, boolean sort) {
+ ArrayList<View> children = mChildren;
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+ children.add(child);
+ }
+ if (sort) {
+ ArrayList<ViewLocationHolder> holders = mHolders;
+ for (int i = 0; i < childCount; i++) {
+ View child = children.get(i);
+ ViewLocationHolder holder = ViewLocationHolder.obtain(parent, child);
+ holders.add(holder);
+ }
+ sort(holders);
+ for (int i = 0; i < childCount; i++) {
+ ViewLocationHolder holder = holders.get(i);
+ children.set(i, holder.mView);
+ holder.recycle();
+ }
+ holders.clear();
+ }
+ }
+
+ private void sort(ArrayList<ViewLocationHolder> holders) {
+ // This is gross but the least risky solution. The current comparison
+ // strategy breaks transitivity but produces very good results. Coming
+ // up with a new strategy requires time which we do not have, so ...
+ try {
+ ViewLocationHolder.setComparisonStrategy(
+ ViewLocationHolder.COMPARISON_STRATEGY_STRIPE);
+ Collections.sort(holders);
+ } catch (IllegalArgumentException iae) {
+ // Note that in practice this occurs extremely rarely in a couple
+ // of pathological cases.
+ ViewLocationHolder.setComparisonStrategy(
+ ViewLocationHolder.COMPARISON_STRATEGY_LOCATION);
+ Collections.sort(holders);
+ }
+ }
+
+ private void clear() {
+ mChildren.clear();
+ }
+ }
+
+ /**
+ * Pooled class that holds a View and its location with respect to
+ * a specified root. This enables sorting of views based on their
+ * coordinates without recomputing the position relative to the root
+ * on every comparison.
+ */
+ static class ViewLocationHolder implements Comparable<ViewLocationHolder> {
+
+ private static final int MAX_POOL_SIZE = 32;
+
+ private static final SynchronizedPool<ViewLocationHolder> sPool =
+ new SynchronizedPool<ViewLocationHolder>(MAX_POOL_SIZE);
+
+ public static final int COMPARISON_STRATEGY_STRIPE = 1;
+
+ public static final int COMPARISON_STRATEGY_LOCATION = 2;
+
+ private static int sComparisonStrategy = COMPARISON_STRATEGY_STRIPE;
+
+ private final Rect mLocation = new Rect();
+
+ public View mView;
+
+ private int mLayoutDirection;
+
+ public static ViewLocationHolder obtain(ViewGroup root, View view) {
+ ViewLocationHolder holder = sPool.acquire();
+ if (holder == null) {
+ holder = new ViewLocationHolder();
+ }
+ holder.init(root, view);
+ return holder;
+ }
+
+ public static void setComparisonStrategy(int strategy) {
+ sComparisonStrategy = strategy;
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ @Override
+ public int compareTo(ViewLocationHolder another) {
+ // This instance is greater than an invalid argument.
+ if (another == null) {
+ return 1;
+ }
+
+ if (sComparisonStrategy == COMPARISON_STRATEGY_STRIPE) {
+ // First is above second.
+ if (mLocation.bottom - another.mLocation.top <= 0) {
+ return -1;
+ }
+ // First is below second.
+ if (mLocation.top - another.mLocation.bottom >= 0) {
+ return 1;
+ }
+ }
+
+ // We are ordering left-to-right, top-to-bottom.
+ if (mLayoutDirection == LAYOUT_DIRECTION_LTR) {
+ final int leftDifference = mLocation.left - another.mLocation.left;
+ if (leftDifference != 0) {
+ return leftDifference;
+ }
+ } else { // RTL
+ final int rightDifference = mLocation.right - another.mLocation.right;
+ if (rightDifference != 0) {
+ return -rightDifference;
+ }
+ }
+ // We are ordering left-to-right, top-to-bottom.
+ final int topDifference = mLocation.top - another.mLocation.top;
+ if (topDifference != 0) {
+ return topDifference;
+ }
+ // Break tie by height.
+ final int heightDiference = mLocation.height() - another.mLocation.height();
+ if (heightDiference != 0) {
+ return -heightDiference;
+ }
+ // Break tie by width.
+ final int widthDiference = mLocation.width() - another.mLocation.width();
+ if (widthDiference != 0) {
+ return -widthDiference;
+ }
+ // Just break the tie somehow. The accessibliity ids are unique
+ // and stable, hence this is deterministic tie breaking.
+ return mView.getAccessibilityViewId() - another.mView.getAccessibilityViewId();
+ }
+
+ private void init(ViewGroup root, View view) {
+ Rect viewLocation = mLocation;
+ view.getDrawingRect(viewLocation);
+ root.offsetDescendantRectToMyCoords(view, viewLocation);
+ mView = view;
+ mLayoutDirection = root.getLayoutDirection();
+ }
+
+ private void clear() {
+ mView = null;
+ mLocation.set(0, 0, 0, 0);
+ }
+ }
+
+ private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+ if (sDebugLines== null) {
+ // TODO: This won't work with multiple UI threads in a single process
+ sDebugLines = new float[16];
+ }
+
+ sDebugLines[0] = x1;
+ sDebugLines[1] = y1;
+ sDebugLines[2] = x2;
+ sDebugLines[3] = y1;
+
+ sDebugLines[4] = x2;
+ sDebugLines[5] = y1;
+ sDebugLines[6] = x2;
+ sDebugLines[7] = y2;
+
+ sDebugLines[8] = x2;
+ sDebugLines[9] = y2;
+ sDebugLines[10] = x1;
+ sDebugLines[11] = y2;
+
+ sDebugLines[12] = x1;
+ sDebugLines[13] = y2;
+ sDebugLines[14] = x1;
+ sDebugLines[15] = y1;
+
+ canvas.drawLines(sDebugLines, paint);
+ }
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("focus:descendantFocusability", getDescendantFocusability());
+ encoder.addProperty("drawing:clipChildren", getClipChildren());
+ encoder.addProperty("drawing:clipToPadding", getClipToPadding());
+ encoder.addProperty("drawing:childrenDrawingOrderEnabled", isChildrenDrawingOrderEnabled());
+ encoder.addProperty("drawing:persistentDrawingCache", getPersistentDrawingCache());
+
+ int n = getChildCount();
+ encoder.addProperty("meta:__childCount__", (short)n);
+ for (int i = 0; i < n; i++) {
+ encoder.addPropertyKey("meta:__child__" + i);
+ getChildAt(i).encode(encoder);
+ }
+ }
+}
diff --git a/android/view/ViewGroupOverlay.java b/android/view/ViewGroupOverlay.java
new file mode 100644
index 00000000..c2807bab
--- /dev/null
+++ b/android/view/ViewGroupOverlay.java
@@ -0,0 +1,86 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A group overlay is an extra layer that sits on top of a ViewGroup
+ * (the "host view") which is drawn after all other content in that view
+ * (including the view group's children). Interaction with the overlay
+ * layer is done by adding and removing views and drawables.
+ *
+ * <p>ViewGroupOverlay is a subclass of {@link ViewOverlay}, adding the ability to
+ * manage views for overlays on ViewGroups, in addition to the drawable
+ * support in ViewOverlay.</p>
+ *
+ * @see ViewGroup#getOverlay()
+ */
+public class ViewGroupOverlay extends ViewOverlay {
+
+ ViewGroupOverlay(Context context, View hostView) {
+ super(context, hostView);
+ }
+
+ /**
+ * Adds a {@code View} to the overlay. The bounds of the added view should be
+ * relative to the host view. Any view added to the overlay should be
+ * removed when it is no longer needed or no longer visible.
+ *
+ * <p>Views in the overlay are visual-only; they do not receive input
+ * events and do not participate in focus traversal. Overlay views
+ * are intended to be transient, such as might be needed by a temporary
+ * animation effect.</p>
+ *
+ * <p>If the view has a parent, the view will be removed from that parent
+ * before being added to the overlay. Also, if that parent is attached
+ * in the current view hierarchy, the view will be repositioned
+ * such that it is in the same relative location inside the activity. For
+ * example, if the view's current parent lies 100 pixels to the right
+ * and 200 pixels down from the origin of the overlay's
+ * host view, then the view will be offset by (100, 200).</p>
+ *
+ * <p>{@code View}s added with this API will be drawn in the order they were
+ * added. Drawing of the overlay views will happen before drawing of any of the
+ * {@code Drawable}s added with {@link #add(Drawable)} API even if a call to
+ * this API happened after the call to {@link #add(Drawable)}.</p>
+ *
+ * <p>Passing <code>null</code> parameter will result in an
+ * {@link IllegalArgumentException} being thrown.</p>
+ *
+ * @param view The {@code View} to be added to the overlay. The added view will be
+ * drawn when the overlay is drawn.
+ * @see #remove(View)
+ * @see ViewOverlay#add(Drawable)
+ */
+ public void add(@NonNull View view) {
+ mOverlayViewGroup.add(view);
+ }
+
+ /**
+ * Removes the specified {@code View} from the overlay. Passing <code>null</code> parameter
+ * will result in an {@link IllegalArgumentException} being thrown.
+ *
+ * @param view The {@code View} to be removed from the overlay.
+ * @see #add(View)
+ * @see ViewOverlay#remove(Drawable)
+ */
+ public void remove(@NonNull View view) {
+ mOverlayViewGroup.remove(view);
+ }
+}
diff --git a/android/view/ViewGroup_Delegate.java b/android/view/ViewGroup_Delegate.java
new file mode 100644
index 00000000..4b760a7d
--- /dev/null
+++ b/android/view/ViewGroup_Delegate.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 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 com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Path_Delegate;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.view.animation.Transformation;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewGroup}
+ * <p/>
+ * Through the layoutlib_create tool, the original methods of ViewGroup have been replaced by calls
+ * to methods of the same name in this delegate class.
+ */
+public class ViewGroup_Delegate {
+
+ /**
+ * Overrides the original drawChild call in ViewGroup to draw the shadow.
+ */
+ @LayoutlibDelegate
+ /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
+ long drawingTime) {
+ if (child.getZ() > thisVG.getZ()) {
+ // The background's bounds are set lazily. Make sure they are set correctly so that
+ // the outline obtained is correct.
+ child.setBackgroundBounds();
+ ViewOutlineProvider outlineProvider = child.getOutlineProvider();
+ if (outlineProvider != null) {
+ Outline outline = child.mAttachInfo.mTmpOutline;
+ outlineProvider.getOutline(child, outline);
+ if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
+ int restoreTo = transformCanvas(thisVG, canvas, child);
+ drawShadow(thisVG, canvas, child, outline);
+ canvas.restoreToCount(restoreTo);
+ }
+ }
+ }
+ return thisVG.drawChild_Original(canvas, child, drawingTime);
+ }
+
+ private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
+ Outline outline) {
+ float elevation = getElevation(child, parent);
+ if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
+ RectShadowPainter.paintShadow(outline, elevation, canvas);
+ return;
+ }
+ BufferedImage shadow = null;
+ if (outline.mPath != null) {
+ shadow = getPathShadow(outline, canvas, elevation);
+ }
+ if (shadow == null) {
+ return;
+ }
+ Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
+ Density.getEnum(canvas.getDensity()));
+ Rect clipBounds = canvas.getClipBounds();
+ Rect newBounds = new Rect(clipBounds);
+ newBounds.inset((int)-elevation, (int)-elevation);
+ canvas.clipRect(newBounds, Op.REPLACE);
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ canvas.clipRect(clipBounds, Op.REPLACE);
+ }
+
+ private static float getElevation(View child, ViewGroup parent) {
+ return child.getZ() - parent.getZ();
+ }
+
+ private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
+ Rect clipBounds = canvas.getClipBounds();
+ if (clipBounds.isEmpty()) {
+ return null;
+ }
+ BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = image.createGraphics();
+ graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
+ graphics.dispose();
+ return ShadowPainter.createDropShadow(image, (int) elevation);
+ }
+
+ // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
+ // which were never taken. Ideally, we should hook up the shadow code in the same method so
+ // that we don't have to transform the canvas twice.
+ private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) {
+ final int restoreTo = canvas.save();
+ final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
+ int flags = thisVG.mGroupFlags;
+ Transformation transformToApply = null;
+ boolean concatMatrix = false;
+ if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+ final Transformation t = thisVG.getChildTransformation();
+ final boolean hasTransform = thisVG.getChildStaticTransformation(child, t);
+ if (hasTransform) {
+ final int transformType = t.getTransformationType();
+ transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
+ concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+ }
+ }
+ concatMatrix |= childHasIdentityMatrix;
+
+ child.computeScroll();
+ int sx = child.mScrollX;
+ int sy = child.mScrollY;
+
+ canvas.translate(child.mLeft - sx, child.mTop - sy);
+ float alpha = child.getAlpha() * child.getTransitionAlpha();
+
+ if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) {
+ if (transformToApply != null || !childHasIdentityMatrix) {
+ int transX = -sx;
+ int transY = -sy;
+
+ if (transformToApply != null) {
+ if (concatMatrix) {
+ // Undo the scroll translation, apply the transformation matrix,
+ // then redo the scroll translate to get the correct result.
+ canvas.translate(-transX, -transY);
+ canvas.concat(transformToApply.getMatrix());
+ canvas.translate(transX, transY);
+ }
+ if (!childHasIdentityMatrix) {
+ canvas.translate(-transX, -transY);
+ canvas.concat(child.getMatrix());
+ canvas.translate(transX, transY);
+ }
+ }
+
+ }
+ }
+ return restoreTo;
+ }
+}
diff --git a/android/view/ViewHierarchyEncoder.java b/android/view/ViewHierarchyEncoder.java
new file mode 100644
index 00000000..87702161
--- /dev/null
+++ b/android/view/ViewHierarchyEncoder.java
@@ -0,0 +1,201 @@
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
+ * view hierarchies (the view tree, along with the properties for each view) to a stream.
+ *
+ * It is typically used as follows:
+ * <pre>
+ * ViewHierarchyEncoder e = new ViewHierarchyEncoder();
+ *
+ * for (View view : views) {
+ * e.beginObject(view);
+ * e.addProperty("prop1", value);
+ * ...
+ * e.endObject();
+ * }
+ *
+ * // repeat above snippet for each view, finally end with:
+ * e.endStream();
+ * </pre>
+ *
+ * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
+ * corresponding to each view) with the property name as the key and the property value
+ * as the value.
+ *
+ * <p>Since the property names are practically the same across all views, rather than using
+ * the property name directly as the key, we use a short integer id corresponding to each
+ * property name as the key. A final map is added at the end which contains the mapping
+ * from the integer to its property name.
+ *
+ * <p>A value is encoded as a single byte type identifier followed by the encoding of the
+ * value. Only primitive types are supported as values, in addition to the Map type.
+ *
+ * @hide
+ */
+public class ViewHierarchyEncoder {
+ // Prefixes for simple primitives. These match the JNI definitions.
+ private static final byte SIG_BOOLEAN = 'Z';
+ private static final byte SIG_BYTE = 'B';
+ private static final byte SIG_SHORT = 'S';
+ private static final byte SIG_INT = 'I';
+ private static final byte SIG_LONG = 'J';
+ private static final byte SIG_FLOAT = 'F';
+ private static final byte SIG_DOUBLE = 'D';
+
+ // Prefixes for some commonly used objects
+ private static final byte SIG_STRING = 'R';
+
+ private static final byte SIG_MAP = 'M'; // a map with an short key
+ private static final short SIG_END_MAP = 0;
+
+ private final DataOutputStream mStream;
+
+ private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
+ private short mPropertyId = 1;
+ private Charset mCharset = Charset.forName("utf-8");
+
+ public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
+ mStream = new DataOutputStream(stream);
+ }
+
+ public void beginObject(@NonNull Object o) {
+ startPropertyMap();
+ addProperty("meta:__name__", o.getClass().getName());
+ addProperty("meta:__hash__", o.hashCode());
+ }
+
+ public void endObject() {
+ endPropertyMap();
+ }
+
+ public void endStream() {
+ // write out the string table
+ startPropertyMap();
+ addProperty("__name__", "propertyIndex");
+ for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
+ writeShort(entry.getValue());
+ writeString(entry.getKey());
+ }
+ endPropertyMap();
+ }
+
+ public void addProperty(@NonNull String name, boolean v) {
+ writeShort(createPropertyIndex(name));
+ writeBoolean(v);
+ }
+
+ public void addProperty(@NonNull String name, short s) {
+ writeShort(createPropertyIndex(name));
+ writeShort(s);
+ }
+
+ public void addProperty(@NonNull String name, int v) {
+ writeShort(createPropertyIndex(name));
+ writeInt(v);
+ }
+
+ public void addProperty(@NonNull String name, float v) {
+ writeShort(createPropertyIndex(name));
+ writeFloat(v);
+ }
+
+ public void addProperty(@NonNull String name, @Nullable String s) {
+ writeShort(createPropertyIndex(name));
+ writeString(s);
+ }
+
+ /**
+ * Writes the given name as the property name, and leaves it to the callee
+ * to fill in value for this property.
+ */
+ public void addPropertyKey(@NonNull String name) {
+ writeShort(createPropertyIndex(name));
+ }
+
+ private short createPropertyIndex(@NonNull String name) {
+ Short index = mPropertyNames.get(name);
+ if (index == null) {
+ index = mPropertyId++;
+ mPropertyNames.put(name, index);
+ }
+
+ return index;
+ }
+
+ private void startPropertyMap() {
+ try {
+ mStream.write(SIG_MAP);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void endPropertyMap() {
+ writeShort(SIG_END_MAP);
+ }
+
+ private void writeBoolean(boolean v) {
+ try {
+ mStream.write(SIG_BOOLEAN);
+ mStream.write(v ? 1 : 0);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeShort(short s) {
+ try {
+ mStream.write(SIG_SHORT);
+ mStream.writeShort(s);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeInt(int i) {
+ try {
+ mStream.write(SIG_INT);
+ mStream.writeInt(i);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeFloat(float v) {
+ try {
+ mStream.write(SIG_FLOAT);
+ mStream.writeFloat(v);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeString(@Nullable String s) {
+ if (s == null) {
+ s = "";
+ }
+
+ try {
+ mStream.write(SIG_STRING);
+ byte[] bytes = s.getBytes(mCharset);
+
+ short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
+ mStream.writeShort(len);
+
+ mStream.write(bytes, 0, len);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+}
diff --git a/android/view/ViewManager.java b/android/view/ViewManager.java
new file mode 100644
index 00000000..ab6856f8
--- /dev/null
+++ b/android/view/ViewManager.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006 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;
+
+/** Interface to let you add and remove child views to an Activity. To get an instance
+ * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
+ */
+public interface ViewManager
+{
+ /**
+ * Assign the passed LayoutParams to the passed View and add the view to the window.
+ * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
+ * errors, such as adding a second view to a window without removing the first view.
+ * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
+ * secondary {@link Display} and the specified display can't be found
+ * (see {@link android.app.Presentation}).
+ * @param view The view to be added to this window.
+ * @param params The LayoutParams to assign to view.
+ */
+ public void addView(View view, ViewGroup.LayoutParams params);
+ public void updateViewLayout(View view, ViewGroup.LayoutParams params);
+ public void removeView(View view);
+}
diff --git a/android/view/ViewOutlineProvider.java b/android/view/ViewOutlineProvider.java
new file mode 100644
index 00000000..a1a02f67
--- /dev/null
+++ b/android/view/ViewOutlineProvider.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 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.Outline;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Interface by which a View builds its {@link Outline}, used for shadow casting and clipping.
+ */
+public abstract class ViewOutlineProvider {
+ /**
+ * Default outline provider for Views, which queries the Outline from the View's background,
+ * or generates a 0 alpha, rectangular Outline the size of the View if a background
+ * isn't present.
+ *
+ * @see Drawable#getOutline(Outline)
+ */
+ public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ Drawable background = view.getBackground();
+ if (background != null) {
+ background.getOutline(outline);
+ } else {
+ outline.setRect(0, 0, view.getWidth(), view.getHeight());
+ outline.setAlpha(0.0f);
+ }
+ }
+ };
+
+ /**
+ * Maintains the outline of the View to match its rectangular bounds,
+ * at <code>1.0f</code> alpha.
+ *
+ * This can be used to enable Views that are opaque but lacking a background cast a shadow.
+ */
+ public static final ViewOutlineProvider BOUNDS = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRect(0, 0, view.getWidth(), view.getHeight());
+ }
+ };
+
+ /**
+ * Maintains the outline of the View to match its rectangular padded bounds,
+ * at <code>1.0f</code> alpha.
+ *
+ * This can be used to enable Views that are opaque but lacking a background cast a shadow.
+ */
+ public static final ViewOutlineProvider PADDED_BOUNDS = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRect(view.getPaddingLeft(),
+ view.getPaddingTop(),
+ view.getWidth() - view.getPaddingRight(),
+ view.getHeight() - view.getPaddingBottom());
+ }
+ };
+
+ /**
+ * Called to get the provider to populate the Outline.
+ *
+ * This method will be called by a View when its owned Drawables are invalidated, when the
+ * View's size changes, or if {@link View#invalidateOutline()} is called
+ * explicitly.
+ *
+ * The input outline is empty and has an alpha of <code>1.0f</code>.
+ *
+ * @param view The view building the outline.
+ * @param outline The empty outline to be populated.
+ */
+ public abstract void getOutline(View view, Outline outline);
+}
diff --git a/android/view/ViewOverlay.java b/android/view/ViewOverlay.java
new file mode 100644
index 00000000..21123c16
--- /dev/null
+++ b/android/view/ViewOverlay.java
@@ -0,0 +1,366 @@
+/*
+ * 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.view;
+
+import android.animation.LayoutTransition;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+
+/**
+ * An overlay is an extra layer that sits on top of a View (the "host view")
+ * which is drawn after all other content in that view (including children,
+ * if the view is a ViewGroup). Interaction with the overlay layer is done
+ * by adding and removing drawables.
+ *
+ * <p>An overlay requested from a ViewGroup is of type {@link ViewGroupOverlay},
+ * which also supports adding and removing views.</p>
+ *
+ * @see View#getOverlay() View.getOverlay()
+ * @see ViewGroup#getOverlay() ViewGroup.getOverlay()
+ * @see ViewGroupOverlay
+ */
+public class ViewOverlay {
+
+ /**
+ * The actual container for the drawables (and views, if it's a ViewGroupOverlay).
+ * All of the management and rendering details for the overlay are handled in
+ * OverlayViewGroup.
+ */
+ OverlayViewGroup mOverlayViewGroup;
+
+ ViewOverlay(Context context, View hostView) {
+ mOverlayViewGroup = new OverlayViewGroup(context, hostView);
+ }
+
+ /**
+ * Used internally by View and ViewGroup to handle drawing and invalidation
+ * of the overlay
+ * @return
+ */
+ ViewGroup getOverlayView() {
+ return mOverlayViewGroup;
+ }
+
+ /**
+ * Adds a {@link Drawable} to the overlay. The bounds of the drawable should be relative to
+ * the host view. Any drawable added to the overlay should be removed when it is no longer
+ * needed or no longer visible. Adding an already existing {@link Drawable}
+ * is a no-op. Passing <code>null</code> parameter will result in an
+ * {@link IllegalArgumentException} being thrown.
+ *
+ * @param drawable The {@link Drawable} to be added to the overlay. This drawable will be
+ * drawn when the view redraws its overlay. {@link Drawable}s will be drawn in the order that
+ * they were added.
+ * @see #remove(Drawable)
+ */
+ public void add(@NonNull Drawable drawable) {
+ mOverlayViewGroup.add(drawable);
+ }
+
+ /**
+ * Removes the specified {@link Drawable} from the overlay. Removing a {@link Drawable} that was
+ * not added with {@link #add(Drawable)} is a no-op. Passing <code>null</code> parameter will
+ * result in an {@link IllegalArgumentException} being thrown.
+ *
+ * @param drawable The {@link Drawable} to be removed from the overlay.
+ * @see #add(Drawable)
+ */
+ public void remove(@NonNull Drawable drawable) {
+ mOverlayViewGroup.remove(drawable);
+ }
+
+ /**
+ * Removes all content from the overlay.
+ */
+ public void clear() {
+ mOverlayViewGroup.clear();
+ }
+
+ boolean isEmpty() {
+ return mOverlayViewGroup.isEmpty();
+ }
+
+ /**
+ * OverlayViewGroup is a container that View and ViewGroup use to host
+ * drawables and views added to their overlays ({@link ViewOverlay} and
+ * {@link ViewGroupOverlay}, respectively). Drawables are added to the overlay
+ * via the add/remove methods in ViewOverlay, Views are added/removed via
+ * ViewGroupOverlay. These drawable and view objects are
+ * drawn whenever the view itself is drawn; first the view draws its own
+ * content (and children, if it is a ViewGroup), then it draws its overlay
+ * (if it has one).
+ *
+ * <p>Besides managing and drawing the list of drawables, this class serves
+ * two purposes:
+ * (1) it noops layout calls because children are absolutely positioned and
+ * (2) it forwards all invalidation calls to its host view. The invalidation
+ * redirect is necessary because the overlay is not a child of the host view
+ * and invalidation cannot therefore follow the normal path up through the
+ * parent hierarchy.</p>
+ *
+ * @see View#getOverlay()
+ * @see ViewGroup#getOverlay()
+ */
+ static class OverlayViewGroup extends ViewGroup {
+
+ /**
+ * The View for which this is an overlay. Invalidations of the overlay are redirected to
+ * this host view.
+ */
+ final View mHostView;
+
+ /**
+ * The set of drawables to draw when the overlay is rendered.
+ */
+ ArrayList<Drawable> mDrawables = null;
+
+ OverlayViewGroup(Context context, View hostView) {
+ super(context);
+ mHostView = hostView;
+ mAttachInfo = mHostView.mAttachInfo;
+
+ mRight = hostView.getWidth();
+ mBottom = hostView.getHeight();
+ // pass right+bottom directly to RenderNode, since not going through setters
+ mRenderNode.setLeftTopRightBottom(0, 0, mRight, mBottom);
+ }
+
+ public void add(@NonNull Drawable drawable) {
+ if (drawable == null) {
+ throw new IllegalArgumentException("drawable must be non-null");
+ }
+ if (mDrawables == null) {
+ mDrawables = new ArrayList<>();
+ }
+ if (!mDrawables.contains(drawable)) {
+ // Make each drawable unique in the overlay; can't add it more than once
+ mDrawables.add(drawable);
+ invalidate(drawable.getBounds());
+ drawable.setCallback(this);
+ }
+ }
+
+ public void remove(@NonNull Drawable drawable) {
+ if (drawable == null) {
+ throw new IllegalArgumentException("drawable must be non-null");
+ }
+ if (mDrawables != null) {
+ mDrawables.remove(drawable);
+ invalidate(drawable.getBounds());
+ drawable.setCallback(null);
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(@NonNull Drawable who) {
+ return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who));
+ }
+
+ public void add(@NonNull View child) {
+ if (child == null) {
+ throw new IllegalArgumentException("view must be non-null");
+ }
+
+ if (child.getParent() instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) child.getParent();
+ if (parent != mHostView && parent.getParent() != null &&
+ parent.mAttachInfo != null) {
+ // Moving to different container; figure out how to position child such that
+ // it is in the same location on the screen
+ int[] parentLocation = new int[2];
+ int[] hostViewLocation = new int[2];
+ parent.getLocationOnScreen(parentLocation);
+ mHostView.getLocationOnScreen(hostViewLocation);
+ child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]);
+ child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]);
+ }
+ parent.removeView(child);
+ if (parent.getLayoutTransition() != null) {
+ // LayoutTransition will cause the child to delay removal - cancel it
+ parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING);
+ }
+ // fail-safe if view is still attached for any reason
+ if (child.getParent() != null) {
+ child.mParent = null;
+ }
+ }
+ super.addView(child);
+ }
+
+ public void remove(@NonNull View view) {
+ if (view == null) {
+ throw new IllegalArgumentException("view must be non-null");
+ }
+
+ super.removeView(view);
+ }
+
+ public void clear() {
+ removeAllViews();
+ if (mDrawables != null) {
+ for (Drawable drawable : mDrawables) {
+ drawable.setCallback(null);
+ }
+ mDrawables.clear();
+ }
+ }
+
+ boolean isEmpty() {
+ if (getChildCount() == 0 &&
+ (mDrawables == null || mDrawables.size() == 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void invalidateDrawable(@NonNull Drawable drawable) {
+ invalidate(drawable.getBounds());
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ /*
+ * The OverlayViewGroup doesn't draw with a DisplayList, because
+ * draw(Canvas, View, long) is never called on it. This is fine, since it doesn't need
+ * RenderNode/DisplayList features, and can just draw into the owner's Canvas.
+ *
+ * This means that we need to insert reorder barriers manually though, so that children
+ * of the OverlayViewGroup can cast shadows and Z reorder with each other.
+ */
+ canvas.insertReorderBarrier();
+
+ super.dispatchDraw(canvas);
+
+ canvas.insertInorderBarrier();
+ final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size();
+ for (int i = 0; i < numDrawables; ++i) {
+ mDrawables.get(i).draw(canvas);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // Noop: children are positioned absolutely
+ }
+
+ /*
+ The following invalidation overrides exist for the purpose of redirecting invalidation to
+ the host view. The overlay is not parented to the host view (since a View cannot be a
+ parent), so the invalidation cannot proceed through the normal parent hierarchy.
+ There is a built-in assumption that the overlay exactly covers the host view, therefore
+ the invalidation rectangles received do not need to be adjusted when forwarded to
+ the host view.
+ */
+
+ @Override
+ public void invalidate(Rect dirty) {
+ super.invalidate(dirty);
+ if (mHostView != null) {
+ mHostView.invalidate(dirty);
+ }
+ }
+
+ @Override
+ public void invalidate(int l, int t, int r, int b) {
+ super.invalidate(l, t, r, b);
+ if (mHostView != null) {
+ mHostView.invalidate(l, t, r, b);
+ }
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ if (mHostView != null) {
+ mHostView.invalidate();
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void invalidate(boolean invalidateCache) {
+ super.invalidate(invalidateCache);
+ if (mHostView != null) {
+ mHostView.invalidate(invalidateCache);
+ }
+ }
+
+ @Override
+ void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
+ super.invalidateViewProperty(invalidateParent, forceRedraw);
+ if (mHostView != null) {
+ mHostView.invalidateViewProperty(invalidateParent, forceRedraw);
+ }
+ }
+
+ @Override
+ protected void invalidateParentCaches() {
+ super.invalidateParentCaches();
+ if (mHostView != null) {
+ mHostView.invalidateParentCaches();
+ }
+ }
+
+ @Override
+ protected void invalidateParentIfNeeded() {
+ super.invalidateParentIfNeeded();
+ if (mHostView != null) {
+ mHostView.invalidateParentIfNeeded();
+ }
+ }
+
+ @Override
+ public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+ if (mHostView != null) {
+ if (mHostView instanceof ViewGroup) {
+ // Propagate invalidate through the host...
+ ((ViewGroup) mHostView).onDescendantInvalidated(mHostView, target);
+
+ // ...and also this view, since it will hold the descendant, and must later
+ // propagate the calls to update display lists if dirty
+ super.onDescendantInvalidated(child, target);
+ } else {
+ // Can't use onDescendantInvalidated because host isn't a ViewGroup - fall back
+ // to invalidating.
+ invalidate();
+ }
+ }
+ }
+
+ @Override
+ public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
+ if (mHostView != null) {
+ dirty.offset(location[0], location[1]);
+ if (mHostView instanceof ViewGroup) {
+ location[0] = 0;
+ location[1] = 0;
+ super.invalidateChildInParent(location, dirty);
+ return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty);
+ } else {
+ invalidate(dirty);
+ }
+ }
+ return null;
+ }
+ }
+
+}
diff --git a/android/view/ViewParent.java b/android/view/ViewParent.java
new file mode 100644
index 00000000..572e69b1
--- /dev/null
+++ b/android/view/ViewParent.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2006 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.graphics.Rect;
+import android.os.Bundle;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * Defines the responsibilities for a class that will be a parent of a View.
+ * This is the API that a view sees when it wants to interact with its parent.
+ *
+ */
+public interface ViewParent {
+ /**
+ * Called when something has changed which has invalidated the layout of a
+ * child of this view parent. This will schedule a layout pass of the view
+ * tree.
+ */
+ public void requestLayout();
+
+ /**
+ * Indicates whether layout was requested on this view parent.
+ *
+ * @return true if layout was requested, false otherwise
+ */
+ public boolean isLayoutRequested();
+
+ /**
+ * Called when a child wants the view hierarchy to gather and report
+ * transparent regions to the window compositor. Views that "punch" holes in
+ * the view hierarchy, such as SurfaceView can use this API to improve
+ * performance of the system. When no such a view is present in the
+ * hierarchy, this optimization in unnecessary and might slightly reduce the
+ * view hierarchy performance.
+ *
+ * @param child the view requesting the transparent region computation
+ *
+ */
+ public void requestTransparentRegion(View child);
+
+
+ /**
+ * The target View has been invalidated, or has had a drawing property changed that
+ * requires the hierarchy to re-render.
+ *
+ * This method is called by the View hierarchy to signal ancestors that a View either needs to
+ * re-record its drawing commands, or drawing properties have changed. This is how Views
+ * schedule a drawing traversal.
+ *
+ * This signal is generally only dispatched for attached Views, since only they need to draw.
+ *
+ * @param child Direct child of this ViewParent containing target
+ * @param target The view that needs to redraw
+ */
+ default void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+ if (getParent() != null) {
+ // Note: should pass 'this' as default, but can't since we may not be a View
+ getParent().onDescendantInvalidated(child, target);
+ }
+ }
+
+ /**
+ * All or part of a child is dirty and needs to be redrawn.
+ *
+ * @param child The child which is dirty
+ * @param r The area within the child that is invalid
+ *
+ * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
+ */
+ @Deprecated
+ public void invalidateChild(View child, Rect r);
+
+ /**
+ * All or part of a child is dirty and needs to be redrawn.
+ *
+ * <p>The location array is an array of two int values which respectively
+ * define the left and the top position of the dirty child.</p>
+ *
+ * <p>This method must return the parent of this ViewParent if the specified
+ * rectangle must be invalidated in the parent. If the specified rectangle
+ * does not require invalidation in the parent or if the parent does not
+ * exist, this method must return null.</p>
+ *
+ * <p>When this method returns a non-null value, the location array must
+ * have been updated with the left and top coordinates of this ViewParent.</p>
+ *
+ * @param location An array of 2 ints containing the left and top
+ * coordinates of the child to invalidate
+ * @param r The area within the child that is invalid
+ *
+ * @return the parent of this ViewParent or null
+ *
+ * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
+ */
+ @Deprecated
+ public ViewParent invalidateChildInParent(int[] location, Rect r);
+
+ /**
+ * Returns the parent if it exists, or null.
+ *
+ * @return a ViewParent or null if this ViewParent does not have a parent
+ */
+ public ViewParent getParent();
+
+ /**
+ * Called when a child of this parent wants focus
+ *
+ * @param child The child of this ViewParent that wants focus. This view
+ * will contain the focused view. It is not necessarily the view that
+ * actually has focus.
+ * @param focused The view that is a descendant of child that actually has
+ * focus
+ */
+ public void requestChildFocus(View child, View focused);
+
+ /**
+ * Tell view hierarchy that the global view attributes need to be
+ * re-evaluated.
+ *
+ * @param child View whose attributes have changed.
+ */
+ public void recomputeViewAttributes(View child);
+
+ /**
+ * Called when a child of this parent is giving up focus
+ *
+ * @param child The view that is giving up focus
+ */
+ public void clearChildFocus(View child);
+
+ /**
+ * Compute the visible part of a rectangular region defined in terms of a child view's
+ * coordinates.
+ *
+ * <p>Returns the clipped visible part of the rectangle <code>r</code>, defined in the
+ * <code>child</code>'s local coordinate system. <code>r</code> is modified by this method to
+ * contain the result, expressed in the global (root) coordinate system.</p>
+ *
+ * <p>The resulting rectangle is always axis aligned. If a rotation is applied to a node in the
+ * View hierarchy, the result is the axis-aligned bounding box of the visible rectangle.</p>
+ *
+ * @param child A child View, whose rectangular visible region we want to compute
+ * @param r The input rectangle, defined in the child coordinate system. Will be overwritten to
+ * contain the resulting visible rectangle, expressed in global (root) coordinates
+ * @param offset The input coordinates of a point, defined in the child coordinate system.
+ * As with the <code>r</code> parameter, this will be overwritten to contain the global (root)
+ * coordinates of that point.
+ * A <code>null</code> value is valid (in case you are not interested in this result)
+ * @return true if the resulting rectangle is not empty, false otherwise
+ */
+ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset);
+
+ /**
+ * Find the nearest view in the specified direction that wants to take focus
+ *
+ * @param v The view that currently has focus
+ * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
+ */
+ public View focusSearch(View v, int direction);
+
+ /**
+ * Find the nearest keyboard navigation cluster in the specified direction.
+ * This does not actually give focus to that cluster.
+ *
+ * @param currentCluster The starting point of the search. Null means the current cluster is not
+ * found yet
+ * @param direction Direction to look
+ *
+ * @return The nearest keyboard navigation cluster in the specified direction, or null if none
+ * can be found
+ */
+ View keyboardNavigationClusterSearch(View currentCluster, int direction);
+
+ /**
+ * Change the z order of the child so it's on top of all other children.
+ * This ordering change may affect layout, if this container
+ * uses an order-dependent layout scheme (e.g., LinearLayout). Prior
+ * to {@link android.os.Build.VERSION_CODES#KITKAT} this
+ * method should be followed by calls to {@link #requestLayout()} and
+ * {@link View#invalidate()} on this parent to force the parent to redraw
+ * with the new child ordering.
+ *
+ * @param child The child to bring to the top of the z order
+ */
+ public void bringChildToFront(View child);
+
+ /**
+ * Tells the parent that a new focusable view has become available. This is
+ * to handle transitions from the case where there are no focusable views to
+ * the case where the first focusable view appears.
+ *
+ * @param v The view that has become newly focusable
+ */
+ public void focusableViewAvailable(View v);
+
+ /**
+ * Shows the context menu for the specified view or its ancestors.
+ * <p>
+ * In most cases, a subclass does not need to override this. However, if
+ * the subclass is added directly to the window manager (for example,
+ * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+ * then it should override this and show the context menu.
+ *
+ * @param originalView the source view where the context menu was first
+ * invoked
+ * @return {@code true} if the context menu was shown, {@code false}
+ * otherwise
+ * @see #showContextMenuForChild(View, float, float)
+ */
+ public boolean showContextMenuForChild(View originalView);
+
+ /**
+ * Shows the context menu for the specified view or its ancestors anchored
+ * to the specified view-relative coordinate.
+ * <p>
+ * In most cases, a subclass does not need to override this. However, if
+ * the subclass is added directly to the window manager (for example,
+ * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+ * then it should override this and show the context menu.
+ * <p>
+ * If a subclass overrides this method it should also override
+ * {@link #showContextMenuForChild(View)}.
+ *
+ * @param originalView the source view where the context menu was first
+ * invoked
+ * @param x the X coordinate in pixels relative to the original view to
+ * which the menu should be anchored, or {@link Float#NaN} to
+ * disable anchoring
+ * @param y the Y coordinate in pixels relative to the original view to
+ * which the menu should be anchored, or {@link Float#NaN} to
+ * disable anchoring
+ * @return {@code true} if the context menu was shown, {@code false}
+ * otherwise
+ */
+ boolean showContextMenuForChild(View originalView, float x, float y);
+
+ /**
+ * Have the parent populate the specified context menu if it has anything to
+ * add (and then recurse on its parent).
+ *
+ * @param menu The menu to populate
+ */
+ public void createContextMenu(ContextMenu menu);
+
+ /**
+ * Start an action mode for the specified view with the default type
+ * {@link ActionMode#TYPE_PRIMARY}.
+ *
+ * <p>In most cases, a subclass does not need to override this. However, if the
+ * subclass is added directly to the window manager (for example,
+ * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+ * then it should override this and start the action mode.</p>
+ *
+ * @param originalView The source view where the action mode was first invoked
+ * @param callback The callback that will handle lifecycle events for the action mode
+ * @return The new action mode if it was started, null otherwise
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+ */
+ public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
+
+ /**
+ * Start an action mode of a specific type for the specified view.
+ *
+ * <p>In most cases, a subclass does not need to override this. However, if the
+ * subclass is added directly to the window manager (for example,
+ * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+ * then it should override this and start the action mode.</p>
+ *
+ * @param originalView The source view where the action mode was first invoked
+ * @param callback The callback that will handle lifecycle events for the action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The new action mode if it was started, null otherwise
+ */
+ public ActionMode startActionModeForChild(
+ View originalView, ActionMode.Callback callback, int type);
+
+ /**
+ * This method is called on the parent when a child's drawable state
+ * has changed.
+ *
+ * @param child The child whose drawable state has changed.
+ */
+ public void childDrawableStateChanged(View child);
+
+ /**
+ * Called when a child does not want this parent and its ancestors to
+ * intercept touch events with
+ * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
+ *
+ * <p>This parent should pass this call onto its parents. This parent must obey
+ * this request for the duration of the touch (that is, only clear the flag
+ * after this parent has received an up or a cancel.</p>
+ *
+ * @param disallowIntercept True if the child does not want the parent to
+ * intercept touch events.
+ */
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
+
+ /**
+ * Called when a child of this group wants a particular rectangle to be
+ * positioned onto the screen. {@link ViewGroup}s overriding this can trust
+ * that:
+ * <ul>
+ * <li>child will be a direct child of this group</li>
+ * <li>rectangle will be in the child's content coordinates</li>
+ * </ul>
+ *
+ * <p>{@link ViewGroup}s overriding this should uphold the contract:</p>
+ * <ul>
+ * <li>nothing will change if the rectangle is already visible</li>
+ * <li>the view port will be scrolled only just enough to make the
+ * rectangle visible</li>
+ * <ul>
+ *
+ * @param child The direct child making the request.
+ * @param rectangle The rectangle in the child's coordinates the child
+ * wishes to be on the screen.
+ * @param immediate True to forbid animated or delayed scrolling,
+ * false otherwise
+ * @return Whether the group scrolled to handle the operation
+ */
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+ boolean immediate);
+
+ /**
+ * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
+ * The child has already populated a record for itself in the event and is delegating
+ * to its parent to send the event. The parent can optionally add a record for itself.
+ * <p>
+ * Note: An accessibility event is fired by an individual view which populates the
+ * event with a record for its state and requests from its parent to perform
+ * the sending. The parent can optionally add a record for itself before
+ * dispatching the request to its parent. A parent can also choose not to
+ * respect the request for sending the event. The accessibility event is sent
+ * by the topmost view in the view tree.</p>
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event was sent.
+ */
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event);
+
+ /**
+ * Called when a child view now has or no longer is tracking transient state.
+ *
+ * <p>"Transient state" is any state that a View might hold that is not expected to
+ * be reflected in the data model that the View currently presents. This state only
+ * affects the presentation to the user within the View itself, such as the current
+ * state of animations in progress or the state of a text selection operation.</p>
+ *
+ * <p>Transient state is useful for hinting to other components of the View system
+ * that a particular view is tracking something complex but encapsulated.
+ * A <code>ListView</code> for example may acknowledge that list item Views
+ * with transient state should be preserved within their position or stable item ID
+ * instead of treating that view as trivially replaceable by the backing adapter.
+ * This allows adapter implementations to be simpler instead of needing to track
+ * the state of item view animations in progress such that they could be restored
+ * in the event of an unexpected recycling and rebinding of attached item views.</p>
+ *
+ * <p>This method is called on a parent view when a child view or a view within
+ * its subtree begins or ends tracking of internal transient state.</p>
+ *
+ * @param child Child view whose state has changed
+ * @param hasTransientState true if this child has transient state
+ */
+ public void childHasTransientStateChanged(View child, boolean hasTransientState);
+
+ /**
+ * Ask that a new dispatch of {@link View#fitSystemWindows(Rect)
+ * View.fitSystemWindows(Rect)} be performed.
+ */
+ public void requestFitSystemWindows();
+
+ /**
+ * Gets the parent of a given View for accessibility. Since some Views are not
+ * exposed to the accessibility layer the parent for accessibility is not
+ * necessarily the direct parent of the View, rather it is a predecessor.
+ *
+ * @return The parent or <code>null</code> if no such is found.
+ */
+ public ViewParent getParentForAccessibility();
+
+ /**
+ * Notifies a view parent that the accessibility state of one of its
+ * descendants has changed and that the structure of the subtree is
+ * different.
+ * @param child The direct child whose subtree has changed.
+ * @param source The descendant view that changed. May not be {@code null}.
+ * @param changeType A bit mask of the types of changes that occurred. One
+ * or more of:
+ * <ul>
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
+ * </ul>
+ */
+ public void notifySubtreeAccessibilityStateChanged(
+ View child, @NonNull View source, int changeType);
+
+ /**
+ * Tells if this view parent can resolve the layout direction.
+ * See {@link View#setLayoutDirection(int)}
+ *
+ * @return True if this view parent can resolve the layout direction.
+ */
+ public boolean canResolveLayoutDirection();
+
+ /**
+ * Tells if this view parent layout direction is resolved.
+ * See {@link View#setLayoutDirection(int)}
+ *
+ * @return True if this view parent layout direction is resolved.
+ */
+ public boolean isLayoutDirectionResolved();
+
+ /**
+ * Return this view parent layout direction. See {@link View#getLayoutDirection()}
+ *
+ * @return {@link View#LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
+ * {@link View#LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+ */
+ public int getLayoutDirection();
+
+ /**
+ * Tells if this view parent can resolve the text direction.
+ * See {@link View#setTextDirection(int)}
+ *
+ * @return True if this view parent can resolve the text direction.
+ */
+ public boolean canResolveTextDirection();
+
+ /**
+ * Tells if this view parent text direction is resolved.
+ * See {@link View#setTextDirection(int)}
+ *
+ * @return True if this view parent text direction is resolved.
+ */
+ public boolean isTextDirectionResolved();
+
+ /**
+ * Return this view parent text direction. See {@link View#getTextDirection()}
+ *
+ * @return the resolved text direction. Returns one of:
+ *
+ * {@link View#TEXT_DIRECTION_FIRST_STRONG}
+ * {@link View#TEXT_DIRECTION_ANY_RTL},
+ * {@link View#TEXT_DIRECTION_LTR},
+ * {@link View#TEXT_DIRECTION_RTL},
+ * {@link View#TEXT_DIRECTION_LOCALE}
+ */
+ public int getTextDirection();
+
+ /**
+ * Tells if this view parent can resolve the text alignment.
+ * See {@link View#setTextAlignment(int)}
+ *
+ * @return True if this view parent can resolve the text alignment.
+ */
+ public boolean canResolveTextAlignment();
+
+ /**
+ * Tells if this view parent text alignment is resolved.
+ * See {@link View#setTextAlignment(int)}
+ *
+ * @return True if this view parent text alignment is resolved.
+ */
+ public boolean isTextAlignmentResolved();
+
+ /**
+ * Return this view parent text alignment. See {@link android.view.View#getTextAlignment()}
+ *
+ * @return the resolved text alignment. Returns one of:
+ *
+ * {@link View#TEXT_ALIGNMENT_GRAVITY},
+ * {@link View#TEXT_ALIGNMENT_CENTER},
+ * {@link View#TEXT_ALIGNMENT_TEXT_START},
+ * {@link View#TEXT_ALIGNMENT_TEXT_END},
+ * {@link View#TEXT_ALIGNMENT_VIEW_START},
+ * {@link View#TEXT_ALIGNMENT_VIEW_END}
+ */
+ public int getTextAlignment();
+
+ /**
+ * React to a descendant view initiating a nestable scroll operation, claiming the
+ * nested scroll operation if appropriate.
+ *
+ * <p>This method will be called in response to a descendant view invoking
+ * {@link View#startNestedScroll(int)}. Each parent up the view hierarchy will be
+ * given an opportunity to respond and claim the nested scrolling operation by returning
+ * <code>true</code>.</p>
+ *
+ * <p>This method may be overridden by ViewParent implementations to indicate when the view
+ * is willing to support a nested scrolling operation that is about to begin. If it returns
+ * true, this ViewParent will become the target view's nested scrolling parent for the duration
+ * of the scroll operation in progress. When the nested scroll is finished this ViewParent
+ * will receive a call to {@link #onStopNestedScroll(View)}.
+ * </p>
+ *
+ * @param child Direct child of this ViewParent containing target
+ * @param target View that initiated the nested scroll
+ * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+ * {@link View#SCROLL_AXIS_VERTICAL} or both
+ * @return true if this ViewParent accepts the nested scroll operation
+ */
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
+
+ /**
+ * React to the successful claiming of a nested scroll operation.
+ *
+ * <p>This method will be called after
+ * {@link #onStartNestedScroll(View, View, int) onStartNestedScroll} returns true. It offers
+ * an opportunity for the view and its superclasses to perform initial configuration
+ * for the nested scroll. Implementations of this method should always call their superclass's
+ * implementation of this method if one is present.</p>
+ *
+ * @param child Direct child of this ViewParent containing target
+ * @param target View that initiated the nested scroll
+ * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+ * {@link View#SCROLL_AXIS_VERTICAL} or both
+ * @see #onStartNestedScroll(View, View, int)
+ * @see #onStopNestedScroll(View)
+ */
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
+
+ /**
+ * React to a nested scroll operation ending.
+ *
+ * <p>Perform cleanup after a nested scrolling operation.
+ * This method will be called when a nested scroll stops, for example when a nested touch
+ * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event.
+ * Implementations of this method should always call their superclass's implementation of this
+ * method if one is present.</p>
+ *
+ * @param target View that initiated the nested scroll
+ */
+ public void onStopNestedScroll(View target);
+
+ /**
+ * React to a nested scroll in progress.
+ *
+ * <p>This method will be called when the ViewParent's current nested scrolling child view
+ * dispatches a nested scroll event. To receive calls to this method the ViewParent must have
+ * previously returned <code>true</code> for a call to
+ * {@link #onStartNestedScroll(View, View, int)}.</p>
+ *
+ * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the
+ * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll
+ * position of multiple child elements, for example. The unconsumed portion may be used to
+ * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling
+ * a list within a vertical drawer where the drawer begins dragging once the edge of inner
+ * scrolling content is reached.</p>
+ *
+ * @param target The descendent view controlling the nested scroll
+ * @param dxConsumed Horizontal scroll distance in pixels already consumed by target
+ * @param dyConsumed Vertical scroll distance in pixels already consumed by target
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target
+ * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target
+ */
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed);
+
+ /**
+ * React to a nested scroll in progress before the target view consumes a portion of the scroll.
+ *
+ * <p>When working with nested scrolling often the parent view may want an opportunity
+ * to consume the scroll before the nested scrolling child does. An example of this is a
+ * drawer that contains a scrollable list. The user will want to be able to scroll the list
+ * fully into view before the list itself begins scrolling.</p>
+ *
+ * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes
+ * {@link View#dispatchNestedPreScroll(int, int, int[], int[])}. The implementation should
+ * report how any pixels of the scroll reported by dx, dy were consumed in the
+ * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy.
+ * This parameter will never be null. Initial values for consumed[0] and consumed[1]
+ * will always be 0.</p>
+ *
+ * @param target View that initiated the nested scroll
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
+ */
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
+
+ /**
+ * Request a fling from a nested scroll.
+ *
+ * <p>This method signifies that a nested scrolling child has detected suitable conditions
+ * for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+ *
+ * @param target View that initiated the nested scroll
+ * @param velocityX Horizontal velocity in pixels per second
+ * @param velocityY Vertical velocity in pixels per second
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if this parent consumed or otherwise reacted to the fling
+ */
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
+
+ /**
+ * React to a nested fling before the target view consumes it.
+ *
+ * <p>This method siginfies that a nested scrolling child has detected a fling with the given
+ * velocity along each axis. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling parent is consuming motion as part of a
+ * {@link #onNestedPreScroll(View, int, int, int[]) pre-scroll}, it may be appropriate for
+ * it to also consume the pre-fling to complete that same motion. By returning
+ * <code>true</code> from this method, the parent indicates that the child should not
+ * fling its own internal content as well.</p>
+ *
+ * @param target View that initiated the nested scroll
+ * @param velocityX Horizontal velocity in pixels per second
+ * @param velocityY Vertical velocity in pixels per second
+ * @return true if this parent consumed the fling ahead of the target view
+ */
+ public boolean onNestedPreFling(View target, float velocityX, float velocityY);
+
+ /**
+ * React to an accessibility action delegated by a target descendant view before the target
+ * processes it.
+ *
+ * <p>This method may be called by a target descendant view if the target wishes to give
+ * a view in its parent chain a chance to react to the event before normal processing occurs.
+ * Most commonly this will be a scroll event such as
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}.
+ * A ViewParent that supports acting as a nested scrolling parent should override this
+ * method and act accordingly to implement scrolling via accesibility systems.</p>
+ *
+ * @param target The target view dispatching this action
+ * @param action Action being performed; see
+ * {@link android.view.accessibility.AccessibilityNodeInfo}
+ * @param arguments Optional action arguments
+ * @return true if the action was consumed by this ViewParent
+ */
+ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
+}
diff --git a/android/view/ViewPerfTest.java b/android/view/ViewPerfTest.java
new file mode 100644
index 00000000..990be24b
--- /dev/null
+++ b/android/view/ViewPerfTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 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.content.Context;
+import android.content.res.Resources;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.widget.FrameLayout;
+
+import com.android.perftests.core.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class ViewPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testSimpleViewInflate() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ FrameLayout root = new FrameLayout(context);
+ while (state.keepRunning()) {
+ inflater.inflate(R.layout.test_simple_view, root, false);
+ }
+ }
+
+ @Test
+ public void testTwelveKeyInflate() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ FrameLayout root = new FrameLayout(context);
+ while (state.keepRunning()) {
+ inflater.inflate(R.layout.twelve_key_entry, root, false);
+ }
+ }
+}
diff --git a/android/view/ViewPropertyAnimator.java b/android/view/ViewPropertyAnimator.java
new file mode 100644
index 00000000..6c84b63b
--- /dev/null
+++ b/android/view/ViewPropertyAnimator.java
@@ -0,0 +1,1196 @@
+/*
+ * Copyright (C) 2011 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.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * This class enables automatic and optimized animation of select properties on View objects.
+ * If only one or two properties on a View object are being animated, then using an
+ * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator
+ * are well equipped to do the right thing to set the property and invalidate the view
+ * appropriately. But if several properties are animated simultaneously, or if you just want a
+ * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be
+ * more well-suited to the task.
+ *
+ * <p>This class may provide better performance for several simultaneous animations, because
+ * it will optimize invalidate calls to take place only once for several properties instead of each
+ * animated property independently causing its own invalidation. Also, the syntax of using this
+ * class could be easier to use because the caller need only tell the View object which
+ * property to animate, and the value to animate either to or by, and this class handles the
+ * details of configuring the underlying Animator class and starting it.</p>
+ *
+ * <p>This class is not constructed by the caller, but rather by the View whose properties
+ * it will animate. Calls to {@link android.view.View#animate()} will return a reference
+ * to the appropriate ViewPropertyAnimator object for that View.</p>
+ *
+ */
+public class ViewPropertyAnimator {
+
+ /**
+ * The View whose properties are being animated by this class. This is set at
+ * construction time.
+ */
+ final View mView;
+
+ /**
+ * The duration of the underlying Animator object. By default, we don't set the duration
+ * on the Animator and just use its default duration. If the duration is ever set on this
+ * Animator, then we use the duration that it was set to.
+ */
+ private long mDuration;
+
+ /**
+ * A flag indicating whether the duration has been set on this object. If not, we don't set
+ * the duration on the underlying Animator, but instead just use its default duration.
+ */
+ private boolean mDurationSet = false;
+
+ /**
+ * The startDelay of the underlying Animator object. By default, we don't set the startDelay
+ * on the Animator and just use its default startDelay. If the startDelay is ever set on this
+ * Animator, then we use the startDelay that it was set to.
+ */
+ private long mStartDelay = 0;
+
+ /**
+ * A flag indicating whether the startDelay has been set on this object. If not, we don't set
+ * the startDelay on the underlying Animator, but instead just use its default startDelay.
+ */
+ private boolean mStartDelaySet = false;
+
+ /**
+ * The interpolator of the underlying Animator object. By default, we don't set the interpolator
+ * on the Animator and just use its default interpolator. If the interpolator is ever set on
+ * this Animator, then we use the interpolator that it was set to.
+ */
+ private TimeInterpolator mInterpolator;
+
+ /**
+ * A flag indicating whether the interpolator has been set on this object. If not, we don't set
+ * the interpolator on the underlying Animator, but instead just use its default interpolator.
+ */
+ private boolean mInterpolatorSet = false;
+
+ /**
+ * Listener for the lifecycle events of the underlying ValueAnimator object.
+ */
+ private Animator.AnimatorListener mListener = null;
+
+ /**
+ * Listener for the update events of the underlying ValueAnimator object.
+ */
+ private ValueAnimator.AnimatorUpdateListener mUpdateListener = null;
+
+ /**
+ * A lazily-created ValueAnimator used in order to get some default animator properties
+ * (duration, start delay, interpolator, etc.).
+ */
+ private ValueAnimator mTempValueAnimator;
+
+ /**
+ * A RenderThread-driven backend that may intercept startAnimation
+ */
+ private ViewPropertyAnimatorRT mRTBackend;
+
+ /**
+ * This listener is the mechanism by which the underlying Animator causes changes to the
+ * properties currently being animated, as well as the cleanup after an animation is
+ * complete.
+ */
+ private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener();
+
+ /**
+ * This list holds the properties that have been asked to animate. We allow the caller to
+ * request several animations prior to actually starting the underlying animator. This
+ * enables us to run one single animator to handle several properties in parallel. Each
+ * property is tossed onto the pending list until the animation actually starts (which is
+ * done by posting it onto mView), at which time the pending list is cleared and the properties
+ * on that list are added to the list of properties associated with that animator.
+ */
+ ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>();
+ private Runnable mPendingSetupAction;
+ private Runnable mPendingCleanupAction;
+ private Runnable mPendingOnStartAction;
+ private Runnable mPendingOnEndAction;
+
+ /**
+ * Constants used to associate a property being requested and the mechanism used to set
+ * the property (this class calls directly into View to set the properties in question).
+ */
+ static final int NONE = 0x0000;
+ static final int TRANSLATION_X = 0x0001;
+ static final int TRANSLATION_Y = 0x0002;
+ static final int TRANSLATION_Z = 0x0004;
+ static final int SCALE_X = 0x0008;
+ static final int SCALE_Y = 0x0010;
+ static final int ROTATION = 0x0020;
+ static final int ROTATION_X = 0x0040;
+ static final int ROTATION_Y = 0x0080;
+ static final int X = 0x0100;
+ static final int Y = 0x0200;
+ static final int Z = 0x0400;
+ static final int ALPHA = 0x0800;
+
+ private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
+ SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z;
+
+ /**
+ * The mechanism by which the user can request several properties that are then animated
+ * together works by posting this Runnable to start the underlying Animator. Every time
+ * a property animation is requested, we cancel any previous postings of the Runnable
+ * and re-post it. This means that we will only ever run the Runnable (and thus start the
+ * underlying animator) after the caller is done setting the properties that should be
+ * animated together.
+ */
+ private Runnable mAnimationStarter = new Runnable() {
+ @Override
+ public void run() {
+ startAnimation();
+ }
+ };
+
+ /**
+ * This class holds information about the overall animation being run on the set of
+ * properties. The mask describes which properties are being animated and the
+ * values holder is the list of all property/value objects.
+ */
+ private static class PropertyBundle {
+ int mPropertyMask;
+ ArrayList<NameValuesHolder> mNameValuesHolder;
+
+ PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) {
+ mPropertyMask = propertyMask;
+ mNameValuesHolder = nameValuesHolder;
+ }
+
+ /**
+ * Removes the given property from being animated as a part of this
+ * PropertyBundle. If the property was a part of this bundle, it returns
+ * true to indicate that it was, in fact, canceled. This is an indication
+ * to the caller that a cancellation actually occurred.
+ *
+ * @param propertyConstant The property whose cancellation is requested.
+ * @return true if the given property is a part of this bundle and if it
+ * has therefore been canceled.
+ */
+ boolean cancel(int propertyConstant) {
+ if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) {
+ int count = mNameValuesHolder.size();
+ for (int i = 0; i < count; ++i) {
+ NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i);
+ if (nameValuesHolder.mNameConstant == propertyConstant) {
+ mNameValuesHolder.remove(i);
+ mPropertyMask &= ~propertyConstant;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * This list tracks the list of properties being animated by any particular animator.
+ * In most situations, there would only ever be one animator running at a time. But it is
+ * possible to request some properties to animate together, then while those properties
+ * are animating, to request some other properties to animate together. The way that
+ * works is by having this map associate the group of properties being animated with the
+ * animator handling the animation. On every update event for an Animator, we ask the
+ * map for the associated properties and set them accordingly.
+ */
+ private HashMap<Animator, PropertyBundle> mAnimatorMap =
+ new HashMap<Animator, PropertyBundle>();
+ private HashMap<Animator, Runnable> mAnimatorSetupMap;
+ private HashMap<Animator, Runnable> mAnimatorCleanupMap;
+ private HashMap<Animator, Runnable> mAnimatorOnStartMap;
+ private HashMap<Animator, Runnable> mAnimatorOnEndMap;
+
+ /**
+ * This is the information we need to set each property during the animation.
+ * mNameConstant is used to set the appropriate field in View, and the from/delta
+ * values are used to calculate the animated value for a given animation fraction
+ * during the animation.
+ */
+ static class NameValuesHolder {
+ int mNameConstant;
+ float mFromValue;
+ float mDeltaValue;
+ NameValuesHolder(int nameConstant, float fromValue, float deltaValue) {
+ mNameConstant = nameConstant;
+ mFromValue = fromValue;
+ mDeltaValue = deltaValue;
+ }
+ }
+
+ /**
+ * Constructor, called by View. This is private by design, as the user should only
+ * get a ViewPropertyAnimator by calling View.animate().
+ *
+ * @param view The View associated with this ViewPropertyAnimator
+ */
+ ViewPropertyAnimator(View view) {
+ mView = view;
+ view.ensureTransformationInfo();
+ }
+
+ /**
+ * Sets the duration for the underlying animator that animates the requested properties.
+ * By default, the animator uses the default value for ValueAnimator. Calling this method
+ * will cause the declared value to be used instead.
+ * @param duration The length of ensuing property animations, in milliseconds. The value
+ * cannot be negative.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator setDuration(long duration) {
+ if (duration < 0) {
+ throw new IllegalArgumentException("Animators cannot have negative duration: " +
+ duration);
+ }
+ mDurationSet = true;
+ mDuration = duration;
+ return this;
+ }
+
+ /**
+ * Returns the current duration of property animations. If the duration was set on this
+ * object, that value is returned. Otherwise, the default value of the underlying Animator
+ * is returned.
+ *
+ * @see #setDuration(long)
+ * @return The duration of animations, in milliseconds.
+ */
+ public long getDuration() {
+ if (mDurationSet) {
+ return mDuration;
+ } else {
+ // Just return the default from ValueAnimator, since that's what we'd get if
+ // the value has not been set otherwise
+ if (mTempValueAnimator == null) {
+ mTempValueAnimator = new ValueAnimator();
+ }
+ return mTempValueAnimator.getDuration();
+ }
+ }
+
+ /**
+ * Returns the current startDelay of property animations. If the startDelay was set on this
+ * object, that value is returned. Otherwise, the default value of the underlying Animator
+ * is returned.
+ *
+ * @see #setStartDelay(long)
+ * @return The startDelay of animations, in milliseconds.
+ */
+ public long getStartDelay() {
+ if (mStartDelaySet) {
+ return mStartDelay;
+ } else {
+ // Just return the default from ValueAnimator (0), since that's what we'd get if
+ // the value has not been set otherwise
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the startDelay for the underlying animator that animates the requested properties.
+ * By default, the animator uses the default value for ValueAnimator. Calling this method
+ * will cause the declared value to be used instead.
+ * @param startDelay The delay of ensuing property animations, in milliseconds. The value
+ * cannot be negative.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator setStartDelay(long startDelay) {
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("Animators cannot have negative start " +
+ "delay: " + startDelay);
+ }
+ mStartDelaySet = true;
+ mStartDelay = startDelay;
+ return this;
+ }
+
+ /**
+ * Sets the interpolator for the underlying animator that animates the requested properties.
+ * By default, the animator uses the default interpolator for ValueAnimator. Calling this method
+ * will cause the declared object to be used instead.
+ *
+ * @param interpolator The TimeInterpolator to be used for ensuing property animations. A value
+ * of <code>null</code> will result in linear interpolation.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) {
+ mInterpolatorSet = true;
+ mInterpolator = interpolator;
+ return this;
+ }
+
+ /**
+ * Returns the timing interpolator that this animation uses.
+ *
+ * @return The timing interpolator for this animation.
+ */
+ public TimeInterpolator getInterpolator() {
+ if (mInterpolatorSet) {
+ return mInterpolator;
+ } else {
+ // Just return the default from ValueAnimator, since that's what we'd get if
+ // the value has not been set otherwise
+ if (mTempValueAnimator == null) {
+ mTempValueAnimator = new ValueAnimator();
+ }
+ return mTempValueAnimator.getInterpolator();
+ }
+ }
+
+ /**
+ * Sets a listener for events in the underlying Animators that run the property
+ * animations.
+ *
+ * @see Animator.AnimatorListener
+ *
+ * @param listener The listener to be called with AnimatorListener events. A value of
+ * <code>null</code> removes any existing listener.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
+ mListener = listener;
+ return this;
+ }
+
+ Animator.AnimatorListener getListener() {
+ return mListener;
+ }
+
+ /**
+ * Sets a listener for update events in the underlying ValueAnimator that runs
+ * the property animations. Note that the underlying animator is animating between
+ * 0 and 1 (these values are then turned into the actual property values internally
+ * by ViewPropertyAnimator). So the animator cannot give information on the current
+ * values of the properties being animated by this ViewPropertyAnimator, although
+ * the view object itself can be queried to get the current values.
+ *
+ * @see android.animation.ValueAnimator.AnimatorUpdateListener
+ *
+ * @param listener The listener to be called with update events. A value of
+ * <code>null</code> removes any existing listener.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator setUpdateListener(ValueAnimator.AnimatorUpdateListener listener) {
+ mUpdateListener = listener;
+ return this;
+ }
+
+ ValueAnimator.AnimatorUpdateListener getUpdateListener() {
+ return mUpdateListener;
+ }
+
+ /**
+ * Starts the currently pending property animations immediately. Calling <code>start()</code>
+ * is optional because all animations start automatically at the next opportunity. However,
+ * if the animations are needed to start immediately and synchronously (not at the time when
+ * the next event is processed by the hierarchy, which is when the animations would begin
+ * otherwise), then this method can be used.
+ */
+ public void start() {
+ mView.removeCallbacks(mAnimationStarter);
+ startAnimation();
+ }
+
+ /**
+ * Cancels all property animations that are currently running or pending.
+ */
+ public void cancel() {
+ if (mAnimatorMap.size() > 0) {
+ HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
+ (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
+ Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
+ for (Animator runningAnim : animatorSet) {
+ runningAnim.cancel();
+ }
+ }
+ mPendingAnimations.clear();
+ mPendingSetupAction = null;
+ mPendingCleanupAction = null;
+ mPendingOnStartAction = null;
+ mPendingOnEndAction = null;
+ mView.removeCallbacks(mAnimationStarter);
+ if (mRTBackend != null) {
+ mRTBackend.cancelAll();
+ }
+ }
+
+ /**
+ * This method will cause the View's <code>x</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator x(float value) {
+ animateProperty(X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>x</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator xBy(float value) {
+ animatePropertyBy(X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>y</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator y(float value) {
+ animateProperty(Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>y</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator yBy(float value) {
+ animatePropertyBy(Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>z</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator z(float value) {
+ animateProperty(Z, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>z</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator zBy(float value) {
+ animatePropertyBy(Z, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>rotation</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setRotation(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator rotation(float value) {
+ animateProperty(ROTATION, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>rotation</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setRotation(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator rotationBy(float value) {
+ animatePropertyBy(ROTATION, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>rotationX</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setRotationX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator rotationX(float value) {
+ animateProperty(ROTATION_X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>rotationX</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setRotationX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator rotationXBy(float value) {
+ animatePropertyBy(ROTATION_X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>rotationY</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setRotationY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator rotationY(float value) {
+ animateProperty(ROTATION_Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>rotationY</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setRotationY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator rotationYBy(float value) {
+ animatePropertyBy(ROTATION_Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>translationX</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setTranslationX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationX(float value) {
+ animateProperty(TRANSLATION_X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>translationX</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setTranslationX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationXBy(float value) {
+ animatePropertyBy(TRANSLATION_X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>translationY</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setTranslationY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationY(float value) {
+ animateProperty(TRANSLATION_Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>translationY</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setTranslationY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationYBy(float value) {
+ animatePropertyBy(TRANSLATION_Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>translationZ</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setTranslationZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationZ(float value) {
+ animateProperty(TRANSLATION_Z, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>translationZ</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setTranslationZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationZBy(float value) {
+ animatePropertyBy(TRANSLATION_Z, value);
+ return this;
+ }
+ /**
+ * This method will cause the View's <code>scaleX</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setScaleX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator scaleX(float value) {
+ animateProperty(SCALE_X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>scaleX</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setScaleX(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator scaleXBy(float value) {
+ animatePropertyBy(SCALE_X, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>scaleY</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setScaleY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator scaleY(float value) {
+ animateProperty(SCALE_Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>scaleY</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setScaleY(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator scaleYBy(float value) {
+ animatePropertyBy(SCALE_Y, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>alpha</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setAlpha(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator alpha(float value) {
+ animateProperty(ALPHA, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>alpha</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setAlpha(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator alphaBy(float value) {
+ animatePropertyBy(ALPHA, value);
+ return this;
+ }
+
+ /**
+ * The View associated with this ViewPropertyAnimator will have its
+ * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to
+ * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation.
+ * As stated in the documentation for {@link View#LAYER_TYPE_HARDWARE},
+ * the actual type of layer used internally depends on the runtime situation of the
+ * view. If the activity and this view are hardware-accelerated, then the layer will be
+ * accelerated as well. If the activity or the view is not accelerated, then the layer will
+ * effectively be the same as {@link View#LAYER_TYPE_SOFTWARE}.
+ *
+ * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the
+ * layer type of the View will be restored when the animation ends to what it was when this
+ * method was called, and this setting on ViewPropertyAnimator is only valid for the next
+ * animation. Note that calling this method and then independently setting the layer type of
+ * the View (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will
+ * result in some inconsistency, including having the layer type restored to its pre-withLayer()
+ * value when the animation ends.</p>
+ *
+ * @see View#setLayerType(int, android.graphics.Paint)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator withLayer() {
+ mPendingSetupAction= new Runnable() {
+ @Override
+ public void run() {
+ mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ if (mView.isAttachedToWindow()) {
+ mView.buildLayer();
+ }
+ }
+ };
+ final int currentLayerType = mView.getLayerType();
+ mPendingCleanupAction = new Runnable() {
+ @Override
+ public void run() {
+ mView.setLayerType(currentLayerType, null);
+ }
+ };
+ if (mAnimatorSetupMap == null) {
+ mAnimatorSetupMap = new HashMap<Animator, Runnable>();
+ }
+ if (mAnimatorCleanupMap == null) {
+ mAnimatorCleanupMap = new HashMap<Animator, Runnable>();
+ }
+
+ return this;
+ }
+
+ /**
+ * Specifies an action to take place when the next animation runs. If there is a
+ * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
+ * action will run after that startDelay expires, when the actual animation begins.
+ * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
+ * choreographing ViewPropertyAnimator animations with other animations or actions
+ * in the application.
+ *
+ * @param runnable The action to run when the next animation starts.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator withStartAction(Runnable runnable) {
+ mPendingOnStartAction = runnable;
+ if (runnable != null && mAnimatorOnStartMap == null) {
+ mAnimatorOnStartMap = new HashMap<Animator, Runnable>();
+ }
+ return this;
+ }
+
+ /**
+ * Specifies an action to take place when the next animation ends. The action is only
+ * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
+ * that animation, the runnable will not run.
+ * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
+ * choreographing ViewPropertyAnimator animations with other animations or actions
+ * in the application.
+ *
+ * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
+ * <pre>
+ * Runnable endAction = new Runnable() {
+ * public void run() {
+ * view.animate().x(0);
+ * }
+ * };
+ * view.animate().x(200).withEndAction(endAction);
+ * </pre>
+ *
+ * @param runnable The action to run when the next animation ends.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator withEndAction(Runnable runnable) {
+ mPendingOnEndAction = runnable;
+ if (runnable != null && mAnimatorOnEndMap == null) {
+ mAnimatorOnEndMap = new HashMap<Animator, Runnable>();
+ }
+ return this;
+ }
+
+ boolean hasActions() {
+ return mPendingSetupAction != null
+ || mPendingCleanupAction != null
+ || mPendingOnStartAction != null
+ || mPendingOnEndAction != null;
+ }
+
+ /**
+ * Starts the underlying Animator for a set of properties. We use a single animator that
+ * simply runs from 0 to 1, and then use that fractional value to set each property
+ * value accordingly.
+ */
+ private void startAnimation() {
+ if (mRTBackend != null && mRTBackend.startAnimation(this)) {
+ return;
+ }
+ mView.setHasTransientState(true);
+ ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
+ ArrayList<NameValuesHolder> nameValueList =
+ (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
+ mPendingAnimations.clear();
+ int propertyMask = 0;
+ int propertyCount = nameValueList.size();
+ for (int i = 0; i < propertyCount; ++i) {
+ NameValuesHolder nameValuesHolder = nameValueList.get(i);
+ propertyMask |= nameValuesHolder.mNameConstant;
+ }
+ mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
+ if (mPendingSetupAction != null) {
+ mAnimatorSetupMap.put(animator, mPendingSetupAction);
+ mPendingSetupAction = null;
+ }
+ if (mPendingCleanupAction != null) {
+ mAnimatorCleanupMap.put(animator, mPendingCleanupAction);
+ mPendingCleanupAction = null;
+ }
+ if (mPendingOnStartAction != null) {
+ mAnimatorOnStartMap.put(animator, mPendingOnStartAction);
+ mPendingOnStartAction = null;
+ }
+ if (mPendingOnEndAction != null) {
+ mAnimatorOnEndMap.put(animator, mPendingOnEndAction);
+ mPendingOnEndAction = null;
+ }
+ animator.addUpdateListener(mAnimatorEventListener);
+ animator.addListener(mAnimatorEventListener);
+ if (mStartDelaySet) {
+ animator.setStartDelay(mStartDelay);
+ }
+ if (mDurationSet) {
+ animator.setDuration(mDuration);
+ }
+ if (mInterpolatorSet) {
+ animator.setInterpolator(mInterpolator);
+ }
+ animator.start();
+ }
+
+ /**
+ * Utility function, called by the various x(), y(), etc. methods. This stores the
+ * constant name for the property along with the from/delta values that will be used to
+ * calculate and set the property during the animation. This structure is added to the
+ * pending animations, awaiting the eventual start() of the underlying animator. A
+ * Runnable is posted to start the animation, and any pending such Runnable is canceled
+ * (which enables us to end up starting just one animator for all of the properties
+ * specified at one time).
+ *
+ * @param constantName The specifier for the property being animated
+ * @param toValue The value to which the property will animate
+ */
+ private void animateProperty(int constantName, float toValue) {
+ float fromValue = getValue(constantName);
+ float deltaValue = toValue - fromValue;
+ animatePropertyBy(constantName, fromValue, deltaValue);
+ }
+
+ /**
+ * Utility function, called by the various xBy(), yBy(), etc. methods. This method is
+ * just like animateProperty(), except the value is an offset from the property's
+ * current value, instead of an absolute "to" value.
+ *
+ * @param constantName The specifier for the property being animated
+ * @param byValue The amount by which the property will change
+ */
+ private void animatePropertyBy(int constantName, float byValue) {
+ float fromValue = getValue(constantName);
+ animatePropertyBy(constantName, fromValue, byValue);
+ }
+
+ /**
+ * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
+ * details of adding a pending animation and posting the request to start the animation.
+ *
+ * @param constantName The specifier for the property being animated
+ * @param startValue The starting value of the property
+ * @param byValue The amount by which the property will change
+ */
+ private void animatePropertyBy(int constantName, float startValue, float byValue) {
+ // First, cancel any existing animations on this property
+ if (mAnimatorMap.size() > 0) {
+ Animator animatorToCancel = null;
+ Set<Animator> animatorSet = mAnimatorMap.keySet();
+ for (Animator runningAnim : animatorSet) {
+ PropertyBundle bundle = mAnimatorMap.get(runningAnim);
+ if (bundle.cancel(constantName)) {
+ // property was canceled - cancel the animation if it's now empty
+ // Note that it's safe to break out here because every new animation
+ // on a property will cancel a previous animation on that property, so
+ // there can only ever be one such animation running.
+ if (bundle.mPropertyMask == NONE) {
+ // the animation is no longer changing anything - cancel it
+ animatorToCancel = runningAnim;
+ break;
+ }
+ }
+ }
+ if (animatorToCancel != null) {
+ animatorToCancel.cancel();
+ }
+ }
+
+ NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
+ mPendingAnimations.add(nameValuePair);
+ mView.removeCallbacks(mAnimationStarter);
+ mView.postOnAnimation(mAnimationStarter);
+ }
+
+ /**
+ * This method handles setting the property values directly in the View object's fields.
+ * propertyConstant tells it which property should be set, value is the value to set
+ * the property to.
+ *
+ * @param propertyConstant The property to be set
+ * @param value The value to set the property to
+ */
+ private void setValue(int propertyConstant, float value) {
+ final View.TransformationInfo info = mView.mTransformationInfo;
+ final RenderNode renderNode = mView.mRenderNode;
+ switch (propertyConstant) {
+ case TRANSLATION_X:
+ renderNode.setTranslationX(value);
+ break;
+ case TRANSLATION_Y:
+ renderNode.setTranslationY(value);
+ break;
+ case TRANSLATION_Z:
+ renderNode.setTranslationZ(value);
+ break;
+ case ROTATION:
+ renderNode.setRotation(value);
+ break;
+ case ROTATION_X:
+ renderNode.setRotationX(value);
+ break;
+ case ROTATION_Y:
+ renderNode.setRotationY(value);
+ break;
+ case SCALE_X:
+ renderNode.setScaleX(value);
+ break;
+ case SCALE_Y:
+ renderNode.setScaleY(value);
+ break;
+ case X:
+ renderNode.setTranslationX(value - mView.mLeft);
+ break;
+ case Y:
+ renderNode.setTranslationY(value - mView.mTop);
+ break;
+ case Z:
+ renderNode.setTranslationZ(value - renderNode.getElevation());
+ break;
+ case ALPHA:
+ info.mAlpha = value;
+ renderNode.setAlpha(value);
+ break;
+ }
+ }
+
+ /**
+ * This method gets the value of the named property from the View object.
+ *
+ * @param propertyConstant The property whose value should be returned
+ * @return float The value of the named property
+ */
+ private float getValue(int propertyConstant) {
+ final RenderNode node = mView.mRenderNode;
+ switch (propertyConstant) {
+ case TRANSLATION_X:
+ return node.getTranslationX();
+ case TRANSLATION_Y:
+ return node.getTranslationY();
+ case TRANSLATION_Z:
+ return node.getTranslationZ();
+ case ROTATION:
+ return node.getRotation();
+ case ROTATION_X:
+ return node.getRotationX();
+ case ROTATION_Y:
+ return node.getRotationY();
+ case SCALE_X:
+ return node.getScaleX();
+ case SCALE_Y:
+ return node.getScaleY();
+ case X:
+ return mView.mLeft + node.getTranslationX();
+ case Y:
+ return mView.mTop + node.getTranslationY();
+ case Z:
+ return node.getElevation() + node.getTranslationZ();
+ case ALPHA:
+ return mView.mTransformationInfo.mAlpha;
+ }
+ return 0;
+ }
+
+ /**
+ * Utility class that handles the various Animator events. The only ones we care
+ * about are the end event (which we use to clean up the animator map when an animator
+ * finishes) and the update event (which we use to calculate the current value of each
+ * property and then set it on the view object).
+ */
+ private class AnimatorEventListener
+ implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mAnimatorSetupMap != null) {
+ Runnable r = mAnimatorSetupMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorSetupMap.remove(animation);
+ }
+ if (mAnimatorOnStartMap != null) {
+ Runnable r = mAnimatorOnStartMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorOnStartMap.remove(animation);
+ }
+ if (mListener != null) {
+ mListener.onAnimationStart(animation);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (mListener != null) {
+ mListener.onAnimationCancel(animation);
+ }
+ if (mAnimatorOnEndMap != null) {
+ mAnimatorOnEndMap.remove(animation);
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ if (mListener != null) {
+ mListener.onAnimationRepeat(animation);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mView.setHasTransientState(false);
+ if (mAnimatorCleanupMap != null) {
+ Runnable r = mAnimatorCleanupMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorCleanupMap.remove(animation);
+ }
+ if (mListener != null) {
+ mListener.onAnimationEnd(animation);
+ }
+ if (mAnimatorOnEndMap != null) {
+ Runnable r = mAnimatorOnEndMap.get(animation);
+ if (r != null) {
+ r.run();
+ }
+ mAnimatorOnEndMap.remove(animation);
+ }
+ mAnimatorMap.remove(animation);
+ }
+
+ /**
+ * Calculate the current value for each property and set it on the view. Invalidate
+ * the view object appropriately, depending on which properties are being animated.
+ *
+ * @param animation The animator associated with the properties that need to be
+ * set. This animator holds the animation fraction which we will use to calculate
+ * the current value of each property.
+ */
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ PropertyBundle propertyBundle = mAnimatorMap.get(animation);
+ if (propertyBundle == null) {
+ // Shouldn't happen, but just to play it safe
+ return;
+ }
+
+ boolean hardwareAccelerated = mView.isHardwareAccelerated();
+
+ // alpha requires slightly different treatment than the other (transform) properties.
+ // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
+ // logic is dependent on how the view handles an internal call to onSetAlpha().
+ // We track what kinds of properties are set, and how alpha is handled when it is
+ // set, and perform the invalidation steps appropriately.
+ boolean alphaHandled = false;
+ if (!hardwareAccelerated) {
+ mView.invalidateParentCaches();
+ }
+ float fraction = animation.getAnimatedFraction();
+ int propertyMask = propertyBundle.mPropertyMask;
+ if ((propertyMask & TRANSFORM_MASK) != 0) {
+ mView.invalidateViewProperty(hardwareAccelerated, false);
+ }
+ ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
+ if (valueList != null) {
+ int count = valueList.size();
+ for (int i = 0; i < count; ++i) {
+ NameValuesHolder values = valueList.get(i);
+ float value = values.mFromValue + fraction * values.mDeltaValue;
+ if (values.mNameConstant == ALPHA) {
+ alphaHandled = mView.setAlphaNoInvalidation(value);
+ } else {
+ setValue(values.mNameConstant, value);
+ }
+ }
+ }
+ if ((propertyMask & TRANSFORM_MASK) != 0) {
+ if (!hardwareAccelerated) {
+ mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
+ }
+ }
+ // invalidate(false) in all cases except if alphaHandled gets set to true
+ // via the call to setAlphaNoInvalidation(), above
+ if (alphaHandled) {
+ mView.invalidate(true);
+ } else {
+ mView.invalidateViewProperty(false, false);
+ }
+ if (mUpdateListener != null) {
+ mUpdateListener.onAnimationUpdate(animation);
+ }
+ }
+ }
+}
diff --git a/android/view/ViewPropertyAnimatorRT.java b/android/view/ViewPropertyAnimatorRT.java
new file mode 100644
index 00000000..de96887d
--- /dev/null
+++ b/android/view/ViewPropertyAnimatorRT.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 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.animation.TimeInterpolator;
+import android.view.ViewPropertyAnimator.NameValuesHolder;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+
+import java.util.ArrayList;
+
+
+/**
+ * This is a RenderThread driven backend for ViewPropertyAnimator.
+ */
+class ViewPropertyAnimatorRT {
+
+ private static final Interpolator sLinearInterpolator = new LinearInterpolator();
+
+ private final View mView;
+
+ private RenderNodeAnimator mAnimators[] = new RenderNodeAnimator[RenderNodeAnimator.LAST_VALUE + 1];
+
+ ViewPropertyAnimatorRT(View view) {
+ mView = view;
+ }
+
+ /**
+ * @return true if ViewPropertyAnimatorRT handled the animation,
+ * false if ViewPropertyAnimator needs to handle it
+ */
+ public boolean startAnimation(ViewPropertyAnimator parent) {
+ cancelAnimators(parent.mPendingAnimations);
+ if (!canHandleAnimator(parent)) {
+ return false;
+ }
+ doStartAnimation(parent);
+ return true;
+ }
+
+ public void cancelAll() {
+ for (int i = 0; i < mAnimators.length; i++) {
+ if (mAnimators[i] != null) {
+ mAnimators[i].cancel();
+ mAnimators[i] = null;
+ }
+ }
+ }
+
+ private void doStartAnimation(ViewPropertyAnimator parent) {
+ int size = parent.mPendingAnimations.size();
+
+ long startDelay = parent.getStartDelay();
+ long duration = parent.getDuration();
+ TimeInterpolator interpolator = parent.getInterpolator();
+ if (interpolator == null) {
+ // Documented to be LinearInterpolator in ValueAnimator.setInterpolator
+ interpolator = sLinearInterpolator;
+ }
+ if (!RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+ interpolator = new FallbackLUTInterpolator(interpolator, duration);
+ }
+ for (int i = 0; i < size; i++) {
+ NameValuesHolder holder = parent.mPendingAnimations.get(i);
+ int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
+
+ final float finalValue = holder.mFromValue + holder.mDeltaValue;
+ RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue);
+ animator.setStartDelay(startDelay);
+ animator.setDuration(duration);
+ animator.setInterpolator(interpolator);
+ animator.setTarget(mView);
+ animator.start();
+
+ mAnimators[property] = animator;
+ }
+
+ parent.mPendingAnimations.clear();
+ }
+
+ private boolean canHandleAnimator(ViewPropertyAnimator parent) {
+ // TODO: Can we eliminate this entirely?
+ // If RenderNode.animatorProperties() can be toggled to point at staging
+ // instead then RNA can be used as the animators for software as well
+ // as the updateListener fallback paths. If this can be toggled
+ // at the top level somehow, combined with requiresUiRedraw, we could
+ // ensure that RT does not self-animate, allowing for safe driving of
+ // the animators from the UI thread using the same mechanisms
+ // ViewPropertyAnimator does, just with everything sitting on a single
+ // animator subsystem instead of multiple.
+
+ if (parent.getUpdateListener() != null) {
+ return false;
+ }
+ if (parent.getListener() != null) {
+ // TODO support
+ return false;
+ }
+ if (!mView.isHardwareAccelerated()) {
+ // TODO handle this maybe?
+ return false;
+ }
+ if (parent.hasActions()) {
+ return false;
+ }
+ // Here goes nothing...
+ return true;
+ }
+
+ private void cancelAnimators(ArrayList<NameValuesHolder> mPendingAnimations) {
+ int size = mPendingAnimations.size();
+ for (int i = 0; i < size; i++) {
+ NameValuesHolder holder = mPendingAnimations.get(i);
+ int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
+ if (mAnimators[property] != null) {
+ mAnimators[property].cancel();
+ mAnimators[property] = null;
+ }
+ }
+ }
+
+}
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
new file mode 100644
index 00000000..415aad54
--- /dev/null
+++ b/android/view/ViewRootImpl.java
@@ -0,0 +1,7976 @@
+/*
+ * Copyright (C) 2006 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 static android.view.Display.INVALID_DISPLAY;
+import static android.view.View.PFLAG_DRAW_ANIMATION;
+import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+
+import android.Manifest;
+import android.animation.LayoutTransition;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.ResourcesManager;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.input.InputManager;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.AndroidRuntimeException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.TypedValue;
+import android.view.Surface.OutOfResourcesException;
+import android.view.View.AttachInfo;
+import android.view.View.FocusDirection;
+import android.view.View.MeasureSpec;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Scroller;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.PhoneFallbackEventHandler;
+import com.android.internal.util.Preconditions;
+import com.android.internal.view.BaseSurfaceHolder;
+import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * The top of a view hierarchy, implementing the needed protocol between View
+ * and the WindowManager. This is for the most part an internal implementation
+ * detail of {@link WindowManagerGlobal}.
+ *
+ * {@hide}
+ */
+@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
+public final class ViewRootImpl implements ViewParent,
+ View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
+ private static final String TAG = "ViewRootImpl";
+ private static final boolean DBG = false;
+ private static final boolean LOCAL_LOGV = false;
+ /** @noinspection PointlessBooleanExpression*/
+ private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
+ private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV;
+ private static final boolean DEBUG_DIALOG = false || LOCAL_LOGV;
+ private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV;
+ private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV;
+ private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
+ private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
+ private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
+ private static final boolean DEBUG_FPS = false;
+ private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV;
+ private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV;
+
+ /**
+ * Set to false if we do not want to use the multi threaded renderer. Note that by disabling
+ * this, WindowCallbacks will not fire.
+ */
+ private static final boolean USE_MT_RENDERER = true;
+
+ /**
+ * Set this system property to true to force the view hierarchy to render
+ * at 60 Hz. This can be used to measure the potential framerate.
+ */
+ private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering";
+
+ // properties used by emulator to determine display shape
+ public static final String PROPERTY_EMULATOR_WIN_OUTSET_BOTTOM_PX =
+ "ro.emu.win_outset_bottom_px";
+
+ /**
+ * Maximum time we allow the user to roll the trackball enough to generate
+ * a key event, before resetting the counters.
+ */
+ static final int MAX_TRACKBALL_DELAY = 250;
+
+ static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
+
+ static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
+ static boolean sFirstDrawComplete = false;
+
+ /**
+ * Callback for notifying about global configuration changes.
+ */
+ public interface ConfigChangedCallback {
+
+ /** Notifies about global config change. */
+ void onConfigurationChanged(Configuration globalConfig);
+ }
+
+ private static final ArrayList<ConfigChangedCallback> sConfigCallbacks = new ArrayList<>();
+
+ /**
+ * Callback for notifying activities about override configuration changes.
+ */
+ public interface ActivityConfigCallback {
+
+ /**
+ * Notifies about override config change and/or move to different display.
+ * @param overrideConfig New override config to apply to activity.
+ * @param newDisplayId New display id, {@link Display#INVALID_DISPLAY} if not changed.
+ */
+ void onConfigurationChanged(Configuration overrideConfig, int newDisplayId);
+ }
+
+ /**
+ * Callback used to notify corresponding activity about override configuration change and make
+ * sure that all resources are set correctly before updating the ViewRootImpl's internal state.
+ */
+ private ActivityConfigCallback mActivityConfigCallback;
+
+ /**
+ * Used when configuration change first updates the config of corresponding activity.
+ * In that case we receive a call back from {@link ActivityThread} and this flag is used to
+ * preserve the initial value.
+ *
+ * @see #performConfigurationChange(Configuration, Configuration, boolean, int)
+ */
+ private boolean mForceNextConfigUpdate;
+
+ /**
+ * Signals that compatibility booleans have been initialized according to
+ * target SDK versions.
+ */
+ private static boolean sCompatibilityDone = false;
+
+ /**
+ * Always assign focus if a focusable View is available.
+ */
+ private static boolean sAlwaysAssignFocus;
+
+ /**
+ * This list must only be modified by the main thread, so a lock is only needed when changing
+ * the list or when accessing the list from a non-main thread.
+ */
+ @GuardedBy("mWindowCallbacks")
+ final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>();
+ final Context mContext;
+ final IWindowSession mWindowSession;
+ @NonNull Display mDisplay;
+ final DisplayManager mDisplayManager;
+ final String mBasePackageName;
+
+ final int[] mTmpLocation = new int[2];
+
+ final TypedValue mTmpValue = new TypedValue();
+
+ final Thread mThread;
+
+ final WindowLeaked mLocation;
+
+ final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
+
+ final W mWindow;
+
+ final int mTargetSdkVersion;
+
+ int mSeq;
+
+ View mView;
+
+ View mAccessibilityFocusedHost;
+ AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
+
+ // True if the window currently has pointer capture enabled.
+ boolean mPointerCapture;
+
+ int mViewVisibility;
+ boolean mAppVisible = true;
+ // For recents to freeform transition we need to keep drawing after the app receives information
+ // that it became invisible. This will ignore that information and depend on the decor view
+ // visibility to control drawing. The decor view visibility will get adjusted when the app get
+ // stopped and that's when the app will stop drawing further frames.
+ private boolean mForceDecorViewVisibility = false;
+ // Used for tracking app visibility updates separately in case we get double change. This will
+ // make sure that we always call relayout for the corresponding window.
+ private boolean mAppVisibilityChanged;
+ int mOrigWindowType = -1;
+
+ /** Whether the window had focus during the most recent traversal. */
+ boolean mHadWindowFocus;
+
+ /**
+ * Whether the window lost focus during a previous traversal and has not
+ * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
+ * accessibility events should be sent during traversal.
+ */
+ boolean mLostWindowFocus;
+
+ // Set to true if the owner of this window is in the stopped state,
+ // so the window should no longer be active.
+ boolean mStopped = false;
+
+ // Set to true if the owner of this window is in ambient mode,
+ // which means it won't receive input events.
+ boolean mIsAmbientMode = false;
+
+ // Set to true to stop input during an Activity Transition.
+ boolean mPausedForTransition = false;
+
+ boolean mLastInCompatMode = false;
+
+ SurfaceHolder.Callback2 mSurfaceHolderCallback;
+ BaseSurfaceHolder mSurfaceHolder;
+ boolean mIsCreating;
+ boolean mDrawingAllowed;
+
+ final Region mTransparentRegion;
+ final Region mPreviousTransparentRegion;
+
+ int mWidth;
+ int mHeight;
+ Rect mDirty;
+ public boolean mIsAnimating;
+
+ private boolean mDragResizing;
+ private boolean mInvalidateRootRequested;
+ private int mResizeMode;
+ private int mCanvasOffsetX;
+ private int mCanvasOffsetY;
+ private boolean mActivityRelaunched;
+
+ CompatibilityInfo.Translator mTranslator;
+
+ final View.AttachInfo mAttachInfo;
+ InputChannel mInputChannel;
+ InputQueue.Callback mInputQueueCallback;
+ InputQueue mInputQueue;
+ FallbackEventHandler mFallbackEventHandler;
+ Choreographer mChoreographer;
+
+ final Rect mTempRect; // used in the transaction to not thrash the heap.
+ final Rect mVisRect; // used to retrieve visible rect of focused view.
+
+ public boolean mTraversalScheduled;
+ int mTraversalBarrier;
+ boolean mWillDrawSoon;
+ /** Set to true while in performTraversals for detecting when die(true) is called from internal
+ * callbacks such as onMeasure, onPreDraw, onDraw and deferring doDie() until later. */
+ boolean mIsInTraversal;
+ boolean mApplyInsetsRequested;
+ boolean mLayoutRequested;
+ boolean mFirst;
+ boolean mReportNextDraw;
+ boolean mFullRedrawNeeded;
+ boolean mNewSurfaceNeeded;
+ boolean mHasHadWindowFocus;
+ boolean mLastWasImTarget;
+ boolean mForceNextWindowRelayout;
+ CountDownLatch mWindowDrawCountDown;
+
+ boolean mIsDrawing;
+ int mLastSystemUiVisibility;
+ int mClientWindowLayoutFlags;
+ boolean mLastOverscanRequested;
+
+ // Pool of queued input events.
+ private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
+ private QueuedInputEvent mQueuedInputEventPool;
+ private int mQueuedInputEventPoolSize;
+
+ /* Input event queue.
+ * Pending input events are input events waiting to be delivered to the input stages
+ * and handled by the application.
+ */
+ QueuedInputEvent mPendingInputEventHead;
+ QueuedInputEvent mPendingInputEventTail;
+ int mPendingInputEventCount;
+ boolean mProcessInputEventsScheduled;
+ boolean mUnbufferedInputDispatch;
+ String mPendingInputEventQueueLengthCounterName = "pq";
+
+ InputStage mFirstInputStage;
+ InputStage mFirstPostImeInputStage;
+ InputStage mSyntheticInputStage;
+
+ boolean mWindowAttributesChanged = false;
+ int mWindowAttributesChangesFlag = 0;
+
+ // These can be accessed by any thread, must be protected with a lock.
+ // Surface can never be reassigned or cleared (use Surface.clear()).
+ final Surface mSurface = new Surface();
+
+ boolean mAdded;
+ boolean mAddedTouchMode;
+
+ // These are accessed by multiple threads.
+ final Rect mWinFrame; // frame given by window manager.
+
+ final Rect mPendingOverscanInsets = new Rect();
+ final Rect mPendingVisibleInsets = new Rect();
+ final Rect mPendingStableInsets = new Rect();
+ final Rect mPendingContentInsets = new Rect();
+ final Rect mPendingOutsets = new Rect();
+ final Rect mPendingBackDropFrame = new Rect();
+ boolean mPendingAlwaysConsumeNavBar;
+ final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
+ = new ViewTreeObserver.InternalInsetsInfo();
+
+ final Rect mDispatchContentInsets = new Rect();
+ final Rect mDispatchStableInsets = new Rect();
+
+ private WindowInsets mLastWindowInsets;
+
+ /** Last applied configuration obtained from resources. */
+ private final Configuration mLastConfigurationFromResources = new Configuration();
+ /** Last configuration reported from WM or via {@link #MSG_UPDATE_CONFIGURATION}. */
+ private final MergedConfiguration mLastReportedMergedConfiguration = new MergedConfiguration();
+ /** Configurations waiting to be applied. */
+ private final MergedConfiguration mPendingMergedConfiguration = new MergedConfiguration();
+
+ boolean mScrollMayChange;
+ @SoftInputModeFlags
+ int mSoftInputMode;
+ WeakReference<View> mLastScrolledFocus;
+ int mScrollY;
+ int mCurScrollY;
+ Scroller mScroller;
+ static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
+ private ArrayList<LayoutTransition> mPendingTransitions;
+
+ final ViewConfiguration mViewConfiguration;
+
+ /* Drag/drop */
+ ClipDescription mDragDescription;
+ View mCurrentDragView;
+ volatile Object mLocalDragState;
+ final PointF mDragPoint = new PointF();
+ final PointF mLastTouchPoint = new PointF();
+ int mLastTouchSource;
+
+ private boolean mProfileRendering;
+ private Choreographer.FrameCallback mRenderProfiler;
+ private boolean mRenderProfilingEnabled;
+
+ // Variables to track frames per second, enabled via DEBUG_FPS flag
+ private long mFpsStartTime = -1;
+ private long mFpsPrevTime = -1;
+ private int mFpsNumFrames;
+
+ private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ private PointerIcon mCustomPointerIcon = null;
+
+ /**
+ * see {@link #playSoundEffect(int)}
+ */
+ AudioManager mAudioManager;
+
+ final AccessibilityManager mAccessibilityManager;
+
+ AccessibilityInteractionController mAccessibilityInteractionController;
+
+ final AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager =
+ new AccessibilityInteractionConnectionManager();
+ final HighContrastTextManager mHighContrastTextManager;
+
+ SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
+
+ HashSet<View> mTempHashSet;
+
+ private final int mDensity;
+ private final int mNoncompatDensity;
+
+ private boolean mInLayout = false;
+ ArrayList<View> mLayoutRequesters = new ArrayList<View>();
+ boolean mHandlingLayoutInLayoutRequest = false;
+
+ private int mViewLayoutDirectionInitial;
+
+ /** Set to true once doDie() has been called. */
+ private boolean mRemoved;
+
+ private boolean mNeedsRendererSetup;
+
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
+ static final class SystemUiVisibilityInfo {
+ int seq;
+ int globalVisibility;
+ int localValue;
+ int localChanges;
+ }
+
+ private String mTag = TAG;
+
+ public ViewRootImpl(Context context, Display display) {
+ mContext = context;
+ mWindowSession = WindowManagerGlobal.getWindowSession();
+ mDisplay = display;
+ mBasePackageName = context.getBasePackageName();
+ mThread = Thread.currentThread();
+ mLocation = new WindowLeaked(null);
+ mLocation.fillInStackTrace();
+ mWidth = -1;
+ mHeight = -1;
+ mDirty = new Rect();
+ mTempRect = new Rect();
+ mVisRect = new Rect();
+ mWinFrame = new Rect();
+ mWindow = new W(this);
+ mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ mViewVisibility = View.GONE;
+ mTransparentRegion = new Region();
+ mPreviousTransparentRegion = new Region();
+ mFirst = true; // true for the first time the view is added
+ mAdded = false;
+ mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
+ context);
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mAccessibilityManager.addAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager, mHandler);
+ mHighContrastTextManager = new HighContrastTextManager();
+ mAccessibilityManager.addHighTextContrastStateChangeListener(
+ mHighContrastTextManager, mHandler);
+ mViewConfiguration = ViewConfiguration.get(context);
+ mDensity = context.getResources().getDisplayMetrics().densityDpi;
+ mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
+ mFallbackEventHandler = new PhoneFallbackEventHandler(context);
+ mChoreographer = Choreographer.getInstance();
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+
+ if (!sCompatibilityDone) {
+ sAlwaysAssignFocus = true;
+
+ sCompatibilityDone = true;
+ }
+
+ loadSystemProperties();
+ }
+
+ public static void addFirstDrawHandler(Runnable callback) {
+ synchronized (sFirstDrawHandlers) {
+ if (!sFirstDrawComplete) {
+ sFirstDrawHandlers.add(callback);
+ }
+ }
+ }
+
+ /** Add static config callback to be notified about global config changes. */
+ public static void addConfigCallback(ConfigChangedCallback callback) {
+ synchronized (sConfigCallbacks) {
+ sConfigCallbacks.add(callback);
+ }
+ }
+
+ /** Add activity config callback to be notified about override config changes. */
+ public void setActivityConfigCallback(ActivityConfigCallback callback) {
+ mActivityConfigCallback = callback;
+ }
+
+ public void addWindowCallbacks(WindowCallbacks callback) {
+ if (USE_MT_RENDERER) {
+ synchronized (mWindowCallbacks) {
+ mWindowCallbacks.add(callback);
+ }
+ }
+ }
+
+ public void removeWindowCallbacks(WindowCallbacks callback) {
+ if (USE_MT_RENDERER) {
+ synchronized (mWindowCallbacks) {
+ mWindowCallbacks.remove(callback);
+ }
+ }
+ }
+
+ public void reportDrawFinish() {
+ if (mWindowDrawCountDown != null) {
+ mWindowDrawCountDown.countDown();
+ }
+ }
+
+ // FIXME for perf testing only
+ private boolean mProfile = false;
+
+ /**
+ * Call this to profile the next traversal call.
+ * FIXME for perf testing only. Remove eventually
+ */
+ public void profile() {
+ mProfile = true;
+ }
+
+ /**
+ * Indicates whether we are in touch mode. Calling this method triggers an IPC
+ * call and should be avoided whenever possible.
+ *
+ * @return True, if the device is in touch mode, false otherwise.
+ *
+ * @hide
+ */
+ static boolean isInTouchMode() {
+ IWindowSession windowSession = WindowManagerGlobal.peekWindowSession();
+ if (windowSession != null) {
+ try {
+ return windowSession.getInTouchMode();
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Notifies us that our child has been rebuilt, following
+ * a window preservation operation. In these cases we
+ * keep the same DecorView, but the activity controlling it
+ * is a different instance, and we need to update our
+ * callbacks.
+ *
+ * @hide
+ */
+ public void notifyChildRebuilt() {
+ if (mView instanceof RootViewSurfaceTaker) {
+ if (mSurfaceHolderCallback != null) {
+ mSurfaceHolder.removeCallback(mSurfaceHolderCallback);
+ }
+
+ mSurfaceHolderCallback =
+ ((RootViewSurfaceTaker)mView).willYouTakeTheSurface();
+
+ if (mSurfaceHolderCallback != null) {
+ mSurfaceHolder = new TakenSurfaceHolder();
+ mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
+ mSurfaceHolder.addCallback(mSurfaceHolderCallback);
+ } else {
+ mSurfaceHolder = null;
+ }
+
+ mInputQueueCallback =
+ ((RootViewSurfaceTaker)mView).willYouTakeTheInputQueue();
+ if (mInputQueueCallback != null) {
+ mInputQueueCallback.onInputQueueCreated(mInputQueue);
+ }
+ }
+ }
+
+ /**
+ * We have one child
+ */
+ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
+ synchronized (this) {
+ if (mView == null) {
+ mView = view;
+
+ mAttachInfo.mDisplayState = mDisplay.getState();
+ mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+
+ mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
+ mFallbackEventHandler.setView(view);
+ mWindowAttributes.copyFrom(attrs);
+ if (mWindowAttributes.packageName == null) {
+ mWindowAttributes.packageName = mBasePackageName;
+ }
+ attrs = mWindowAttributes;
+ setTag();
+
+ if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
+ & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
+ && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
+ Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!");
+ }
+ // Keep track of the actual window flags supplied by the client.
+ mClientWindowLayoutFlags = attrs.flags;
+
+ setAccessibilityFocus(null, null);
+
+ if (view instanceof RootViewSurfaceTaker) {
+ mSurfaceHolderCallback =
+ ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
+ if (mSurfaceHolderCallback != null) {
+ mSurfaceHolder = new TakenSurfaceHolder();
+ mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
+ mSurfaceHolder.addCallback(mSurfaceHolderCallback);
+ }
+ }
+
+ // Compute surface insets required to draw at specified Z value.
+ // TODO: Use real shadow insets for a constant max Z.
+ if (!attrs.hasManualSurfaceInsets) {
+ attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
+ }
+
+ CompatibilityInfo compatibilityInfo =
+ mDisplay.getDisplayAdjustments().getCompatibilityInfo();
+ mTranslator = compatibilityInfo.getTranslator();
+
+ // If the application owns the surface, don't enable hardware acceleration
+ if (mSurfaceHolder == null) {
+ enableHardwareAcceleration(attrs);
+ }
+
+ boolean restore = false;
+ if (mTranslator != null) {
+ mSurface.setCompatibilityTranslator(mTranslator);
+ restore = true;
+ attrs.backup();
+ mTranslator.translateWindowLayout(attrs);
+ }
+ if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
+
+ if (!compatibilityInfo.supportsScreen()) {
+ attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ mLastInCompatMode = true;
+ }
+
+ mSoftInputMode = attrs.softInputMode;
+ mWindowAttributesChanged = true;
+ mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
+ mAttachInfo.mRootView = view;
+ mAttachInfo.mScalingRequired = mTranslator != null;
+ mAttachInfo.mApplicationScale =
+ mTranslator == null ? 1.0f : mTranslator.applicationScale;
+ if (panelParentView != null) {
+ mAttachInfo.mPanelParentWindowToken
+ = panelParentView.getApplicationWindowToken();
+ }
+ mAdded = true;
+ int res; /* = WindowManagerImpl.ADD_OKAY; */
+
+ // Schedule the first layout -before- adding to the window
+ // manager, to make sure we do the relayout before receiving
+ // any other events from the system.
+ requestLayout();
+ if ((mWindowAttributes.inputFeatures
+ & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
+ mInputChannel = new InputChannel();
+ }
+ mForceDecorViewVisibility = (mWindowAttributes.privateFlags
+ & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
+ try {
+ mOrigWindowType = mWindowAttributes.type;
+ mAttachInfo.mRecomputeGlobalAttributes = true;
+ collectViewAttributes();
+ res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
+ getHostVisibility(), mDisplay.getDisplayId(),
+ mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
+ mAttachInfo.mOutsets, mInputChannel);
+ } catch (RemoteException e) {
+ mAdded = false;
+ mView = null;
+ mAttachInfo.mRootView = null;
+ mInputChannel = null;
+ mFallbackEventHandler.setView(null);
+ unscheduleTraversals();
+ setAccessibilityFocus(null, null);
+ throw new RuntimeException("Adding window failed", e);
+ } finally {
+ if (restore) {
+ attrs.restore();
+ }
+ }
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
+ }
+ mPendingOverscanInsets.set(0, 0, 0, 0);
+ mPendingContentInsets.set(mAttachInfo.mContentInsets);
+ mPendingStableInsets.set(mAttachInfo.mStableInsets);
+ mPendingVisibleInsets.set(0, 0, 0, 0);
+ mAttachInfo.mAlwaysConsumeNavBar =
+ (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0;
+ mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar;
+ if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
+ if (res < WindowManagerGlobal.ADD_OKAY) {
+ mAttachInfo.mRootView = null;
+ mAdded = false;
+ mFallbackEventHandler.setView(null);
+ unscheduleTraversals();
+ setAccessibilityFocus(null, null);
+ switch (res) {
+ case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
+ case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
+ throw new WindowManager.BadTokenException(
+ "Unable to add window -- token " + attrs.token
+ + " is not valid; is your activity running?");
+ case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
+ throw new WindowManager.BadTokenException(
+ "Unable to add window -- token " + attrs.token
+ + " is not for an application");
+ case WindowManagerGlobal.ADD_APP_EXITING:
+ throw new WindowManager.BadTokenException(
+ "Unable to add window -- app for token " + attrs.token
+ + " is exiting");
+ case WindowManagerGlobal.ADD_DUPLICATE_ADD:
+ throw new WindowManager.BadTokenException(
+ "Unable to add window -- window " + mWindow
+ + " has already been added");
+ case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
+ // Silently ignore -- we would have just removed it
+ // right away, anyway.
+ return;
+ case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
+ throw new WindowManager.BadTokenException("Unable to add window "
+ + mWindow + " -- another window of type "
+ + mWindowAttributes.type + " already exists");
+ case WindowManagerGlobal.ADD_PERMISSION_DENIED:
+ throw new WindowManager.BadTokenException("Unable to add window "
+ + mWindow + " -- permission denied for window type "
+ + mWindowAttributes.type);
+ case WindowManagerGlobal.ADD_INVALID_DISPLAY:
+ throw new WindowManager.InvalidDisplayException("Unable to add window "
+ + mWindow + " -- the specified display can not be found");
+ case WindowManagerGlobal.ADD_INVALID_TYPE:
+ throw new WindowManager.InvalidDisplayException("Unable to add window "
+ + mWindow + " -- the specified window type "
+ + mWindowAttributes.type + " is not valid");
+ }
+ throw new RuntimeException(
+ "Unable to add window -- unknown error code " + res);
+ }
+
+ if (view instanceof RootViewSurfaceTaker) {
+ mInputQueueCallback =
+ ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
+ }
+ if (mInputChannel != null) {
+ if (mInputQueueCallback != null) {
+ mInputQueue = new InputQueue();
+ mInputQueueCallback.onInputQueueCreated(mInputQueue);
+ }
+ mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
+ Looper.myLooper());
+ }
+
+ view.assignParent(this);
+ mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
+ mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
+
+ if (mAccessibilityManager.isEnabled()) {
+ mAccessibilityInteractionConnectionManager.ensureConnection();
+ }
+
+ if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ // Set up the input pipeline.
+ CharSequence counterSuffix = attrs.getTitle();
+ mSyntheticInputStage = new SyntheticInputStage();
+ InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
+ InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
+ "aq:native-post-ime:" + counterSuffix);
+ InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
+ InputStage imeStage = new ImeInputStage(earlyPostImeStage,
+ "aq:ime:" + counterSuffix);
+ InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
+ InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
+ "aq:native-pre-ime:" + counterSuffix);
+
+ mFirstInputStage = nativePreImeStage;
+ mFirstPostImeInputStage = earlyPostImeStage;
+ mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
+ }
+ }
+ }
+
+ private void setTag() {
+ final String[] split = mWindowAttributes.getTitle().toString().split("\\.");
+ if (split.length > 0) {
+ mTag = TAG + "[" + split[split.length - 1] + "]";
+ }
+ }
+
+ /** Whether the window is in local focus mode or not */
+ private boolean isInLocalFocusMode() {
+ return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+ }
+
+ public int getWindowFlags() {
+ return mWindowAttributes.flags;
+ }
+
+ public int getDisplayId() {
+ return mDisplay.getDisplayId();
+ }
+
+ public CharSequence getTitle() {
+ return mWindowAttributes.getTitle();
+ }
+
+ void destroyHardwareResources() {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.destroyHardwareResources(mView);
+ mAttachInfo.mThreadedRenderer.destroy();
+ }
+ }
+
+ public void detachFunctor(long functor) {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ // Fence so that any pending invokeFunctor() messages will be processed
+ // before we return from detachFunctor.
+ mAttachInfo.mThreadedRenderer.stopDrawing();
+ }
+ }
+
+ /**
+ * Schedules the functor for execution in either kModeProcess or
+ * kModeProcessNoContext, depending on whether or not there is an EGLContext.
+ *
+ * @param functor The native functor to invoke
+ * @param waitForCompletion If true, this will not return until the functor
+ * has invoked. If false, the functor may be invoked
+ * asynchronously.
+ */
+ public static void invokeFunctor(long functor, boolean waitForCompletion) {
+ ThreadedRenderer.invokeFunctor(functor, waitForCompletion);
+ }
+
+ public void registerAnimatingRenderNode(RenderNode animator) {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.registerAnimatingRenderNode(animator);
+ } else {
+ if (mAttachInfo.mPendingAnimatingRenderNodes == null) {
+ mAttachInfo.mPendingAnimatingRenderNodes = new ArrayList<RenderNode>();
+ }
+ mAttachInfo.mPendingAnimatingRenderNodes.add(animator);
+ }
+ }
+
+ public void registerVectorDrawableAnimator(
+ AnimatedVectorDrawable.VectorDrawableAnimatorRT animator) {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.registerVectorDrawableAnimator(animator);
+ }
+ }
+
+ private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
+ mAttachInfo.mHardwareAccelerated = false;
+ mAttachInfo.mHardwareAccelerationRequested = false;
+
+ // Don't enable hardware acceleration when the application is in compatibility mode
+ if (mTranslator != null) return;
+
+ // Try to enable hardware acceleration if requested
+ final boolean hardwareAccelerated =
+ (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
+
+ if (hardwareAccelerated) {
+ if (!ThreadedRenderer.isAvailable()) {
+ return;
+ }
+
+ // Persistent processes (including the system) should not do
+ // accelerated rendering on low-end devices. In that case,
+ // sRendererDisabled will be set. In addition, the system process
+ // itself should never do accelerated rendering. In that case, both
+ // sRendererDisabled and sSystemRendererDisabled are set. When
+ // sSystemRendererDisabled is set, PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED
+ // can be used by code on the system process to escape that and enable
+ // HW accelerated drawing. (This is basically for the lock screen.)
+
+ final boolean fakeHwAccelerated = (attrs.privateFlags &
+ WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
+ final boolean forceHwAccelerated = (attrs.privateFlags &
+ WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
+
+ if (fakeHwAccelerated) {
+ // This is exclusively for the preview windows the window manager
+ // shows for launching applications, so they will look more like
+ // the app being launched.
+ mAttachInfo.mHardwareAccelerationRequested = true;
+ } else if (!ThreadedRenderer.sRendererDisabled
+ || (ThreadedRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.destroy();
+ }
+
+ final Rect insets = attrs.surfaceInsets;
+ final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
+ || insets.top != 0 || insets.bottom != 0;
+ final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
+ final boolean wideGamut =
+ mContext.getResources().getConfiguration().isScreenWideColorGamut()
+ && attrs.getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
+
+ mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
+ attrs.getTitle().toString());
+ mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut);
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mHardwareAccelerated =
+ mAttachInfo.mHardwareAccelerationRequested = true;
+ }
+ }
+ }
+ }
+
+ public View getView() {
+ return mView;
+ }
+
+ final WindowLeaked getLocation() {
+ return mLocation;
+ }
+
+ void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
+ synchronized (this) {
+ final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
+ final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
+ final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
+ final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
+ final int oldSoftInputMode = mWindowAttributes.softInputMode;
+ final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
+
+ if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
+ & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
+ && (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
+ Slog.d(mTag, "setLayoutParams: FLAG_KEEP_SCREEN_ON from true to false!");
+ }
+
+ // Keep track of the actual window flags supplied by the client.
+ mClientWindowLayoutFlags = attrs.flags;
+
+ // Preserve compatible window flag if exists.
+ final int compatibleWindowFlag = mWindowAttributes.privateFlags
+ & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+
+ // Transfer over system UI visibility values as they carry current state.
+ attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
+ attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
+
+ mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
+ if ((mWindowAttributesChangesFlag
+ & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
+ // Recompute system ui visibility.
+ mAttachInfo.mRecomputeGlobalAttributes = true;
+ }
+ if ((mWindowAttributesChangesFlag
+ & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
+ // Request to update light center.
+ mAttachInfo.mNeedsUpdateLightCenter = true;
+ }
+ if (mWindowAttributes.packageName == null) {
+ mWindowAttributes.packageName = mBasePackageName;
+ }
+ mWindowAttributes.privateFlags |= compatibleWindowFlag;
+
+ if (mWindowAttributes.preservePreviousSurfaceInsets) {
+ // Restore old surface insets.
+ mWindowAttributes.surfaceInsets.set(
+ oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
+ mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
+ } else if (mWindowAttributes.surfaceInsets.left != oldInsetLeft
+ || mWindowAttributes.surfaceInsets.top != oldInsetTop
+ || mWindowAttributes.surfaceInsets.right != oldInsetRight
+ || mWindowAttributes.surfaceInsets.bottom != oldInsetBottom) {
+ mNeedsRendererSetup = true;
+ }
+
+ applyKeepScreenOnFlag(mWindowAttributes);
+
+ if (newView) {
+ mSoftInputMode = attrs.softInputMode;
+ requestLayout();
+ }
+
+ // Don't lose the mode we last auto-computed.
+ if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
+ == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
+ mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
+ & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
+ | (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
+ }
+
+ mWindowAttributesChanged = true;
+ scheduleTraversals();
+ }
+ }
+
+ void handleAppVisibility(boolean visible) {
+ if (mAppVisible != visible) {
+ mAppVisible = visible;
+ mAppVisibilityChanged = true;
+ scheduleTraversals();
+ if (!mAppVisible) {
+ WindowManagerGlobal.trimForeground();
+ }
+ }
+ }
+
+ void handleGetNewSurface() {
+ mNewSurfaceNeeded = true;
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ }
+
+ private final DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (mView != null && mDisplay.getDisplayId() == displayId) {
+ final int oldDisplayState = mAttachInfo.mDisplayState;
+ final int newDisplayState = mDisplay.getState();
+ if (oldDisplayState != newDisplayState) {
+ mAttachInfo.mDisplayState = newDisplayState;
+ pokeDrawLockIfNeeded();
+ if (oldDisplayState != Display.STATE_UNKNOWN) {
+ final int oldScreenState = toViewScreenState(oldDisplayState);
+ final int newScreenState = toViewScreenState(newDisplayState);
+ if (oldScreenState != newScreenState) {
+ mView.dispatchScreenStateChanged(newScreenState);
+ }
+ if (oldDisplayState == Display.STATE_OFF) {
+ // Draw was suppressed so we need to for it to happen here.
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ private int toViewScreenState(int displayState) {
+ return displayState == Display.STATE_OFF ?
+ View.SCREEN_STATE_OFF : View.SCREEN_STATE_ON;
+ }
+ };
+
+ /**
+ * Notify about move to a different display.
+ * @param displayId The id of the display where this view root is moved to.
+ * @param config Configuration of the resources on new display after move.
+ *
+ * @hide
+ */
+ public void onMovedToDisplay(int displayId, Configuration config) {
+ if (mDisplay.getDisplayId() == displayId) {
+ return;
+ }
+
+ // Get new instance of display based on current display adjustments. It may be updated later
+ // if moving between the displays also involved a configuration change.
+ mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
+ mView.getResources());
+ mAttachInfo.mDisplayState = mDisplay.getState();
+ // Internal state updated, now notify the view hierarchy.
+ mView.dispatchMovedToDisplay(mDisplay, config);
+ }
+
+ void pokeDrawLockIfNeeded() {
+ final int displayState = mAttachInfo.mDisplayState;
+ if (mView != null && mAdded && mTraversalScheduled
+ && (displayState == Display.STATE_DOZE
+ || displayState == Display.STATE_DOZE_SUSPEND)) {
+ try {
+ mWindowSession.pokeDrawLock(mWindow);
+ } catch (RemoteException ex) {
+ // System server died, oh well.
+ }
+ }
+ }
+
+ @Override
+ public void requestFitSystemWindows() {
+ checkThread();
+ mApplyInsetsRequested = true;
+ scheduleTraversals();
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mHandlingLayoutInLayoutRequest) {
+ checkThread();
+ mLayoutRequested = true;
+ scheduleTraversals();
+ }
+ }
+
+ @Override
+ public boolean isLayoutRequested() {
+ return mLayoutRequested;
+ }
+
+ @Override
+ public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
+ if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
+ mIsAnimating = true;
+ }
+ invalidate();
+ }
+
+ void invalidate() {
+ mDirty.set(0, 0, mWidth, mHeight);
+ if (!mWillDrawSoon) {
+ scheduleTraversals();
+ }
+ }
+
+ void invalidateWorld(View view) {
+ view.invalidate();
+ if (view instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) view;
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ invalidateWorld(parent.getChildAt(i));
+ }
+ }
+ }
+
+ @Override
+ public void invalidateChild(View child, Rect dirty) {
+ invalidateChildInParent(null, dirty);
+ }
+
+ @Override
+ public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
+ checkThread();
+ if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
+
+ if (dirty == null) {
+ invalidate();
+ return null;
+ } else if (dirty.isEmpty() && !mIsAnimating) {
+ return null;
+ }
+
+ if (mCurScrollY != 0 || mTranslator != null) {
+ mTempRect.set(dirty);
+ dirty = mTempRect;
+ if (mCurScrollY != 0) {
+ dirty.offset(0, -mCurScrollY);
+ }
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(dirty);
+ }
+ if (mAttachInfo.mScalingRequired) {
+ dirty.inset(-1, -1);
+ }
+ }
+
+ invalidateRectOnScreen(dirty);
+
+ return null;
+ }
+
+ private void invalidateRectOnScreen(Rect dirty) {
+ final Rect localDirty = mDirty;
+ if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
+ mAttachInfo.mSetIgnoreDirtyState = true;
+ mAttachInfo.mIgnoreDirtyState = true;
+ }
+
+ // Add the new dirty rect to the current one
+ localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
+ // Intersect with the bounds of the window to skip
+ // updates that lie outside of the visible region
+ final float appScale = mAttachInfo.mApplicationScale;
+ final boolean intersected = localDirty.intersect(0, 0,
+ (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
+ if (!intersected) {
+ localDirty.setEmpty();
+ }
+ if (!mWillDrawSoon && (intersected || mIsAnimating)) {
+ scheduleTraversals();
+ }
+ }
+
+ public void setIsAmbientMode(boolean ambient) {
+ mIsAmbientMode = ambient;
+ }
+
+ interface WindowStoppedCallback {
+ public void windowStopped(boolean stopped);
+ }
+ private final ArrayList<WindowStoppedCallback> mWindowStoppedCallbacks = new ArrayList<>();
+
+ void addWindowStoppedCallback(WindowStoppedCallback c) {
+ mWindowStoppedCallbacks.add(c);
+ }
+
+ void removeWindowStoppedCallback(WindowStoppedCallback c) {
+ mWindowStoppedCallbacks.remove(c);
+ }
+
+ void setWindowStopped(boolean stopped) {
+ if (mStopped != stopped) {
+ mStopped = stopped;
+ final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
+ if (renderer != null) {
+ if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
+ renderer.setStopped(mStopped);
+ }
+ if (!mStopped) {
+ scheduleTraversals();
+ } else {
+ if (renderer != null) {
+ renderer.destroyHardwareResources(mView);
+ }
+ }
+
+ for (int i = 0; i < mWindowStoppedCallbacks.size(); i++) {
+ mWindowStoppedCallbacks.get(i).windowStopped(stopped);
+ }
+ }
+ }
+
+ /**
+ * Block the input events during an Activity Transition. The KEYCODE_BACK event is allowed
+ * through to allow quick reversal of the Activity Transition.
+ *
+ * @param paused true to pause, false to resume.
+ */
+ public void setPausedForTransition(boolean paused) {
+ mPausedForTransition = paused;
+ }
+
+ @Override
+ public ViewParent getParent() {
+ return null;
+ }
+
+ @Override
+ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+ if (child != mView) {
+ throw new RuntimeException("child is not mine, honest!");
+ }
+ // Note: don't apply scroll offset, because we want to know its
+ // visibility in the virtual canvas being given to the view hierarchy.
+ return r.intersect(0, 0, mWidth, mHeight);
+ }
+
+ @Override
+ public void bringChildToFront(View child) {
+ }
+
+ int getHostVisibility() {
+ return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
+ }
+
+ /**
+ * Add LayoutTransition to the list of transitions to be started in the next traversal.
+ * This list will be cleared after the transitions on the list are start()'ed. These
+ * transitionsa re added by LayoutTransition itself when it sets up animations. The setup
+ * happens during the layout phase of traversal, which we want to complete before any of the
+ * animations are started (because those animations may side-effect properties that layout
+ * depends upon, like the bounding rectangles of the affected views). So we add the transition
+ * to the list and it is started just prior to starting the drawing phase of traversal.
+ *
+ * @param transition The LayoutTransition to be started on the next traversal.
+ *
+ * @hide
+ */
+ public void requestTransitionStart(LayoutTransition transition) {
+ if (mPendingTransitions == null || !mPendingTransitions.contains(transition)) {
+ if (mPendingTransitions == null) {
+ mPendingTransitions = new ArrayList<LayoutTransition>();
+ }
+ mPendingTransitions.add(transition);
+ }
+ }
+
+ /**
+ * Notifies the HardwareRenderer that a new frame will be coming soon.
+ * Currently only {@link ThreadedRenderer} cares about this, and uses
+ * this knowledge to adjust the scheduling of off-thread animations
+ */
+ void notifyRendererOfFramePending() {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.notifyFramePending();
+ }
+ }
+
+ void scheduleTraversals() {
+ if (!mTraversalScheduled) {
+ mTraversalScheduled = true;
+ mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
+ mChoreographer.postCallback(
+ Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
+ if (!mUnbufferedInputDispatch) {
+ scheduleConsumeBatchedInput();
+ }
+ notifyRendererOfFramePending();
+ pokeDrawLockIfNeeded();
+ }
+ }
+
+ void unscheduleTraversals() {
+ if (mTraversalScheduled) {
+ mTraversalScheduled = false;
+ mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
+ mChoreographer.removeCallbacks(
+ Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
+ }
+ }
+
+ void doTraversal() {
+ if (mTraversalScheduled) {
+ mTraversalScheduled = false;
+ mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
+
+ if (mProfile) {
+ Debug.startMethodTracing("ViewAncestor");
+ }
+
+ performTraversals();
+
+ if (mProfile) {
+ Debug.stopMethodTracing();
+ mProfile = false;
+ }
+ }
+ }
+
+ private void applyKeepScreenOnFlag(WindowManager.LayoutParams params) {
+ // Update window's global keep screen on flag: if a view has requested
+ // that the screen be kept on, then it is always set; otherwise, it is
+ // set to whatever the client last requested for the global state.
+ if (mAttachInfo.mKeepScreenOn) {
+ params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+ } else {
+ params.flags = (params.flags&~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ | (mClientWindowLayoutFlags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+ private boolean collectViewAttributes() {
+ if (mAttachInfo.mRecomputeGlobalAttributes) {
+ //Log.i(mTag, "Computing view hierarchy attributes!");
+ mAttachInfo.mRecomputeGlobalAttributes = false;
+ boolean oldScreenOn = mAttachInfo.mKeepScreenOn;
+ mAttachInfo.mKeepScreenOn = false;
+ mAttachInfo.mSystemUiVisibility = 0;
+ mAttachInfo.mHasSystemUiListeners = false;
+ mView.dispatchCollectViewAttributes(mAttachInfo, 0);
+ mAttachInfo.mSystemUiVisibility &= ~mAttachInfo.mDisabledSystemUiVisibility;
+ WindowManager.LayoutParams params = mWindowAttributes;
+ mAttachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params);
+ if (mAttachInfo.mKeepScreenOn != oldScreenOn
+ || mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
+ || mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
+ applyKeepScreenOnFlag(params);
+ params.subtreeSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+ params.hasSystemUiListeners = mAttachInfo.mHasSystemUiListeners;
+ mView.dispatchWindowSystemUiVisiblityChanged(mAttachInfo.mSystemUiVisibility);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) {
+ int vis = 0;
+ // Translucent decor window flags imply stable system ui visibility.
+ if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) {
+ vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ }
+ if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0) {
+ vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ }
+ return vis;
+ }
+
+ private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
+ final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
+ int childWidthMeasureSpec;
+ int childHeightMeasureSpec;
+ boolean windowSizeMayChange = false;
+
+ if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
+ "Measuring " + host + " in display " + desiredWindowWidth
+ + "x" + desiredWindowHeight + "...");
+
+ boolean goodMeasure = false;
+ if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ // On large screens, we don't want to allow dialogs to just
+ // stretch to fill the entire width of the screen to display
+ // one line of text. First try doing the layout at a smaller
+ // size to see if it will fit.
+ final DisplayMetrics packageMetrics = res.getDisplayMetrics();
+ res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
+ int baseSize = 0;
+ if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+ baseSize = (int)mTmpValue.getDimension(packageMetrics);
+ }
+ if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ + ", desiredWindowWidth=" + desiredWindowWidth);
+ if (baseSize != 0 && desiredWindowWidth > baseSize) {
+ childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
+ childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
+ if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
+ goodMeasure = true;
+ } else {
+ // Didn't fit in that size... try expanding a bit.
+ baseSize = (baseSize+desiredWindowWidth)/2;
+ if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ + baseSize);
+ childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
+ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
+ if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
+ if (DEBUG_DIALOG) Log.v(mTag, "Good!");
+ goodMeasure = true;
+ }
+ }
+ }
+ }
+
+ if (!goodMeasure) {
+ childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
+ childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
+ windowSizeMayChange = true;
+ }
+ }
+
+ if (DBG) {
+ System.out.println("======================================");
+ System.out.println("performTraversals -- after measure");
+ host.debug();
+ }
+
+ return windowSizeMayChange;
+ }
+
+ /**
+ * Modifies the input matrix such that it maps view-local coordinates to
+ * on-screen coordinates.
+ *
+ * @param m input matrix to modify
+ */
+ void transformMatrixToGlobal(Matrix m) {
+ m.preTranslate(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+ }
+
+ /**
+ * Modifies the input matrix such that it maps on-screen coordinates to
+ * view-local coordinates.
+ *
+ * @param m input matrix to modify
+ */
+ void transformMatrixToLocal(Matrix m) {
+ m.postTranslate(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
+ }
+
+ /* package */ WindowInsets getWindowInsets(boolean forceConstruct) {
+ if (mLastWindowInsets == null || forceConstruct) {
+ mDispatchContentInsets.set(mAttachInfo.mContentInsets);
+ mDispatchStableInsets.set(mAttachInfo.mStableInsets);
+ Rect contentInsets = mDispatchContentInsets;
+ Rect stableInsets = mDispatchStableInsets;
+ // For dispatch we preserve old logic, but for direct requests from Views we allow to
+ // immediately use pending insets.
+ if (!forceConstruct
+ && (!mPendingContentInsets.equals(contentInsets) ||
+ !mPendingStableInsets.equals(stableInsets))) {
+ contentInsets = mPendingContentInsets;
+ stableInsets = mPendingStableInsets;
+ }
+ Rect outsets = mAttachInfo.mOutsets;
+ if (outsets.left > 0 || outsets.top > 0 || outsets.right > 0 || outsets.bottom > 0) {
+ contentInsets = new Rect(contentInsets.left + outsets.left,
+ contentInsets.top + outsets.top, contentInsets.right + outsets.right,
+ contentInsets.bottom + outsets.bottom);
+ }
+ mLastWindowInsets = new WindowInsets(contentInsets,
+ null /* windowDecorInsets */, stableInsets,
+ mContext.getResources().getConfiguration().isScreenRound(),
+ mAttachInfo.mAlwaysConsumeNavBar);
+ }
+ return mLastWindowInsets;
+ }
+
+ void dispatchApplyInsets(View host) {
+ host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
+ }
+
+ private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) {
+ return lp.type == TYPE_STATUS_BAR_PANEL
+ || lp.type == TYPE_INPUT_METHOD
+ || lp.type == TYPE_VOLUME_OVERLAY;
+ }
+
+ private int dipToPx(int dip) {
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+ return (int) (displayMetrics.density * dip + 0.5f);
+ }
+
+ private void performTraversals() {
+ // cache mView since it is used so much below...
+ final View host = mView;
+
+ if (DBG) {
+ System.out.println("======================================");
+ System.out.println("performTraversals");
+ host.debug();
+ }
+
+ if (host == null || !mAdded)
+ return;
+
+ mIsInTraversal = true;
+ mWillDrawSoon = true;
+ boolean windowSizeMayChange = false;
+ boolean newSurface = false;
+ boolean surfaceChanged = false;
+ WindowManager.LayoutParams lp = mWindowAttributes;
+
+ int desiredWindowWidth;
+ int desiredWindowHeight;
+
+ final int viewVisibility = getHostVisibility();
+ final boolean viewVisibilityChanged = !mFirst
+ && (mViewVisibility != viewVisibility || mNewSurfaceNeeded
+ // Also check for possible double visibility update, which will make current
+ // viewVisibility value equal to mViewVisibility and we may miss it.
+ || mAppVisibilityChanged);
+ mAppVisibilityChanged = false;
+ final boolean viewUserVisibilityChanged = !mFirst &&
+ ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
+
+ WindowManager.LayoutParams params = null;
+ if (mWindowAttributesChanged) {
+ mWindowAttributesChanged = false;
+ surfaceChanged = true;
+ params = lp;
+ }
+ CompatibilityInfo compatibilityInfo =
+ mDisplay.getDisplayAdjustments().getCompatibilityInfo();
+ if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
+ params = lp;
+ mFullRedrawNeeded = true;
+ mLayoutRequested = true;
+ if (mLastInCompatMode) {
+ params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ mLastInCompatMode = false;
+ } else {
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ mLastInCompatMode = true;
+ }
+ }
+
+ mWindowAttributesChangesFlag = 0;
+
+ Rect frame = mWinFrame;
+ if (mFirst) {
+ mFullRedrawNeeded = true;
+ mLayoutRequested = true;
+
+ final Configuration config = mContext.getResources().getConfiguration();
+ if (shouldUseDisplaySize(lp)) {
+ // NOTE -- system code, won't try to do compat mode.
+ Point size = new Point();
+ mDisplay.getRealSize(size);
+ desiredWindowWidth = size.x;
+ desiredWindowHeight = size.y;
+ } else {
+ desiredWindowWidth = dipToPx(config.screenWidthDp);
+ desiredWindowHeight = dipToPx(config.screenHeightDp);
+ }
+
+ // We used to use the following condition to choose 32 bits drawing caches:
+ // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
+ // However, windows are now always 32 bits by default, so choose 32 bits
+ mAttachInfo.mUse32BitDrawingCache = true;
+ mAttachInfo.mHasWindowFocus = false;
+ mAttachInfo.mWindowVisibility = viewVisibility;
+ mAttachInfo.mRecomputeGlobalAttributes = false;
+ mLastConfigurationFromResources.setTo(config);
+ mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+ // Set the layout direction if it has not been set before (inherit is the default)
+ if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
+ host.setLayoutDirection(config.getLayoutDirection());
+ }
+ host.dispatchAttachedToWindow(mAttachInfo, 0);
+ mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
+ dispatchApplyInsets(host);
+ //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
+
+ } else {
+ desiredWindowWidth = frame.width();
+ desiredWindowHeight = frame.height();
+ if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
+ if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
+ mFullRedrawNeeded = true;
+ mLayoutRequested = true;
+ windowSizeMayChange = true;
+ }
+ }
+
+ if (viewVisibilityChanged) {
+ mAttachInfo.mWindowVisibility = viewVisibility;
+ host.dispatchWindowVisibilityChanged(viewVisibility);
+ if (viewUserVisibilityChanged) {
+ host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
+ }
+ if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
+ endDragResizing();
+ destroyHardwareResources();
+ }
+ if (viewVisibility == View.GONE) {
+ // After making a window gone, we will count it as being
+ // shown for the first time the next time it gets focus.
+ mHasHadWindowFocus = false;
+ }
+ }
+
+ // Non-visible windows can't hold accessibility focus.
+ if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
+ host.clearAccessibilityFocus();
+ }
+
+ // Execute enqueued actions on every traversal in case a detached view enqueued an action
+ getRunQueue().executeActions(mAttachInfo.mHandler);
+
+ boolean insetsChanged = false;
+
+ boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
+ if (layoutRequested) {
+
+ final Resources res = mView.getContext().getResources();
+
+ if (mFirst) {
+ // make sure touch mode code executes by setting cached value
+ // to opposite of the added touch mode.
+ mAttachInfo.mInTouchMode = !mAddedTouchMode;
+ ensureTouchModeLocally(mAddedTouchMode);
+ } else {
+ if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
+ insetsChanged = true;
+ }
+ if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
+ insetsChanged = true;
+ }
+ if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
+ insetsChanged = true;
+ }
+ if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
+ mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ + mAttachInfo.mVisibleInsets);
+ }
+ if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
+ insetsChanged = true;
+ }
+ if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
+ insetsChanged = true;
+ }
+ if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+ || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ windowSizeMayChange = true;
+
+ if (shouldUseDisplaySize(lp)) {
+ // NOTE -- system code, won't try to do compat mode.
+ Point size = new Point();
+ mDisplay.getRealSize(size);
+ desiredWindowWidth = size.x;
+ desiredWindowHeight = size.y;
+ } else {
+ Configuration config = res.getConfiguration();
+ desiredWindowWidth = dipToPx(config.screenWidthDp);
+ desiredWindowHeight = dipToPx(config.screenHeightDp);
+ }
+ }
+ }
+
+ // Ask host how big it wants to be
+ windowSizeMayChange |= measureHierarchy(host, lp, res,
+ desiredWindowWidth, desiredWindowHeight);
+ }
+
+ if (collectViewAttributes()) {
+ params = lp;
+ }
+ if (mAttachInfo.mForceReportNewAttributes) {
+ mAttachInfo.mForceReportNewAttributes = false;
+ params = lp;
+ }
+
+ if (mFirst || mAttachInfo.mViewVisibilityChanged) {
+ mAttachInfo.mViewVisibilityChanged = false;
+ int resizeMode = mSoftInputMode &
+ WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+ // If we are in auto resize mode, then we need to determine
+ // what mode to use now.
+ if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
+ final int N = mAttachInfo.mScrollContainers.size();
+ for (int i=0; i<N; i++) {
+ if (mAttachInfo.mScrollContainers.get(i).isShown()) {
+ resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ }
+ }
+ if (resizeMode == 0) {
+ resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+ }
+ if ((lp.softInputMode &
+ WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
+ lp.softInputMode = (lp.softInputMode &
+ ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
+ resizeMode;
+ params = lp;
+ }
+ }
+ }
+
+ if (params != null) {
+ if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
+ if (!PixelFormat.formatHasAlpha(params.format)) {
+ params.format = PixelFormat.TRANSLUCENT;
+ }
+ }
+ mAttachInfo.mOverscanRequested = (params.flags
+ & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
+ }
+
+ if (mApplyInsetsRequested) {
+ mApplyInsetsRequested = false;
+ mLastOverscanRequested = mAttachInfo.mOverscanRequested;
+ dispatchApplyInsets(host);
+ if (mLayoutRequested) {
+ // Short-circuit catching a new layout request here, so
+ // we don't need to go through two layout passes when things
+ // change due to fitting system windows, which can happen a lot.
+ windowSizeMayChange |= measureHierarchy(host, lp,
+ mView.getContext().getResources(),
+ desiredWindowWidth, desiredWindowHeight);
+ }
+ }
+
+ if (layoutRequested) {
+ // Clear this now, so that if anything requests a layout in the
+ // rest of this function we will catch it and re-run a full
+ // layout pass.
+ mLayoutRequested = false;
+ }
+
+ boolean windowShouldResize = layoutRequested && windowSizeMayChange
+ && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
+ || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
+ frame.width() < desiredWindowWidth && frame.width() != mWidth)
+ || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
+ frame.height() < desiredWindowHeight && frame.height() != mHeight));
+ windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
+
+ // If the activity was just relaunched, it might have unfrozen the task bounds (while
+ // relaunching), so we need to force a call into window manager to pick up the latest
+ // bounds.
+ windowShouldResize |= mActivityRelaunched;
+
+ // Determine whether to compute insets.
+ // If there are no inset listeners remaining then we may still need to compute
+ // insets in case the old insets were non-empty and must be reset.
+ final boolean computesInternalInsets =
+ mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
+ || mAttachInfo.mHasNonEmptyGivenInternalInsets;
+
+ boolean insetsPending = false;
+ int relayoutResult = 0;
+ boolean updatedConfiguration = false;
+
+ final int surfaceGenerationId = mSurface.getGenerationId();
+
+ final boolean isViewVisible = viewVisibility == View.VISIBLE;
+ final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
+ if (mFirst || windowShouldResize || insetsChanged ||
+ viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
+ mForceNextWindowRelayout = false;
+
+ if (isViewVisible) {
+ // If this window is giving internal insets to the window
+ // manager, and it is being added or changing its visibility,
+ // then we want to first give the window manager "fake"
+ // insets to cause it to effectively ignore the content of
+ // the window during layout. This avoids it briefly causing
+ // other windows to resize/move based on the raw frame of the
+ // window, waiting until we can finish laying out this window
+ // and get back to the window manager with the ultimately
+ // computed insets.
+ insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
+ }
+
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.mSurfaceLock.lock();
+ mDrawingAllowed = true;
+ }
+
+ boolean hwInitialized = false;
+ boolean contentInsetsChanged = false;
+ boolean hadSurface = mSurface.isValid();
+
+ try {
+ if (DEBUG_LAYOUT) {
+ Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
+ host.getMeasuredHeight() + ", params=" + params);
+ }
+
+ if (mAttachInfo.mThreadedRenderer != null) {
+ // relayoutWindow may decide to destroy mSurface. As that decision
+ // happens in WindowManager service, we need to be defensive here
+ // and stop using the surface in case it gets destroyed.
+ if (mAttachInfo.mThreadedRenderer.pauseSurface(mSurface)) {
+ // Animations were running so we need to push a frame
+ // to resume them
+ mDirty.set(0, 0, mWidth, mHeight);
+ }
+ mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
+ }
+ relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
+
+ if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
+ + " overscan=" + mPendingOverscanInsets.toShortString()
+ + " content=" + mPendingContentInsets.toShortString()
+ + " visible=" + mPendingVisibleInsets.toShortString()
+ + " visible=" + mPendingStableInsets.toShortString()
+ + " outsets=" + mPendingOutsets.toShortString()
+ + " surface=" + mSurface);
+
+ // If the pending {@link MergedConfiguration} handed back from
+ // {@link #relayoutWindow} does not match the one last reported,
+ // WindowManagerService has reported back a frame from a configuration not yet
+ // handled by the client. In this case, we need to accept the configuration so we
+ // do not lay out and draw with the wrong configuration.
+ if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
+ if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
+ + mPendingMergedConfiguration.getMergedConfiguration());
+ performConfigurationChange(mPendingMergedConfiguration, !mFirst,
+ INVALID_DISPLAY /* same display */);
+ updatedConfiguration = true;
+ }
+
+ final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
+ mAttachInfo.mOverscanInsets);
+ contentInsetsChanged = !mPendingContentInsets.equals(
+ mAttachInfo.mContentInsets);
+ final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
+ mAttachInfo.mVisibleInsets);
+ final boolean stableInsetsChanged = !mPendingStableInsets.equals(
+ mAttachInfo.mStableInsets);
+ final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
+ final boolean surfaceSizeChanged = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
+ final boolean alwaysConsumeNavBarChanged =
+ mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
+ if (contentInsetsChanged) {
+ mAttachInfo.mContentInsets.set(mPendingContentInsets);
+ if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: "
+ + mAttachInfo.mContentInsets);
+ }
+ if (overscanInsetsChanged) {
+ mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
+ if (DEBUG_LAYOUT) Log.v(mTag, "Overscan insets changing to: "
+ + mAttachInfo.mOverscanInsets);
+ // Need to relayout with content insets.
+ contentInsetsChanged = true;
+ }
+ if (stableInsetsChanged) {
+ mAttachInfo.mStableInsets.set(mPendingStableInsets);
+ if (DEBUG_LAYOUT) Log.v(mTag, "Decor insets changing to: "
+ + mAttachInfo.mStableInsets);
+ // Need to relayout with content insets.
+ contentInsetsChanged = true;
+ }
+ if (alwaysConsumeNavBarChanged) {
+ mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar;
+ contentInsetsChanged = true;
+ }
+ if (contentInsetsChanged || mLastSystemUiVisibility !=
+ mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
+ || mLastOverscanRequested != mAttachInfo.mOverscanRequested
+ || outsetsChanged) {
+ mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+ mLastOverscanRequested = mAttachInfo.mOverscanRequested;
+ mAttachInfo.mOutsets.set(mPendingOutsets);
+ mApplyInsetsRequested = false;
+ dispatchApplyInsets(host);
+ }
+ if (visibleInsetsChanged) {
+ mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ + mAttachInfo.mVisibleInsets);
+ }
+
+ if (!hadSurface) {
+ if (mSurface.isValid()) {
+ // If we are creating a new surface, then we need to
+ // completely redraw it. Also, when we get to the
+ // point of drawing it we will hold off and schedule
+ // a new traversal instead. This is so we can tell the
+ // window manager about all of the windows being displayed
+ // before actually drawing them, so it can display then
+ // all at once.
+ newSurface = true;
+ mFullRedrawNeeded = true;
+ mPreviousTransparentRegion.setEmpty();
+
+ // Only initialize up-front if transparent regions are not
+ // requested, otherwise defer to see if the entire window
+ // will be transparent
+ if (mAttachInfo.mThreadedRenderer != null) {
+ try {
+ hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
+ mSurface);
+ if (hwInitialized && (host.mPrivateFlags
+ & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
+ // Don't pre-allocate if transparent regions
+ // are requested as they may not be needed
+ mSurface.allocateBuffers();
+ }
+ } catch (OutOfResourcesException e) {
+ handleOutOfResourcesException(e);
+ return;
+ }
+ }
+ }
+ } else if (!mSurface.isValid()) {
+ // If the surface has been removed, then reset the scroll
+ // positions.
+ if (mLastScrolledFocus != null) {
+ mLastScrolledFocus.clear();
+ }
+ mScrollY = mCurScrollY = 0;
+ if (mView instanceof RootViewSurfaceTaker) {
+ ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
+ }
+ if (mScroller != null) {
+ mScroller.abortAnimation();
+ }
+ // Our surface is gone
+ if (mAttachInfo.mThreadedRenderer != null &&
+ mAttachInfo.mThreadedRenderer.isEnabled()) {
+ mAttachInfo.mThreadedRenderer.destroy();
+ }
+ } else if ((surfaceGenerationId != mSurface.getGenerationId()
+ || surfaceSizeChanged || windowRelayoutWasForced)
+ && mSurfaceHolder == null
+ && mAttachInfo.mThreadedRenderer != null) {
+ mFullRedrawNeeded = true;
+ try {
+ // Need to do updateSurface (which leads to CanvasContext::setSurface and
+ // re-create the EGLSurface) if either the Surface changed (as indicated by
+ // generation id), or WindowManager changed the surface size. The latter is
+ // because on some chips, changing the consumer side's BufferQueue size may
+ // not take effect immediately unless we create a new EGLSurface.
+ // Note that frame size change doesn't always imply surface size change (eg.
+ // drag resizing uses fullscreen surface), need to check surfaceSizeChanged
+ // flag from WindowManager.
+ mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
+ } catch (OutOfResourcesException e) {
+ handleOutOfResourcesException(e);
+ return;
+ }
+ }
+
+ final boolean freeformResizing = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
+ final boolean dockedResizing = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
+ final boolean dragResizing = freeformResizing || dockedResizing;
+ if (mDragResizing != dragResizing) {
+ if (dragResizing) {
+ mResizeMode = freeformResizing
+ ? RESIZE_MODE_FREEFORM
+ : RESIZE_MODE_DOCKED_DIVIDER;
+ startDragResizing(mPendingBackDropFrame,
+ mWinFrame.equals(mPendingBackDropFrame), mPendingVisibleInsets,
+ mPendingStableInsets, mResizeMode);
+ } else {
+ // We shouldn't come here, but if we come we should end the resize.
+ endDragResizing();
+ }
+ }
+ if (!USE_MT_RENDERER) {
+ if (dragResizing) {
+ mCanvasOffsetX = mWinFrame.left;
+ mCanvasOffsetY = mWinFrame.top;
+ } else {
+ mCanvasOffsetX = mCanvasOffsetY = 0;
+ }
+ }
+ } catch (RemoteException e) {
+ }
+
+ if (DEBUG_ORIENTATION) Log.v(
+ TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
+
+ mAttachInfo.mWindowLeft = frame.left;
+ mAttachInfo.mWindowTop = frame.top;
+
+ // !!FIXME!! This next section handles the case where we did not get the
+ // window size we asked for. We should avoid this by getting a maximum size from
+ // the window session beforehand.
+ if (mWidth != frame.width() || mHeight != frame.height()) {
+ mWidth = frame.width();
+ mHeight = frame.height();
+ }
+
+ if (mSurfaceHolder != null) {
+ // The app owns the surface; tell it about what is going on.
+ if (mSurface.isValid()) {
+ // XXX .copyFrom() doesn't work!
+ //mSurfaceHolder.mSurface.copyFrom(mSurface);
+ mSurfaceHolder.mSurface = mSurface;
+ }
+ mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
+ mSurfaceHolder.mSurfaceLock.unlock();
+ if (mSurface.isValid()) {
+ if (!hadSurface) {
+ mSurfaceHolder.ungetCallbacks();
+
+ mIsCreating = true;
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
+ }
+ surfaceChanged = true;
+ }
+ if (surfaceChanged || surfaceGenerationId != mSurface.getGenerationId()) {
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, lp.format,
+ mWidth, mHeight);
+ }
+ }
+ }
+ mIsCreating = false;
+ } else if (hadSurface) {
+ mSurfaceHolder.ungetCallbacks();
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
+ }
+ mSurfaceHolder.mSurfaceLock.lock();
+ try {
+ mSurfaceHolder.mSurface = new Surface();
+ } finally {
+ mSurfaceHolder.mSurfaceLock.unlock();
+ }
+ }
+ }
+
+ final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer;
+ if (threadedRenderer != null && threadedRenderer.isEnabled()) {
+ if (hwInitialized
+ || mWidth != threadedRenderer.getWidth()
+ || mHeight != threadedRenderer.getHeight()
+ || mNeedsRendererSetup) {
+ threadedRenderer.setup(mWidth, mHeight, mAttachInfo,
+ mWindowAttributes.surfaceInsets);
+ mNeedsRendererSetup = false;
+ }
+ }
+
+ if (!mStopped || mReportNextDraw) {
+ boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
+ (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
+ if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
+ || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
+ updatedConfiguration) {
+ int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
+ int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+
+ if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ + mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ + " mHeight=" + mHeight
+ + " measuredHeight=" + host.getMeasuredHeight()
+ + " coveredInsetsChanged=" + contentInsetsChanged);
+
+ // Ask host how big it wants to be
+ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+ // Implementation of weights from WindowManager.LayoutParams
+ // We just grow the dimensions as needed and re-measure if
+ // needs be
+ int width = host.getMeasuredWidth();
+ int height = host.getMeasuredHeight();
+ boolean measureAgain = false;
+
+ if (lp.horizontalWeight > 0.0f) {
+ width += (int) ((mWidth - width) * lp.horizontalWeight);
+ childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
+ MeasureSpec.EXACTLY);
+ measureAgain = true;
+ }
+ if (lp.verticalWeight > 0.0f) {
+ height += (int) ((mHeight - height) * lp.verticalWeight);
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
+ MeasureSpec.EXACTLY);
+ measureAgain = true;
+ }
+
+ if (measureAgain) {
+ if (DEBUG_LAYOUT) Log.v(mTag,
+ "And hey let's measure once more: width=" + width
+ + " height=" + height);
+ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ layoutRequested = true;
+ }
+ }
+ } else {
+ // Not the first pass and no window/insets/visibility change but the window
+ // may have moved and we need check that and if so to update the left and right
+ // in the attach info. We translate only the window frame since on window move
+ // the window manager tells us only for the new frame but the insets are the
+ // same and we do not want to translate them more than once.
+ maybeHandleWindowMove(frame);
+ }
+
+ final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
+ boolean triggerGlobalLayoutListener = didLayout
+ || mAttachInfo.mRecomputeGlobalAttributes;
+ if (didLayout) {
+ performLayout(lp, mWidth, mHeight);
+
+ // By this point all views have been sized and positioned
+ // We can compute the transparent area
+
+ if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
+ // start out transparent
+ // TODO: AVOID THAT CALL BY CACHING THE RESULT?
+ host.getLocationInWindow(mTmpLocation);
+ mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
+ mTmpLocation[0] + host.mRight - host.mLeft,
+ mTmpLocation[1] + host.mBottom - host.mTop);
+
+ host.gatherTransparentRegion(mTransparentRegion);
+ if (mTranslator != null) {
+ mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
+ }
+
+ if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
+ mPreviousTransparentRegion.set(mTransparentRegion);
+ mFullRedrawNeeded = true;
+ // reconfigure window manager
+ try {
+ mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ if (DBG) {
+ System.out.println("======================================");
+ System.out.println("performTraversals -- after setFrame");
+ host.debug();
+ }
+ }
+
+ if (triggerGlobalLayoutListener) {
+ mAttachInfo.mRecomputeGlobalAttributes = false;
+ mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
+ }
+
+ if (computesInternalInsets) {
+ // Clear the original insets.
+ final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
+ insets.reset();
+
+ // Compute new insets in place.
+ mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
+ mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
+
+ // Tell the window manager.
+ if (insetsPending || !mLastGivenInsets.equals(insets)) {
+ mLastGivenInsets.set(insets);
+
+ // Translate insets to screen coordinates if needed.
+ final Rect contentInsets;
+ final Rect visibleInsets;
+ final Region touchableRegion;
+ if (mTranslator != null) {
+ contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
+ visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
+ touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
+ } else {
+ contentInsets = insets.contentInsets;
+ visibleInsets = insets.visibleInsets;
+ touchableRegion = insets.touchableRegion;
+ }
+
+ try {
+ mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
+ contentInsets, visibleInsets, touchableRegion);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ if (mFirst && sAlwaysAssignFocus) {
+ // handle first focus request
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
+ + mView.hasFocus());
+ if (mView != null) {
+ if (!mView.hasFocus()) {
+ mView.restoreDefaultFocus();
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
+ + mView.findFocus());
+ } else {
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
+ + mView.findFocus());
+ }
+ }
+ }
+
+ final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
+ final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
+ final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
+ if (regainedFocus) {
+ mLostWindowFocus = false;
+ } else if (!hasWindowFocus && mHadWindowFocus) {
+ mLostWindowFocus = true;
+ }
+
+ if (changedVisibility || regainedFocus) {
+ // Toasts are presented as notifications - don't present them as windows as well
+ boolean isToast = (mWindowAttributes == null) ? false
+ : (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);
+ if (!isToast) {
+ host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+ }
+
+ mFirst = false;
+ mWillDrawSoon = false;
+ mNewSurfaceNeeded = false;
+ mActivityRelaunched = false;
+ mViewVisibility = viewVisibility;
+ mHadWindowFocus = hasWindowFocus;
+
+ if (hasWindowFocus && !isInLocalFocusMode()) {
+ final boolean imTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
+ if (imTarget != mLastWasImTarget) {
+ mLastWasImTarget = imTarget;
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && imTarget) {
+ imm.onPreWindowFocus(mView, hasWindowFocus);
+ imm.onPostWindowFocus(mView, mView.findFocus(),
+ mWindowAttributes.softInputMode,
+ !mHasHadWindowFocus, mWindowAttributes.flags);
+ }
+ }
+ }
+
+ // Remember if we must report the next draw.
+ if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
+ reportNextDraw();
+ }
+
+ boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
+
+ if (!cancelDraw && !newSurface) {
+ if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ mPendingTransitions.get(i).startChangingAnimations();
+ }
+ mPendingTransitions.clear();
+ }
+
+ performDraw();
+ } else {
+ if (isViewVisible) {
+ // Try again
+ scheduleTraversals();
+ } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ mPendingTransitions.get(i).endChangingAnimations();
+ }
+ mPendingTransitions.clear();
+ }
+ }
+
+ mIsInTraversal = false;
+ }
+
+ private void maybeHandleWindowMove(Rect frame) {
+
+ // TODO: Well, we are checking whether the frame has changed similarly
+ // to how this is done for the insets. This is however incorrect since
+ // the insets and the frame are translated. For example, the old frame
+ // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
+ // reported frame is (2, 2 - 2, 2) which implies no change but this is not
+ // true since we are comparing a not translated value to a translated one.
+ // This scenario is rare but we may want to fix that.
+
+ final boolean windowMoved = mAttachInfo.mWindowLeft != frame.left
+ || mAttachInfo.mWindowTop != frame.top;
+ if (windowMoved) {
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWinFrame(frame);
+ }
+ mAttachInfo.mWindowLeft = frame.left;
+ mAttachInfo.mWindowTop = frame.top;
+ }
+ if (windowMoved || mAttachInfo.mNeedsUpdateLightCenter) {
+ // Update the light position for the new offsets.
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.setLightCenter(mAttachInfo);
+ }
+ mAttachInfo.mNeedsUpdateLightCenter = false;
+ }
+ }
+
+ private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
+ Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
+ try {
+ if (!mWindowSession.outOfMemory(mWindow) &&
+ Process.myUid() != Process.SYSTEM_UID) {
+ Slog.w(mTag, "No processes killed for memory; killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
+ }
+ mLayoutRequested = true; // ask wm for a new surface next time.
+ }
+
+ private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
+ if (mView == null) {
+ return;
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
+ try {
+ mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ /**
+ * Called by {@link android.view.View#isInLayout()} to determine whether the view hierarchy
+ * is currently undergoing a layout pass.
+ *
+ * @return whether the view hierarchy is currently undergoing a layout pass
+ */
+ boolean isInLayout() {
+ return mInLayout;
+ }
+
+ /**
+ * Called by {@link android.view.View#requestLayout()} if the view hierarchy is currently
+ * undergoing a layout pass. requestLayout() should not generally be called during layout,
+ * unless the container hierarchy knows what it is doing (i.e., it is fine as long as
+ * all children in that container hierarchy are measured and laid out at the end of the layout
+ * pass for that container). If requestLayout() is called anyway, we handle it correctly
+ * by registering all requesters during a frame as it proceeds. At the end of the frame,
+ * we check all of those views to see if any still have pending layout requests, which
+ * indicates that they were not correctly handled by their container hierarchy. If that is
+ * the case, we clear all such flags in the tree, to remove the buggy flag state that leads
+ * to blank containers, and force a second request/measure/layout pass in this frame. If
+ * more requestLayout() calls are received during that second layout pass, we post those
+ * requests to the next frame to avoid possible infinite loops.
+ *
+ * <p>The return value from this method indicates whether the request should proceed
+ * (if it is a request during the first layout pass) or should be skipped and posted to the
+ * next frame (if it is a request during the second layout pass).</p>
+ *
+ * @param view the view that requested the layout.
+ *
+ * @return true if request should proceed, false otherwise.
+ */
+ boolean requestLayoutDuringLayout(final View view) {
+ if (view.mParent == null || view.mAttachInfo == null) {
+ // Would not normally trigger another layout, so just let it pass through as usual
+ return true;
+ }
+ if (!mLayoutRequesters.contains(view)) {
+ mLayoutRequesters.add(view);
+ }
+ if (!mHandlingLayoutInLayoutRequest) {
+ // Let the request proceed normally; it will be processed in a second layout pass
+ // if necessary
+ return true;
+ } else {
+ // Don't let the request proceed during the second layout pass.
+ // It will post to the next frame instead.
+ return false;
+ }
+ }
+
+ private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
+ int desiredWindowHeight) {
+ mLayoutRequested = false;
+ mScrollMayChange = true;
+ mInLayout = true;
+
+ final View host = mView;
+ if (host == null) {
+ return;
+ }
+ if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
+ Log.v(mTag, "Laying out " + host + " to (" +
+ host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
+ try {
+ host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
+ mInLayout = false;
+ int numViewsRequestingLayout = mLayoutRequesters.size();
+ if (numViewsRequestingLayout > 0) {
+ // requestLayout() was called during layout.
+ // If no layout-request flags are set on the requesting views, there is no problem.
+ // If some requests are still pending, then we need to clear those flags and do
+ // a full request/measure/layout pass to handle this situation.
+ ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
+ false);
+ if (validLayoutRequesters != null) {
+ // Set this flag to indicate that any further requests are happening during
+ // the second pass, which may result in posting those requests to the next
+ // frame instead
+ mHandlingLayoutInLayoutRequest = true;
+
+ // Process fresh layout requests, then measure and layout
+ int numValidRequests = validLayoutRequesters.size();
+ for (int i = 0; i < numValidRequests; ++i) {
+ final View view = validLayoutRequesters.get(i);
+ Log.w("View", "requestLayout() improperly called by " + view +
+ " during layout: running second layout pass");
+ view.requestLayout();
+ }
+ measureHierarchy(host, lp, mView.getContext().getResources(),
+ desiredWindowWidth, desiredWindowHeight);
+ mInLayout = true;
+ host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
+ mHandlingLayoutInLayoutRequest = false;
+
+ // Check the valid requests again, this time without checking/clearing the
+ // layout flags, since requests happening during the second pass get noop'd
+ validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
+ if (validLayoutRequesters != null) {
+ final ArrayList<View> finalRequesters = validLayoutRequesters;
+ // Post second-pass requests to the next frame
+ getRunQueue().post(new Runnable() {
+ @Override
+ public void run() {
+ int numValidRequests = finalRequesters.size();
+ for (int i = 0; i < numValidRequests; ++i) {
+ final View view = finalRequesters.get(i);
+ Log.w("View", "requestLayout() improperly called by " + view +
+ " during second layout pass: posting in next frame");
+ view.requestLayout();
+ }
+ }
+ });
+ }
+ }
+
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ mInLayout = false;
+ }
+
+ /**
+ * This method is called during layout when there have been calls to requestLayout() during
+ * layout. It walks through the list of views that requested layout to determine which ones
+ * still need it, based on visibility in the hierarchy and whether they have already been
+ * handled (as is usually the case with ListView children).
+ *
+ * @param layoutRequesters The list of views that requested layout during layout
+ * @param secondLayoutRequests Whether the requests were issued during the second layout pass.
+ * If so, the FORCE_LAYOUT flag was not set on requesters.
+ * @return A list of the actual views that still need to be laid out.
+ */
+ private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters,
+ boolean secondLayoutRequests) {
+
+ int numViewsRequestingLayout = layoutRequesters.size();
+ ArrayList<View> validLayoutRequesters = null;
+ for (int i = 0; i < numViewsRequestingLayout; ++i) {
+ View view = layoutRequesters.get(i);
+ if (view != null && view.mAttachInfo != null && view.mParent != null &&
+ (secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==
+ View.PFLAG_FORCE_LAYOUT)) {
+ boolean gone = false;
+ View parent = view;
+ // Only trigger new requests for views in a non-GONE hierarchy
+ while (parent != null) {
+ if ((parent.mViewFlags & View.VISIBILITY_MASK) == View.GONE) {
+ gone = true;
+ break;
+ }
+ if (parent.mParent instanceof View) {
+ parent = (View) parent.mParent;
+ } else {
+ parent = null;
+ }
+ }
+ if (!gone) {
+ if (validLayoutRequesters == null) {
+ validLayoutRequesters = new ArrayList<View>();
+ }
+ validLayoutRequesters.add(view);
+ }
+ }
+ }
+ if (!secondLayoutRequests) {
+ // If we're checking the layout flags, then we need to clean them up also
+ for (int i = 0; i < numViewsRequestingLayout; ++i) {
+ View view = layoutRequesters.get(i);
+ while (view != null &&
+ (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
+ view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+ if (view.mParent instanceof View) {
+ view = (View) view.mParent;
+ } else {
+ view = null;
+ }
+ }
+ }
+ }
+ layoutRequesters.clear();
+ return validLayoutRequesters;
+ }
+
+ @Override
+ public void requestTransparentRegion(View child) {
+ // the test below should not fail unless someone is messing with us
+ checkThread();
+ if (mView == child) {
+ mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
+ // Need to make sure we re-evaluate the window attributes next
+ // time around, to ensure the window has the correct format.
+ mWindowAttributesChanged = true;
+ mWindowAttributesChangesFlag = 0;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Figures out the measure spec for the root view in a window based on it's
+ * layout params.
+ *
+ * @param windowSize
+ * The available width or height of the window
+ *
+ * @param rootDimension
+ * The layout params for one dimension (width or height) of the
+ * window.
+ *
+ * @return The measure spec to use to measure the root view.
+ */
+ private static int getRootMeasureSpec(int windowSize, int rootDimension) {
+ int measureSpec;
+ switch (rootDimension) {
+
+ case ViewGroup.LayoutParams.MATCH_PARENT:
+ // Window can't resize. Force root view to be windowSize.
+ measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
+ break;
+ case ViewGroup.LayoutParams.WRAP_CONTENT:
+ // Window can resize. Set max size for root view.
+ measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
+ break;
+ default:
+ // Window wants to be an exact size. Force root view to be that size.
+ measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
+ break;
+ }
+ return measureSpec;
+ }
+
+ int mHardwareXOffset;
+ int mHardwareYOffset;
+
+ @Override
+ public void onPreDraw(DisplayListCanvas canvas) {
+ // If mCurScrollY is not 0 then this influences the hardwareYOffset. The end result is we
+ // can apply offsets that are not handled by anything else, resulting in underdraw as
+ // the View is shifted (thus shifting the window background) exposing unpainted
+ // content. To handle this with minimal glitches we just clear to BLACK if the window
+ // is opaque. If it's not opaque then HWUI already internally does a glClear to
+ // transparent, so there's no risk of underdraw on non-opaque surfaces.
+ if (mCurScrollY != 0 && mHardwareYOffset != 0 && mAttachInfo.mThreadedRenderer.isOpaque()) {
+ canvas.drawColor(Color.BLACK);
+ }
+ canvas.translate(-mHardwareXOffset, -mHardwareYOffset);
+ }
+
+ @Override
+ public void onPostDraw(DisplayListCanvas canvas) {
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
+ for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+ mWindowCallbacks.get(i).onPostDraw(canvas);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void outputDisplayList(View view) {
+ view.mRenderNode.output();
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.serializeDisplayListTree();
+ }
+ }
+
+ /**
+ * @see #PROPERTY_PROFILE_RENDERING
+ */
+ private void profileRendering(boolean enabled) {
+ if (mProfileRendering) {
+ mRenderProfilingEnabled = enabled;
+
+ if (mRenderProfiler != null) {
+ mChoreographer.removeFrameCallback(mRenderProfiler);
+ }
+ if (mRenderProfilingEnabled) {
+ if (mRenderProfiler == null) {
+ mRenderProfiler = new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ mDirty.set(0, 0, mWidth, mHeight);
+ scheduleTraversals();
+ if (mRenderProfilingEnabled) {
+ mChoreographer.postFrameCallback(mRenderProfiler);
+ }
+ }
+ };
+ }
+ mChoreographer.postFrameCallback(mRenderProfiler);
+ } else {
+ mRenderProfiler = null;
+ }
+ }
+ }
+
+ /**
+ * Called from draw() when DEBUG_FPS is enabled
+ */
+ private void trackFPS() {
+ // Tracks frames per second drawn. First value in a series of draws may be bogus
+ // because it down not account for the intervening idle time
+ long nowTime = System.currentTimeMillis();
+ if (mFpsStartTime < 0) {
+ mFpsStartTime = mFpsPrevTime = nowTime;
+ mFpsNumFrames = 0;
+ } else {
+ ++mFpsNumFrames;
+ String thisHash = Integer.toHexString(System.identityHashCode(this));
+ long frameTime = nowTime - mFpsPrevTime;
+ long totalTime = nowTime - mFpsStartTime;
+ Log.v(mTag, "0x" + thisHash + "\tFrame time:\t" + frameTime);
+ mFpsPrevTime = nowTime;
+ if (totalTime > 1000) {
+ float fps = (float) mFpsNumFrames * 1000 / totalTime;
+ Log.v(mTag, "0x" + thisHash + "\tFPS:\t" + fps);
+ mFpsStartTime = nowTime;
+ mFpsNumFrames = 0;
+ }
+ }
+ }
+
+ /**
+ * A count of the number of calls to pendingDrawFinished we
+ * require to notify the WM drawing is complete.
+ */
+ int mDrawsNeededToReport = 0;
+
+ /**
+ * Delay notifying WM of draw finished until
+ * a balanced call to pendingDrawFinished.
+ */
+ void drawPending() {
+ mDrawsNeededToReport++;
+ }
+
+ void pendingDrawFinished() {
+ if (mDrawsNeededToReport == 0) {
+ throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls");
+ }
+ mDrawsNeededToReport--;
+ if (mDrawsNeededToReport == 0) {
+ reportDrawFinished();
+ }
+ }
+
+ private void postDrawFinished() {
+ mHandler.sendEmptyMessage(MSG_DRAW_FINISHED);
+ }
+
+ private void reportDrawFinished() {
+ try {
+ mDrawsNeededToReport = 0;
+ mWindowSession.finishDrawing(mWindow);
+ } catch (RemoteException e) {
+ // Have fun!
+ }
+ }
+
+ private void performDraw() {
+ if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
+ return;
+ } else if (mView == null) {
+ return;
+ }
+
+ final boolean fullRedrawNeeded = mFullRedrawNeeded;
+ mFullRedrawNeeded = false;
+
+ mIsDrawing = true;
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
+ try {
+ draw(fullRedrawNeeded);
+ } finally {
+ mIsDrawing = false;
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ // For whatever reason we didn't create a HardwareRenderer, end any
+ // hardware animations that are now dangling
+ if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
+ final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
+ for (int i = 0; i < count; i++) {
+ mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
+ }
+ mAttachInfo.mPendingAnimatingRenderNodes.clear();
+ }
+
+ if (mReportNextDraw) {
+ mReportNextDraw = false;
+
+ // if we're using multi-thread renderer, wait for the window frame draws
+ if (mWindowDrawCountDown != null) {
+ try {
+ mWindowDrawCountDown.await();
+ } catch (InterruptedException e) {
+ Log.e(mTag, "Window redraw count down interruped!");
+ }
+ mWindowDrawCountDown = null;
+ }
+
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.fence();
+ mAttachInfo.mThreadedRenderer.setStopped(mStopped);
+ }
+
+ if (LOCAL_LOGV) {
+ Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
+ }
+
+ if (mSurfaceHolder != null && mSurface.isValid()) {
+ SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+
+ sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+ } else {
+ pendingDrawFinished();
+ }
+ }
+ }
+
+ private void draw(boolean fullRedrawNeeded) {
+ Surface surface = mSurface;
+ if (!surface.isValid()) {
+ return;
+ }
+
+ if (DEBUG_FPS) {
+ trackFPS();
+ }
+
+ if (!sFirstDrawComplete) {
+ synchronized (sFirstDrawHandlers) {
+ sFirstDrawComplete = true;
+ final int count = sFirstDrawHandlers.size();
+ for (int i = 0; i< count; i++) {
+ mHandler.post(sFirstDrawHandlers.get(i));
+ }
+ }
+ }
+
+ scrollToRectOrFocus(null, false);
+
+ if (mAttachInfo.mViewScrollChanged) {
+ mAttachInfo.mViewScrollChanged = false;
+ mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
+ }
+
+ boolean animating = mScroller != null && mScroller.computeScrollOffset();
+ final int curScrollY;
+ if (animating) {
+ curScrollY = mScroller.getCurrY();
+ } else {
+ curScrollY = mScrollY;
+ }
+ if (mCurScrollY != curScrollY) {
+ mCurScrollY = curScrollY;
+ fullRedrawNeeded = true;
+ if (mView instanceof RootViewSurfaceTaker) {
+ ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
+ }
+ }
+
+ final float appScale = mAttachInfo.mApplicationScale;
+ final boolean scalingRequired = mAttachInfo.mScalingRequired;
+
+ int resizeAlpha = 0;
+
+ final Rect dirty = mDirty;
+ if (mSurfaceHolder != null) {
+ // The app owns the surface, we won't draw.
+ dirty.setEmpty();
+ if (animating && mScroller != null) {
+ mScroller.abortAnimation();
+ }
+ return;
+ }
+
+ if (fullRedrawNeeded) {
+ mAttachInfo.mIgnoreDirtyState = true;
+ dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
+ }
+
+ if (DEBUG_ORIENTATION || DEBUG_DRAW) {
+ Log.v(mTag, "Draw " + mView + "/"
+ + mWindowAttributes.getTitle()
+ + ": dirty={" + dirty.left + "," + dirty.top
+ + "," + dirty.right + "," + dirty.bottom + "} surface="
+ + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
+ appScale + ", width=" + mWidth + ", height=" + mHeight);
+ }
+
+ mAttachInfo.mTreeObserver.dispatchOnDraw();
+
+ int xOffset = -mCanvasOffsetX;
+ int yOffset = -mCanvasOffsetY + curScrollY;
+ final WindowManager.LayoutParams params = mWindowAttributes;
+ final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
+ if (surfaceInsets != null) {
+ xOffset -= surfaceInsets.left;
+ yOffset -= surfaceInsets.top;
+
+ // Offset dirty rect for surface insets.
+ dirty.offset(surfaceInsets.left, surfaceInsets.right);
+ }
+
+ boolean accessibilityFocusDirty = false;
+ final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
+ if (drawable != null) {
+ final Rect bounds = mAttachInfo.mTmpInvalRect;
+ final boolean hasFocus = getAccessibilityFocusedRect(bounds);
+ if (!hasFocus) {
+ bounds.setEmpty();
+ }
+ if (!bounds.equals(drawable.getBounds())) {
+ accessibilityFocusDirty = true;
+ }
+ }
+
+ mAttachInfo.mDrawingTime =
+ mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
+
+ if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
+ if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
+ // If accessibility focus moved, always invalidate the root.
+ boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
+ mInvalidateRootRequested = false;
+
+ // Draw with hardware renderer.
+ mIsAnimating = false;
+
+ if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
+ mHardwareYOffset = yOffset;
+ mHardwareXOffset = xOffset;
+ invalidateRoot = true;
+ }
+
+ if (invalidateRoot) {
+ mAttachInfo.mThreadedRenderer.invalidateRoot();
+ }
+
+ dirty.setEmpty();
+
+ // Stage the content drawn size now. It will be transferred to the renderer
+ // shortly before the draw commands get send to the renderer.
+ final boolean updated = updateContentDrawBounds();
+
+ if (mReportNextDraw) {
+ // report next draw overrides setStopped()
+ // This value is re-sync'd to the value of mStopped
+ // in the handling of mReportNextDraw post-draw.
+ mAttachInfo.mThreadedRenderer.setStopped(false);
+ }
+
+ if (updated) {
+ requestDrawWindow();
+ }
+
+ mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
+ } else {
+ // If we get here with a disabled & requested hardware renderer, something went
+ // wrong (an invalidate posted right before we destroyed the hardware surface
+ // for instance) so we should just bail out. Locking the surface with software
+ // rendering at this point would lock it forever and prevent hardware renderer
+ // from doing its job when it comes back.
+ // Before we request a new frame we must however attempt to reinitiliaze the
+ // hardware renderer if it's in requested state. This would happen after an
+ // eglTerminate() for instance.
+ if (mAttachInfo.mThreadedRenderer != null &&
+ !mAttachInfo.mThreadedRenderer.isEnabled() &&
+ mAttachInfo.mThreadedRenderer.isRequested()) {
+
+ try {
+ mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+ mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+ } catch (OutOfResourcesException e) {
+ handleOutOfResourcesException(e);
+ return;
+ }
+
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ return;
+ }
+
+ if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
+ return;
+ }
+ }
+ }
+
+ if (animating) {
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ }
+ }
+
+ /**
+ * @return true if drawing was successful, false if an error occurred
+ */
+ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
+ boolean scalingRequired, Rect dirty) {
+
+ // Draw with software renderer.
+ final Canvas canvas;
+ try {
+ final int left = dirty.left;
+ final int top = dirty.top;
+ final int right = dirty.right;
+ final int bottom = dirty.bottom;
+
+ canvas = mSurface.lockCanvas(dirty);
+
+ // The dirty rectangle can be modified by Surface.lockCanvas()
+ //noinspection ConstantConditions
+ if (left != dirty.left || top != dirty.top || right != dirty.right
+ || bottom != dirty.bottom) {
+ attachInfo.mIgnoreDirtyState = true;
+ }
+
+ // TODO: Do this in native
+ canvas.setDensity(mDensity);
+ } catch (Surface.OutOfResourcesException e) {
+ handleOutOfResourcesException(e);
+ return false;
+ } catch (IllegalArgumentException e) {
+ Log.e(mTag, "Could not lock surface", e);
+ // Don't assume this is due to out of memory, it could be
+ // something else, and if it is something else then we could
+ // kill stuff (or ourself) for no reason.
+ mLayoutRequested = true; // ask wm for a new surface next time.
+ return false;
+ }
+
+ try {
+ if (DEBUG_ORIENTATION || DEBUG_DRAW) {
+ Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ + canvas.getWidth() + ", h=" + canvas.getHeight());
+ //canvas.drawARGB(255, 255, 0, 0);
+ }
+
+ // If this bitmap's format includes an alpha channel, we
+ // need to clear it before drawing so that the child will
+ // properly re-composite its drawing on a transparent
+ // background. This automatically respects the clip/dirty region
+ // or
+ // If we are applying an offset, we need to clear the area
+ // where the offset doesn't appear to avoid having garbage
+ // left in the blank areas.
+ if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
+
+ dirty.setEmpty();
+ mIsAnimating = false;
+ mView.mPrivateFlags |= View.PFLAG_DRAWN;
+
+ if (DEBUG_DRAW) {
+ Context cxt = mView.getContext();
+ Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
+ ", metrics=" + cxt.getResources().getDisplayMetrics() +
+ ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
+ }
+ try {
+ canvas.translate(-xoff, -yoff);
+ if (mTranslator != null) {
+ mTranslator.translateCanvas(canvas);
+ }
+ canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
+ attachInfo.mSetIgnoreDirtyState = false;
+
+ mView.draw(canvas);
+
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
+ } finally {
+ if (!attachInfo.mSetIgnoreDirtyState) {
+ // Only clear the flag if it was not set during the mView.draw() call
+ attachInfo.mIgnoreDirtyState = false;
+ }
+ }
+ } finally {
+ try {
+ surface.unlockCanvasAndPost(canvas);
+ } catch (IllegalArgumentException e) {
+ Log.e(mTag, "Could not unlock surface", e);
+ mLayoutRequested = true; // ask wm for a new surface next time.
+ //noinspection ReturnInsideFinallyBlock
+ return false;
+ }
+
+ if (LOCAL_LOGV) {
+ Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * We want to draw a highlight around the current accessibility focused.
+ * Since adding a style for all possible view is not a viable option we
+ * have this specialized drawing method.
+ *
+ * Note: We are doing this here to be able to draw the highlight for
+ * virtual views in addition to real ones.
+ *
+ * @param canvas The canvas on which to draw.
+ */
+ private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
+ final Rect bounds = mAttachInfo.mTmpInvalRect;
+ if (getAccessibilityFocusedRect(bounds)) {
+ final Drawable drawable = getAccessibilityFocusedDrawable();
+ if (drawable != null) {
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+ }
+ } else if (mAttachInfo.mAccessibilityFocusDrawable != null) {
+ mAttachInfo.mAccessibilityFocusDrawable.setBounds(0, 0, 0, 0);
+ }
+ }
+
+ private boolean getAccessibilityFocusedRect(Rect bounds) {
+ final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext);
+ if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ final View host = mAccessibilityFocusedHost;
+ if (host == null || host.mAttachInfo == null) {
+ return false;
+ }
+
+ final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+ if (provider == null) {
+ host.getBoundsOnScreen(bounds, true);
+ } else if (mAccessibilityFocusedVirtualView != null) {
+ mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
+ } else {
+ return false;
+ }
+
+ // Transform the rect into window-relative coordinates.
+ final AttachInfo attachInfo = mAttachInfo;
+ bounds.offset(0, attachInfo.mViewRootImpl.mScrollY);
+ bounds.offset(-attachInfo.mWindowLeft, -attachInfo.mWindowTop);
+ if (!bounds.intersect(0, 0, attachInfo.mViewRootImpl.mWidth,
+ attachInfo.mViewRootImpl.mHeight)) {
+ // If no intersection, set bounds to empty.
+ bounds.setEmpty();
+ }
+ return !bounds.isEmpty();
+ }
+
+ private Drawable getAccessibilityFocusedDrawable() {
+ // Lazily load the accessibility focus drawable.
+ if (mAttachInfo.mAccessibilityFocusDrawable == null) {
+ final TypedValue value = new TypedValue();
+ final boolean resolved = mView.mContext.getTheme().resolveAttribute(
+ R.attr.accessibilityFocusedDrawable, value, true);
+ if (resolved) {
+ mAttachInfo.mAccessibilityFocusDrawable =
+ mView.mContext.getDrawable(value.resourceId);
+ }
+ }
+ return mAttachInfo.mAccessibilityFocusDrawable;
+ }
+
+ /**
+ * Requests that the root render node is invalidated next time we perform a draw, such that
+ * {@link WindowCallbacks#onPostDraw} gets called.
+ */
+ public void requestInvalidateRootRenderNode() {
+ mInvalidateRootRequested = true;
+ }
+
+ boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
+ final Rect ci = mAttachInfo.mContentInsets;
+ final Rect vi = mAttachInfo.mVisibleInsets;
+ int scrollY = 0;
+ boolean handled = false;
+
+ if (vi.left > ci.left || vi.top > ci.top
+ || vi.right > ci.right || vi.bottom > ci.bottom) {
+ // We'll assume that we aren't going to change the scroll
+ // offset, since we want to avoid that unless it is actually
+ // going to make the focus visible... otherwise we scroll
+ // all over the place.
+ scrollY = mScrollY;
+ // We can be called for two different situations: during a draw,
+ // to update the scroll position if the focus has changed (in which
+ // case 'rectangle' is null), or in response to a
+ // requestChildRectangleOnScreen() call (in which case 'rectangle'
+ // is non-null and we just want to scroll to whatever that
+ // rectangle is).
+ final View focus = mView.findFocus();
+ if (focus == null) {
+ return false;
+ }
+ View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
+ if (focus != lastScrolledFocus) {
+ // If the focus has changed, then ignore any requests to scroll
+ // to a rectangle; first we want to make sure the entire focus
+ // view is visible.
+ rectangle = null;
+ }
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Eval scroll: focus=" + focus
+ + " rectangle=" + rectangle + " ci=" + ci
+ + " vi=" + vi);
+ if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
+ // Optimization: if the focus hasn't changed since last
+ // time, and no layout has happened, then just leave things
+ // as they are.
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Keeping scroll y="
+ + mScrollY + " vi=" + vi.toShortString());
+ } else {
+ // We need to determine if the currently focused view is
+ // within the visible part of the window and, if not, apply
+ // a pan so it can be seen.
+ mLastScrolledFocus = new WeakReference<View>(focus);
+ mScrollMayChange = false;
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Need to scroll?");
+ // Try to find the rectangle from the focus view.
+ if (focus.getGlobalVisibleRect(mVisRect, null)) {
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Root w="
+ + mView.getWidth() + " h=" + mView.getHeight()
+ + " ci=" + ci.toShortString()
+ + " vi=" + vi.toShortString());
+ if (rectangle == null) {
+ focus.getFocusedRect(mTempRect);
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus " + focus
+ + ": focusRect=" + mTempRect.toShortString());
+ if (mView instanceof ViewGroup) {
+ ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+ focus, mTempRect);
+ }
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+ "Focus in window: focusRect="
+ + mTempRect.toShortString()
+ + " visRect=" + mVisRect.toShortString());
+ } else {
+ mTempRect.set(rectangle);
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+ "Request scroll to rect: "
+ + mTempRect.toShortString()
+ + " visRect=" + mVisRect.toShortString());
+ }
+ if (mTempRect.intersect(mVisRect)) {
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+ "Focus window visible rect: "
+ + mTempRect.toShortString());
+ if (mTempRect.height() >
+ (mView.getHeight()-vi.top-vi.bottom)) {
+ // If the focus simply is not going to fit, then
+ // best is probably just to leave things as-is.
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+ "Too tall; leaving scrollY=" + scrollY);
+ }
+ // Next, check whether top or bottom is covered based on the non-scrolled
+ // position, and calculate new scrollY (or set it to 0).
+ // We can't keep using mScrollY here. For example mScrollY is non-zero
+ // due to IME, then IME goes away. The current value of mScrollY leaves top
+ // and bottom both visible, but we still need to scroll it back to 0.
+ else if (mTempRect.top < vi.top) {
+ scrollY = mTempRect.top - vi.top;
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+ "Top covered; scrollY=" + scrollY);
+ } else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {
+ scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag,
+ "Bottom covered; scrollY=" + scrollY);
+ } else {
+ scrollY = 0;
+ }
+ handled = true;
+ }
+ }
+ }
+ }
+
+ if (scrollY != mScrollY) {
+ if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Pan scroll changed: old="
+ + mScrollY + " , new=" + scrollY);
+ if (!immediate) {
+ if (mScroller == null) {
+ mScroller = new Scroller(mView.getContext());
+ }
+ mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
+ } else if (mScroller != null) {
+ mScroller.abortAnimation();
+ }
+ mScrollY = scrollY;
+ }
+
+ return handled;
+ }
+
+ /**
+ * @hide
+ */
+ public View getAccessibilityFocusedHost() {
+ return mAccessibilityFocusedHost;
+ }
+
+ /**
+ * @hide
+ */
+ public AccessibilityNodeInfo getAccessibilityFocusedVirtualView() {
+ return mAccessibilityFocusedVirtualView;
+ }
+
+ void setAccessibilityFocus(View view, AccessibilityNodeInfo node) {
+ // If we have a virtual view with accessibility focus we need
+ // to clear the focus and invalidate the virtual view bounds.
+ if (mAccessibilityFocusedVirtualView != null) {
+
+ AccessibilityNodeInfo focusNode = mAccessibilityFocusedVirtualView;
+ View focusHost = mAccessibilityFocusedHost;
+
+ // Wipe the state of the current accessibility focus since
+ // the call into the provider to clear accessibility focus
+ // will fire an accessibility event which will end up calling
+ // this method and we want to have clean state when this
+ // invocation happens.
+ mAccessibilityFocusedHost = null;
+ mAccessibilityFocusedVirtualView = null;
+
+ // Clear accessibility focus on the host after clearing state since
+ // this method may be reentrant.
+ focusHost.clearAccessibilityFocusNoCallbacks(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+
+ AccessibilityNodeProvider provider = focusHost.getAccessibilityNodeProvider();
+ if (provider != null) {
+ // Invalidate the area of the cleared accessibility focus.
+ focusNode.getBoundsInParent(mTempRect);
+ focusHost.invalidate(mTempRect);
+ // Clear accessibility focus in the virtual node.
+ final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+ focusNode.getSourceNodeId());
+ provider.performAction(virtualNodeId,
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+ }
+ focusNode.recycle();
+ }
+ if ((mAccessibilityFocusedHost != null) && (mAccessibilityFocusedHost != view)) {
+ // Clear accessibility focus in the view.
+ mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ }
+
+ // Set the new focus host and node.
+ mAccessibilityFocusedHost = view;
+ mAccessibilityFocusedVirtualView = node;
+
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.invalidateRoot();
+ }
+ }
+
+ boolean hasPointerCapture() {
+ return mPointerCapture;
+ }
+
+ void requestPointerCapture(boolean enabled) {
+ if (mPointerCapture == enabled) {
+ return;
+ }
+ InputManager.getInstance().requestPointerCapture(mAttachInfo.mWindowToken, enabled);
+ }
+
+ private void handlePointerCaptureChanged(boolean hasCapture) {
+ if (mPointerCapture == hasCapture) {
+ return;
+ }
+ mPointerCapture = hasCapture;
+ if (mView != null) {
+ mView.dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "Request child focus: focus now " + focused);
+ }
+ checkThread();
+ scheduleTraversals();
+ }
+
+ @Override
+ public void clearChildFocus(View child) {
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "Clearing child focus");
+ }
+ checkThread();
+ scheduleTraversals();
+ }
+
+ @Override
+ public ViewParent getParentForAccessibility() {
+ return null;
+ }
+
+ @Override
+ public void focusableViewAvailable(View v) {
+ checkThread();
+ if (mView != null) {
+ if (!mView.hasFocus()) {
+ if (sAlwaysAssignFocus) {
+ v.requestFocus();
+ }
+ } else {
+ // the one case where will transfer focus away from the current one
+ // is if the current view is a view group that prefers to give focus
+ // to its children first AND the view is a descendant of it.
+ View focused = mView.findFocus();
+ if (focused instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) focused;
+ if (group.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+ && isViewDescendantOf(v, focused)) {
+ v.requestFocus();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void recomputeViewAttributes(View child) {
+ checkThread();
+ if (mView == child) {
+ mAttachInfo.mRecomputeGlobalAttributes = true;
+ if (!mWillDrawSoon) {
+ scheduleTraversals();
+ }
+ }
+ }
+
+ void dispatchDetachedFromWindow() {
+ if (mView != null && mView.mAttachInfo != null) {
+ mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
+ mView.dispatchDetachedFromWindow();
+ }
+
+ mAccessibilityInteractionConnectionManager.ensureNoConnection();
+ mAccessibilityManager.removeAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager);
+ mAccessibilityManager.removeHighTextContrastStateChangeListener(
+ mHighContrastTextManager);
+ removeSendWindowContentChangedCallback();
+
+ destroyHardwareRenderer();
+
+ setAccessibilityFocus(null, null);
+
+ mView.assignParent(null);
+ mView = null;
+ mAttachInfo.mRootView = null;
+
+ mSurface.release();
+
+ if (mInputQueueCallback != null && mInputQueue != null) {
+ mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
+ mInputQueue.dispose();
+ mInputQueueCallback = null;
+ mInputQueue = null;
+ }
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ try {
+ mWindowSession.remove(mWindow);
+ } catch (RemoteException e) {
+ }
+
+ // Dispose the input channel after removing the window so the Window Manager
+ // doesn't interpret the input channel being closed as an abnormal termination.
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
+
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+
+ unscheduleTraversals();
+ }
+
+ /**
+ * Notifies all callbacks that configuration and/or display has changed and updates internal
+ * state.
+ * @param mergedConfiguration New global and override config in {@link MergedConfiguration}
+ * container.
+ * @param force Flag indicating if we should force apply the config.
+ * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} if not
+ * changed.
+ */
+ private void performConfigurationChange(MergedConfiguration mergedConfiguration, boolean force,
+ int newDisplayId) {
+ if (mergedConfiguration == null) {
+ throw new IllegalArgumentException("No merged config provided.");
+ }
+
+ Configuration globalConfig = mergedConfiguration.getGlobalConfiguration();
+ final Configuration overrideConfig = mergedConfiguration.getOverrideConfiguration();
+ if (DEBUG_CONFIGURATION) Log.v(mTag,
+ "Applying new config to window " + mWindowAttributes.getTitle()
+ + ", globalConfig: " + globalConfig
+ + ", overrideConfig: " + overrideConfig);
+
+ final CompatibilityInfo ci = mDisplay.getDisplayAdjustments().getCompatibilityInfo();
+ if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
+ globalConfig = new Configuration(globalConfig);
+ ci.applyToConfiguration(mNoncompatDensity, globalConfig);
+ }
+
+ synchronized (sConfigCallbacks) {
+ for (int i=sConfigCallbacks.size()-1; i>=0; i--) {
+ sConfigCallbacks.get(i).onConfigurationChanged(globalConfig);
+ }
+ }
+
+ mLastReportedMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
+
+ mForceNextConfigUpdate = force;
+ if (mActivityConfigCallback != null) {
+ // An activity callback is set - notify it about override configuration update.
+ // This basically initiates a round trip to ActivityThread and back, which will ensure
+ // that corresponding activity and resources are updated before updating inner state of
+ // ViewRootImpl. Eventually it will call #updateConfiguration().
+ mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId);
+ } else {
+ // There is no activity callback - update the configuration right away.
+ updateConfiguration(newDisplayId);
+ }
+ mForceNextConfigUpdate = false;
+ }
+
+ /**
+ * Update display and views if last applied merged configuration changed.
+ * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
+ */
+ public void updateConfiguration(int newDisplayId) {
+ if (mView == null) {
+ return;
+ }
+
+ // At this point the resources have been updated to
+ // have the most recent config, whatever that is. Use
+ // the one in them which may be newer.
+ final Resources localResources = mView.getResources();
+ final Configuration config = localResources.getConfiguration();
+
+ // Handle move to display.
+ if (newDisplayId != INVALID_DISPLAY) {
+ onMovedToDisplay(newDisplayId, config);
+ }
+
+ // Handle configuration change.
+ if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) {
+ // Update the display with new DisplayAdjustments.
+ mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(
+ mDisplay.getDisplayId(), localResources);
+
+ final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();
+ final int currentLayoutDirection = config.getLayoutDirection();
+ mLastConfigurationFromResources.setTo(config);
+ if (lastLayoutDirection != currentLayoutDirection
+ && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
+ mView.setLayoutDirection(currentLayoutDirection);
+ }
+ mView.dispatchConfigurationChanged(config);
+
+ // We could have gotten this {@link Configuration} update after we called
+ // {@link #performTraversals} with an older {@link Configuration}. As a result, our
+ // window frame may be stale. We must ensure the next pass of {@link #performTraversals}
+ // catches this.
+ mForceNextWindowRelayout = true;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Return true if child is an ancestor of parent, (or equal to the parent).
+ */
+ public static boolean isViewDescendantOf(View child, View parent) {
+ if (child == parent) {
+ return true;
+ }
+
+ final ViewParent theParent = child.getParent();
+ return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+ }
+
+ private static void forceLayout(View view) {
+ view.forceLayout();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ forceLayout(group.getChildAt(i));
+ }
+ }
+ }
+
+ 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_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;
+
+ final class ViewRootHandler extends Handler {
+ @Override
+ public String getMessageName(Message message) {
+ switch (message.what) {
+ case MSG_INVALIDATE:
+ return "MSG_INVALIDATE";
+ case MSG_INVALIDATE_RECT:
+ return "MSG_INVALIDATE_RECT";
+ case MSG_DIE:
+ return "MSG_DIE";
+ case MSG_RESIZED:
+ return "MSG_RESIZED";
+ case MSG_RESIZED_REPORT:
+ return "MSG_RESIZED_REPORT";
+ case MSG_WINDOW_FOCUS_CHANGED:
+ return "MSG_WINDOW_FOCUS_CHANGED";
+ case MSG_DISPATCH_INPUT_EVENT:
+ return "MSG_DISPATCH_INPUT_EVENT";
+ case MSG_DISPATCH_APP_VISIBILITY:
+ return "MSG_DISPATCH_APP_VISIBILITY";
+ case MSG_DISPATCH_GET_NEW_SURFACE:
+ return "MSG_DISPATCH_GET_NEW_SURFACE";
+ case MSG_DISPATCH_KEY_FROM_IME:
+ return "MSG_DISPATCH_KEY_FROM_IME";
+ case MSG_CHECK_FOCUS:
+ return "MSG_CHECK_FOCUS";
+ case MSG_CLOSE_SYSTEM_DIALOGS:
+ return "MSG_CLOSE_SYSTEM_DIALOGS";
+ case MSG_DISPATCH_DRAG_EVENT:
+ return "MSG_DISPATCH_DRAG_EVENT";
+ case MSG_DISPATCH_DRAG_LOCATION_EVENT:
+ return "MSG_DISPATCH_DRAG_LOCATION_EVENT";
+ case MSG_DISPATCH_SYSTEM_UI_VISIBILITY:
+ return "MSG_DISPATCH_SYSTEM_UI_VISIBILITY";
+ case MSG_UPDATE_CONFIGURATION:
+ return "MSG_UPDATE_CONFIGURATION";
+ case MSG_PROCESS_INPUT_EVENTS:
+ return "MSG_PROCESS_INPUT_EVENTS";
+ case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST:
+ return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
+ case MSG_WINDOW_MOVED:
+ return "MSG_WINDOW_MOVED";
+ case MSG_SYNTHESIZE_INPUT_EVENT:
+ return "MSG_SYNTHESIZE_INPUT_EVENT";
+ case MSG_DISPATCH_WINDOW_SHOWN:
+ return "MSG_DISPATCH_WINDOW_SHOWN";
+ case MSG_UPDATE_POINTER_ICON:
+ return "MSG_UPDATE_POINTER_ICON";
+ case MSG_POINTER_CAPTURE_CHANGED:
+ return "MSG_POINTER_CAPTURE_CHANGED";
+ case MSG_DRAW_FINISHED:
+ return "MSG_DRAW_FINISHED";
+ }
+ return super.getMessageName(message);
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ if (msg.what == MSG_REQUEST_KEYBOARD_SHORTCUTS && msg.obj == null) {
+ // Debugging for b/27963013
+ throw new NullPointerException(
+ "Attempted to call MSG_REQUEST_KEYBOARD_SHORTCUTS with null receiver:");
+ }
+ return super.sendMessageAtTime(msg, uptimeMillis);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INVALIDATE:
+ ((View) msg.obj).invalidate();
+ break;
+ case MSG_INVALIDATE_RECT:
+ final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
+ info.target.invalidate(info.left, info.top, info.right, info.bottom);
+ info.recycle();
+ break;
+ case MSG_PROCESS_INPUT_EVENTS:
+ mProcessInputEventsScheduled = false;
+ doProcessInputEvents();
+ break;
+ case MSG_DISPATCH_APP_VISIBILITY:
+ handleAppVisibility(msg.arg1 != 0);
+ break;
+ case MSG_DISPATCH_GET_NEW_SURFACE:
+ handleGetNewSurface();
+ break;
+ case MSG_RESIZED: {
+ // Recycled in the fall through...
+ SomeArgs args = (SomeArgs) msg.obj;
+ if (mWinFrame.equals(args.arg1)
+ && mPendingOverscanInsets.equals(args.arg5)
+ && mPendingContentInsets.equals(args.arg2)
+ && mPendingStableInsets.equals(args.arg6)
+ && mPendingVisibleInsets.equals(args.arg3)
+ && mPendingOutsets.equals(args.arg7)
+ && mPendingBackDropFrame.equals(args.arg8)
+ && args.arg4 == null
+ && args.argi1 == 0
+ && mDisplay.getDisplayId() == args.argi3) {
+ break;
+ }
+ } // fall through...
+ case MSG_RESIZED_REPORT:
+ if (mAdded) {
+ SomeArgs args = (SomeArgs) msg.obj;
+
+ final int displayId = args.argi3;
+ MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
+ final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+
+ if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
+ // If configuration changed - notify about that and, maybe, about move to
+ // display.
+ performConfigurationChange(mergedConfiguration, false /* force */,
+ displayChanged ? displayId : INVALID_DISPLAY /* same display */);
+ } else if (displayChanged) {
+ // Moved to display without config change - report last applied one.
+ onMovedToDisplay(displayId, mLastConfigurationFromResources);
+ }
+
+ final boolean framesChanged = !mWinFrame.equals(args.arg1)
+ || !mPendingOverscanInsets.equals(args.arg5)
+ || !mPendingContentInsets.equals(args.arg2)
+ || !mPendingStableInsets.equals(args.arg6)
+ || !mPendingVisibleInsets.equals(args.arg3)
+ || !mPendingOutsets.equals(args.arg7);
+
+ mWinFrame.set((Rect) args.arg1);
+ mPendingOverscanInsets.set((Rect) args.arg5);
+ mPendingContentInsets.set((Rect) args.arg2);
+ mPendingStableInsets.set((Rect) args.arg6);
+ mPendingVisibleInsets.set((Rect) args.arg3);
+ mPendingOutsets.set((Rect) args.arg7);
+ mPendingBackDropFrame.set((Rect) args.arg8);
+ mForceNextWindowRelayout = args.argi1 != 0;
+ mPendingAlwaysConsumeNavBar = args.argi2 != 0;
+
+ args.recycle();
+
+ if (msg.what == MSG_RESIZED_REPORT) {
+ reportNextDraw();
+ }
+
+ if (mView != null && framesChanged) {
+ forceLayout(mView);
+ }
+ requestLayout();
+ }
+ break;
+ case MSG_WINDOW_MOVED:
+ if (mAdded) {
+ final int w = mWinFrame.width();
+ final int h = mWinFrame.height();
+ final int l = msg.arg1;
+ final int t = msg.arg2;
+ mWinFrame.left = l;
+ mWinFrame.right = l + w;
+ mWinFrame.top = t;
+ mWinFrame.bottom = t + h;
+
+ mPendingBackDropFrame.set(mWinFrame);
+ maybeHandleWindowMove(mWinFrame);
+ }
+ break;
+ case MSG_WINDOW_FOCUS_CHANGED: {
+ if (mAdded) {
+ boolean hasWindowFocus = msg.arg1 != 0;
+ mAttachInfo.mHasWindowFocus = hasWindowFocus;
+
+ profileRendering(hasWindowFocus);
+
+ if (hasWindowFocus) {
+ boolean inTouchMode = msg.arg2 != 0;
+ ensureTouchModeLocally(inTouchMode);
+
+ if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()){
+ mFullRedrawNeeded = true;
+ try {
+ final WindowManager.LayoutParams lp = mWindowAttributes;
+ final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
+ mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+ mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+ } catch (OutOfResourcesException e) {
+ Log.e(mTag, "OutOfResourcesException locking surface", e);
+ try {
+ if (!mWindowSession.outOfMemory(mWindow)) {
+ Slog.w(mTag, "No processes killed for memory; killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
+ }
+ // Retry in a bit.
+ sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
+ return;
+ }
+ }
+ }
+
+ mLastWasImTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+ imm.onPreWindowFocus(mView, hasWindowFocus);
+ }
+ if (mView != null) {
+ mAttachInfo.mKeyDispatchState.reset();
+ mView.dispatchWindowFocusChanged(hasWindowFocus);
+ mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+
+ if (mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.hideTooltip();
+ }
+ }
+
+ // Note: must be done after the focus change callbacks,
+ // so all of the view state is set up correctly.
+ if (hasWindowFocus) {
+ if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+ imm.onPostWindowFocus(mView, mView.findFocus(),
+ mWindowAttributes.softInputMode,
+ !mHasHadWindowFocus, mWindowAttributes.flags);
+ }
+ // Clear the forward bit. We can just do this directly, since
+ // the window manager doesn't care about it.
+ mWindowAttributes.softInputMode &=
+ ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ ((WindowManager.LayoutParams)mView.getLayoutParams())
+ .softInputMode &=
+ ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ mHasHadWindowFocus = true;
+ } else {
+ if (mPointerCapture) {
+ handlePointerCaptureChanged(false);
+ }
+ }
+ }
+ } break;
+ case MSG_DIE:
+ doDie();
+ break;
+ case MSG_DISPATCH_INPUT_EVENT: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ InputEvent event = (InputEvent)args.arg1;
+ InputEventReceiver receiver = (InputEventReceiver)args.arg2;
+ enqueueInputEvent(event, receiver, 0, true);
+ args.recycle();
+ } break;
+ case MSG_SYNTHESIZE_INPUT_EVENT: {
+ InputEvent event = (InputEvent)msg.obj;
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
+ } break;
+ case MSG_DISPATCH_KEY_FROM_IME: {
+ if (LOCAL_LOGV) Log.v(
+ TAG, "Dispatching key "
+ + msg.obj + " from IME to " + mView);
+ KeyEvent event = (KeyEvent)msg.obj;
+ if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
+ // The IME is trying to say this event is from the
+ // system! Bad bad bad!
+ //noinspection UnusedAssignment
+ event = KeyEvent.changeFlags(event, event.getFlags() &
+ ~KeyEvent.FLAG_FROM_SYSTEM);
+ }
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
+ } break;
+ case MSG_CHECK_FOCUS: {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.checkFocus();
+ }
+ } break;
+ case MSG_CLOSE_SYSTEM_DIALOGS: {
+ if (mView != null) {
+ mView.onCloseSystemDialogs((String)msg.obj);
+ }
+ } break;
+ case MSG_DISPATCH_DRAG_EVENT:
+ case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
+ DragEvent event = (DragEvent)msg.obj;
+ event.mLocalState = mLocalDragState; // only present when this app called startDrag()
+ handleDragEvent(event);
+ } break;
+ case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
+ handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+ } break;
+ case MSG_UPDATE_CONFIGURATION: {
+ Configuration config = (Configuration) msg.obj;
+ if (config.isOtherSeqNewer(
+ mLastReportedMergedConfiguration.getMergedConfiguration())) {
+ // If we already have a newer merged config applied - use its global part.
+ config = mLastReportedMergedConfiguration.getGlobalConfiguration();
+ }
+
+ // Use the newer global config and last reported override config.
+ mPendingMergedConfiguration.setConfiguration(config,
+ mLastReportedMergedConfiguration.getOverrideConfiguration());
+
+ performConfigurationChange(mPendingMergedConfiguration, false /* force */,
+ INVALID_DISPLAY /* same display */);
+ } break;
+ case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
+ setAccessibilityFocus(null, null);
+ } break;
+ case MSG_INVALIDATE_WORLD: {
+ if (mView != null) {
+ invalidateWorld(mView);
+ }
+ } break;
+ case MSG_DISPATCH_WINDOW_SHOWN: {
+ handleDispatchWindowShown();
+ } break;
+ case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
+ final IResultReceiver receiver = (IResultReceiver) msg.obj;
+ final int deviceId = msg.arg1;
+ handleRequestKeyboardShortcuts(receiver, deviceId);
+ } break;
+ case MSG_UPDATE_POINTER_ICON: {
+ MotionEvent event = (MotionEvent) msg.obj;
+ resetPointerIcon(event);
+ } break;
+ case MSG_POINTER_CAPTURE_CHANGED: {
+ final boolean hasCapture = msg.arg1 != 0;
+ handlePointerCaptureChanged(hasCapture);
+ } break;
+ case MSG_DRAW_FINISHED: {
+ pendingDrawFinished();
+ } break;
+ }
+ }
+ }
+
+ final ViewRootHandler mHandler = new ViewRootHandler();
+
+ /**
+ * Something in the current window tells us we need to change the touch mode. For
+ * example, we are not in touch mode, and the user touches the screen.
+ *
+ * If the touch mode has changed, tell the window manager, and handle it locally.
+ *
+ * @param inTouchMode Whether we want to be in touch mode.
+ * @return True if the touch mode changed and focus changed was changed as a result
+ */
+ boolean ensureTouchMode(boolean inTouchMode) {
+ if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current "
+ + "touch mode is " + mAttachInfo.mInTouchMode);
+ if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+
+ // tell the window manager
+ try {
+ mWindowSession.setInTouchMode(inTouchMode);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ // handle the change
+ return ensureTouchModeLocally(inTouchMode);
+ }
+
+ /**
+ * Ensure that the touch mode for this window is set, and if it is changing,
+ * take the appropriate action.
+ * @param inTouchMode Whether we want to be in touch mode.
+ * @return True if the touch mode changed and focus changed was changed as a result
+ */
+ private boolean ensureTouchModeLocally(boolean inTouchMode) {
+ if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current "
+ + "touch mode is " + mAttachInfo.mInTouchMode);
+
+ if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+
+ mAttachInfo.mInTouchMode = inTouchMode;
+ mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
+
+ return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
+ }
+
+ private boolean enterTouchMode() {
+ if (mView != null && mView.hasFocus()) {
+ // note: not relying on mFocusedView here because this could
+ // be when the window is first being added, and mFocused isn't
+ // set yet.
+ final View focused = mView.findFocus();
+ if (focused != null && !focused.isFocusableInTouchMode()) {
+ final ViewGroup ancestorToTakeFocus = findAncestorToTakeFocusInTouchMode(focused);
+ if (ancestorToTakeFocus != null) {
+ // there is an ancestor that wants focus after its
+ // descendants that is focusable in touch mode.. give it
+ // focus
+ return ancestorToTakeFocus.requestFocus();
+ } else {
+ // There's nothing to focus. Clear and propagate through the
+ // hierarchy, but don't attempt to place new focus.
+ focused.clearFocusInternal(null, true, false);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find an ancestor of focused that wants focus after its descendants and is
+ * focusable in touch mode.
+ * @param focused The currently focused view.
+ * @return An appropriate view, or null if no such view exists.
+ */
+ private static ViewGroup findAncestorToTakeFocusInTouchMode(View focused) {
+ ViewParent parent = focused.getParent();
+ while (parent instanceof ViewGroup) {
+ final ViewGroup vgParent = (ViewGroup) parent;
+ if (vgParent.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+ && vgParent.isFocusableInTouchMode()) {
+ return vgParent;
+ }
+ if (vgParent.isRootNamespace()) {
+ return null;
+ } else {
+ parent = vgParent.getParent();
+ }
+ }
+ return null;
+ }
+
+ private boolean leaveTouchMode() {
+ if (mView != null) {
+ if (mView.hasFocus()) {
+ View focusedView = mView.findFocus();
+ if (!(focusedView instanceof ViewGroup)) {
+ // some view has focus, let it keep it
+ return false;
+ } else if (((ViewGroup) focusedView).getDescendantFocusability() !=
+ ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+ // some view group has focus, and doesn't prefer its children
+ // over itself for focus, so let them keep it.
+ return false;
+ }
+ }
+
+ // find the best view to give focus to in this brave new non-touch-mode
+ // world
+ final View focused = focusSearch(null, View.FOCUS_DOWN);
+ if (focused != null) {
+ return focused.requestFocus(View.FOCUS_DOWN);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Base class for implementing a stage in the chain of responsibility
+ * for processing input events.
+ * <p>
+ * Events are delivered to the stage by the {@link #deliver} method. The stage
+ * then has the choice of finishing the event or forwarding it to the next stage.
+ * </p>
+ */
+ abstract class InputStage {
+ private final InputStage mNext;
+
+ protected static final int FORWARD = 0;
+ protected static final int FINISH_HANDLED = 1;
+ protected static final int FINISH_NOT_HANDLED = 2;
+
+ /**
+ * Creates an input stage.
+ * @param next The next stage to which events should be forwarded.
+ */
+ public InputStage(InputStage next) {
+ mNext = next;
+ }
+
+ /**
+ * Delivers an event to be processed.
+ */
+ public final void deliver(QueuedInputEvent q) {
+ if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
+ forward(q);
+ } else if (shouldDropInputEvent(q)) {
+ finish(q, false);
+ } else {
+ apply(q, onProcess(q));
+ }
+ }
+
+ /**
+ * Marks the the input event as finished then forwards it to the next stage.
+ */
+ protected void finish(QueuedInputEvent q, boolean handled) {
+ q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
+ if (handled) {
+ q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
+ }
+ forward(q);
+ }
+
+ /**
+ * Forwards the event to the next stage.
+ */
+ protected void forward(QueuedInputEvent q) {
+ onDeliverToNext(q);
+ }
+
+ /**
+ * Applies a result code from {@link #onProcess} to the specified event.
+ */
+ protected void apply(QueuedInputEvent q, int result) {
+ if (result == FORWARD) {
+ forward(q);
+ } else if (result == FINISH_HANDLED) {
+ finish(q, true);
+ } else if (result == FINISH_NOT_HANDLED) {
+ finish(q, false);
+ } else {
+ throw new IllegalArgumentException("Invalid result: " + result);
+ }
+ }
+
+ /**
+ * Called when an event is ready to be processed.
+ * @return A result code indicating how the event was handled.
+ */
+ protected int onProcess(QueuedInputEvent q) {
+ return FORWARD;
+ }
+
+ /**
+ * Called when an event is being delivered to the next stage.
+ */
+ protected void onDeliverToNext(QueuedInputEvent q) {
+ if (DEBUG_INPUT_STAGES) {
+ Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
+ }
+ if (mNext != null) {
+ mNext.deliver(q);
+ } else {
+ finishInputEvent(q);
+ }
+ }
+
+ protected boolean shouldDropInputEvent(QueuedInputEvent q) {
+ if (mView == null || !mAdded) {
+ Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
+ return true;
+ } else if ((!mAttachInfo.mHasWindowFocus
+ && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped
+ || (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
+ || (mPausedForTransition && !isBack(q.mEvent))) {
+ // This is a focus event and the window doesn't currently have input focus or
+ // has stopped. This could be an event that came back from the previous stage
+ // but the window has lost focus or stopped in the meantime.
+ if (isTerminalInputEvent(q.mEvent)) {
+ // Don't drop terminal input events, however mark them as canceled.
+ q.mEvent.cancel();
+ Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent);
+ return false;
+ }
+
+ // Drop non-terminal input events.
+ Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent);
+ return true;
+ }
+ return false;
+ }
+
+ void dump(String prefix, PrintWriter writer) {
+ if (mNext != null) {
+ mNext.dump(prefix, writer);
+ }
+ }
+
+ private boolean isBack(InputEvent event) {
+ if (event instanceof KeyEvent) {
+ return ((KeyEvent) event).getKeyCode() == KeyEvent.KEYCODE_BACK;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Base class for implementing an input pipeline stage that supports
+ * asynchronous and out-of-order processing of input events.
+ * <p>
+ * In addition to what a normal input stage can do, an asynchronous
+ * input stage may also defer an input event that has been delivered to it
+ * and finish or forward it later.
+ * </p>
+ */
+ abstract class AsyncInputStage extends InputStage {
+ private final String mTraceCounter;
+
+ private QueuedInputEvent mQueueHead;
+ private QueuedInputEvent mQueueTail;
+ private int mQueueLength;
+
+ protected static final int DEFER = 3;
+
+ /**
+ * Creates an asynchronous input stage.
+ * @param next The next stage to which events should be forwarded.
+ * @param traceCounter The name of a counter to record the size of
+ * the queue of pending events.
+ */
+ public AsyncInputStage(InputStage next, String traceCounter) {
+ super(next);
+ mTraceCounter = traceCounter;
+ }
+
+ /**
+ * Marks the event as deferred, which is to say that it will be handled
+ * asynchronously. The caller is responsible for calling {@link #forward}
+ * or {@link #finish} later when it is done handling the event.
+ */
+ protected void defer(QueuedInputEvent q) {
+ q.mFlags |= QueuedInputEvent.FLAG_DEFERRED;
+ enqueue(q);
+ }
+
+ @Override
+ protected void forward(QueuedInputEvent q) {
+ // Clear the deferred flag.
+ q.mFlags &= ~QueuedInputEvent.FLAG_DEFERRED;
+
+ // Fast path if the queue is empty.
+ QueuedInputEvent curr = mQueueHead;
+ if (curr == null) {
+ super.forward(q);
+ return;
+ }
+
+ // Determine whether the event must be serialized behind any others
+ // before it can be delivered to the next stage. This is done because
+ // deferred events might be handled out of order by the stage.
+ final int deviceId = q.mEvent.getDeviceId();
+ QueuedInputEvent prev = null;
+ boolean blocked = false;
+ while (curr != null && curr != q) {
+ if (!blocked && deviceId == curr.mEvent.getDeviceId()) {
+ blocked = true;
+ }
+ prev = curr;
+ curr = curr.mNext;
+ }
+
+ // If the event is blocked, then leave it in the queue to be delivered later.
+ // Note that the event might not yet be in the queue if it was not previously
+ // deferred so we will enqueue it if needed.
+ if (blocked) {
+ if (curr == null) {
+ enqueue(q);
+ }
+ return;
+ }
+
+ // The event is not blocked. Deliver it immediately.
+ if (curr != null) {
+ curr = curr.mNext;
+ dequeue(q, prev);
+ }
+ super.forward(q);
+
+ // Dequeuing this event may have unblocked successors. Deliver them.
+ while (curr != null) {
+ if (deviceId == curr.mEvent.getDeviceId()) {
+ if ((curr.mFlags & QueuedInputEvent.FLAG_DEFERRED) != 0) {
+ break;
+ }
+ QueuedInputEvent next = curr.mNext;
+ dequeue(curr, prev);
+ super.forward(curr);
+ curr = next;
+ } else {
+ prev = curr;
+ curr = curr.mNext;
+ }
+ }
+ }
+
+ @Override
+ protected void apply(QueuedInputEvent q, int result) {
+ if (result == DEFER) {
+ defer(q);
+ } else {
+ super.apply(q, result);
+ }
+ }
+
+ private void enqueue(QueuedInputEvent q) {
+ if (mQueueTail == null) {
+ mQueueHead = q;
+ mQueueTail = q;
+ } else {
+ mQueueTail.mNext = q;
+ mQueueTail = q;
+ }
+
+ mQueueLength += 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
+ }
+
+ private void dequeue(QueuedInputEvent q, QueuedInputEvent prev) {
+ if (prev == null) {
+ mQueueHead = q.mNext;
+ } else {
+ prev.mNext = q.mNext;
+ }
+ if (mQueueTail == q) {
+ mQueueTail = prev;
+ }
+ q.mNext = null;
+
+ mQueueLength -= 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
+ }
+
+ @Override
+ void dump(String prefix, PrintWriter writer) {
+ writer.print(prefix);
+ writer.print(getClass().getName());
+ writer.print(": mQueueLength=");
+ writer.println(mQueueLength);
+
+ super.dump(prefix, writer);
+ }
+ }
+
+ /**
+ * Delivers pre-ime input events to a native activity.
+ * Does not support pointer events.
+ */
+ final class NativePreImeInputStage extends AsyncInputStage
+ implements InputQueue.FinishedInputEventCallback {
+ public NativePreImeInputStage(InputStage next, String traceCounter) {
+ super(next, traceCounter);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
+ mInputQueue.sendInputEvent(q.mEvent, q, true, this);
+ return DEFER;
+ }
+ return FORWARD;
+ }
+
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ QueuedInputEvent q = (QueuedInputEvent)token;
+ if (handled) {
+ finish(q, true);
+ return;
+ }
+ forward(q);
+ }
+ }
+
+ /**
+ * Delivers pre-ime input events to the view hierarchy.
+ * Does not support pointer events.
+ */
+ final class ViewPreImeInputStage extends InputStage {
+ public ViewPreImeInputStage(InputStage next) {
+ super(next);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ return processKeyEvent(q);
+ }
+ return FORWARD;
+ }
+
+ private int processKeyEvent(QueuedInputEvent q) {
+ final KeyEvent event = (KeyEvent)q.mEvent;
+ if (mView.dispatchKeyEventPreIme(event)) {
+ return FINISH_HANDLED;
+ }
+ return FORWARD;
+ }
+ }
+
+ /**
+ * Delivers input events to the ime.
+ * Does not support pointer events.
+ */
+ final class ImeInputStage extends AsyncInputStage
+ implements InputMethodManager.FinishedInputEventCallback {
+ public ImeInputStage(InputStage next, String traceCounter) {
+ super(next, traceCounter);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (mLastWasImTarget && !isInLocalFocusMode()) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ final InputEvent event = q.mEvent;
+ if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event);
+ int result = imm.dispatchInputEvent(event, q, this, mHandler);
+ if (result == InputMethodManager.DISPATCH_HANDLED) {
+ return FINISH_HANDLED;
+ } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
+ // The IME could not handle it, so skip along to the next InputStage
+ return FORWARD;
+ } else {
+ return DEFER; // callback will be invoked later
+ }
+ }
+ }
+ return FORWARD;
+ }
+
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ QueuedInputEvent q = (QueuedInputEvent)token;
+ if (handled) {
+ finish(q, true);
+ return;
+ }
+ forward(q);
+ }
+ }
+
+ /**
+ * Performs early processing of post-ime input events.
+ */
+ final class EarlyPostImeInputStage extends InputStage {
+ public EarlyPostImeInputStage(InputStage next) {
+ super(next);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ return processKeyEvent(q);
+ } else {
+ final int source = q.mEvent.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ return processPointerEvent(q);
+ }
+ }
+ return FORWARD;
+ }
+
+ private int processKeyEvent(QueuedInputEvent q) {
+ final KeyEvent event = (KeyEvent)q.mEvent;
+
+ if (mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.handleTooltipKey(event);
+ }
+
+ // If the key's purpose is to exit touch mode then we consume it
+ // and consider it handled.
+ if (checkForLeavingTouchModeAndConsume(event)) {
+ return FINISH_HANDLED;
+ }
+
+ // Make sure the fallback event policy sees all keys that will be
+ // delivered to the view hierarchy.
+ mFallbackEventHandler.preDispatchKeyEvent(event);
+ return FORWARD;
+ }
+
+ private int processPointerEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+
+ // Translate the pointer event for compatibility, if needed.
+ if (mTranslator != null) {
+ mTranslator.translateEventInScreenToAppWindow(event);
+ }
+
+ // Enter touch mode on down or scroll, if it is coming from a touch screen device,
+ // exit otherwise.
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
+ ensureTouchMode(event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
+ }
+
+ if (action == MotionEvent.ACTION_DOWN && mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.hideTooltip();
+ }
+
+ // Offset the scroll position.
+ if (mCurScrollY != 0) {
+ event.offsetLocation(0, mCurScrollY);
+ }
+
+ // Remember the touch position for possible drag-initiation.
+ if (event.isTouchEvent()) {
+ mLastTouchPoint.x = event.getRawX();
+ mLastTouchPoint.y = event.getRawY();
+ mLastTouchSource = event.getSource();
+ }
+ return FORWARD;
+ }
+ }
+
+ /**
+ * Delivers post-ime input events to a native activity.
+ */
+ final class NativePostImeInputStage extends AsyncInputStage
+ implements InputQueue.FinishedInputEventCallback {
+ public NativePostImeInputStage(InputStage next, String traceCounter) {
+ super(next, traceCounter);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (mInputQueue != null) {
+ mInputQueue.sendInputEvent(q.mEvent, q, false, this);
+ return DEFER;
+ }
+ return FORWARD;
+ }
+
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ QueuedInputEvent q = (QueuedInputEvent)token;
+ if (handled) {
+ finish(q, true);
+ return;
+ }
+ forward(q);
+ }
+ }
+
+ /**
+ * Delivers post-ime input events to the view hierarchy.
+ */
+ final class ViewPostImeInputStage extends InputStage {
+ public ViewPostImeInputStage(InputStage next) {
+ super(next);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ return processKeyEvent(q);
+ } else {
+ final int source = q.mEvent.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ return processPointerEvent(q);
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ return processTrackballEvent(q);
+ } else {
+ return processGenericMotionEvent(q);
+ }
+ }
+ }
+
+ @Override
+ protected void onDeliverToNext(QueuedInputEvent q) {
+ if (mUnbufferedInputDispatch
+ && q.mEvent instanceof MotionEvent
+ && ((MotionEvent)q.mEvent).isTouchEvent()
+ && isTerminalInputEvent(q.mEvent)) {
+ mUnbufferedInputDispatch = false;
+ scheduleConsumeBatchedInput();
+ }
+ super.onDeliverToNext(q);
+ }
+
+ private boolean performFocusNavigation(KeyEvent event) {
+ int direction = 0;
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_LEFT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_RIGHT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_UP;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_DOWN;
+ }
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_FORWARD;
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+ direction = View.FOCUS_BACKWARD;
+ }
+ break;
+ }
+ if (direction != 0) {
+ View focused = mView.findFocus();
+ if (focused != null) {
+ View v = focused.focusSearch(direction);
+ if (v != null && v != focused) {
+ // do the math the get the interesting rect
+ // of previous focused into the coord system of
+ // newly focused view
+ focused.getFocusedRect(mTempRect);
+ if (mView instanceof ViewGroup) {
+ ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+ focused, mTempRect);
+ ((ViewGroup) mView).offsetRectIntoDescendantCoords(
+ v, mTempRect);
+ }
+ if (v.requestFocus(direction, mTempRect)) {
+ playSoundEffect(SoundEffectConstants
+ .getContantForFocusDirection(direction));
+ return true;
+ }
+ }
+
+ // Give the focused view a last chance to handle the dpad key.
+ if (mView.dispatchUnhandledMove(focused, direction)) {
+ return true;
+ }
+ } else {
+ if (mView.restoreDefaultFocus()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean performKeyboardGroupNavigation(int direction) {
+ final View focused = mView.findFocus();
+ if (focused == null && mView.restoreDefaultFocus()) {
+ return true;
+ }
+ View cluster = focused == null ? keyboardNavigationClusterSearch(null, direction)
+ : focused.keyboardNavigationClusterSearch(null, direction);
+
+ // Since requestFocus only takes "real" focus directions (and therefore also
+ // restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN.
+ int realDirection = direction;
+ if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+ realDirection = View.FOCUS_DOWN;
+ }
+
+ if (cluster != null && cluster.isRootNamespace()) {
+ // the default cluster. Try to find a non-clustered view to focus.
+ if (cluster.restoreFocusNotInCluster()) {
+ playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+ return true;
+ }
+ // otherwise skip to next actual cluster
+ cluster = keyboardNavigationClusterSearch(null, direction);
+ }
+
+ if (cluster != null && cluster.restoreFocusInCluster(realDirection)) {
+ playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+ return true;
+ }
+
+ return false;
+ }
+
+ private int processKeyEvent(QueuedInputEvent q) {
+ final KeyEvent event = (KeyEvent)q.mEvent;
+
+ // Deliver the key to the view hierarchy.
+ if (mView.dispatchKeyEvent(event)) {
+ return FINISH_HANDLED;
+ }
+
+ if (shouldDropInputEvent(q)) {
+ return FINISH_NOT_HANDLED;
+ }
+
+ int groupNavigationDirection = 0;
+
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
+ if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
+ groupNavigationDirection = View.FOCUS_FORWARD;
+ } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
+ KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
+ groupNavigationDirection = View.FOCUS_BACKWARD;
+ }
+ }
+
+ // If a modifier is held, try to interpret the key as a shortcut.
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
+ && event.getRepeatCount() == 0
+ && !KeyEvent.isModifierKey(event.getKeyCode())
+ && groupNavigationDirection == 0) {
+ if (mView.dispatchKeyShortcutEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ if (shouldDropInputEvent(q)) {
+ return FINISH_NOT_HANDLED;
+ }
+ }
+
+ // Apply the fallback event policy.
+ if (mFallbackEventHandler.dispatchKeyEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ if (shouldDropInputEvent(q)) {
+ return FINISH_NOT_HANDLED;
+ }
+
+ // Handle automatic focus changes.
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (groupNavigationDirection != 0) {
+ if (performKeyboardGroupNavigation(groupNavigationDirection)) {
+ return FINISH_HANDLED;
+ }
+ } else {
+ if (performFocusNavigation(event)) {
+ return FINISH_HANDLED;
+ }
+ }
+ }
+ return FORWARD;
+ }
+
+ private int processPointerEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+
+ mAttachInfo.mUnbufferedDispatchRequested = false;
+ mAttachInfo.mHandlingPointerEvent = true;
+ boolean handled = mView.dispatchPointerEvent(event);
+ maybeUpdatePointerIcon(event);
+ maybeUpdateTooltip(event);
+ mAttachInfo.mHandlingPointerEvent = false;
+ if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
+ mUnbufferedInputDispatch = true;
+ if (mConsumeBatchedInputScheduled) {
+ scheduleConsumeBatchedInputImmediately();
+ }
+ }
+ return handled ? FINISH_HANDLED : FORWARD;
+ }
+
+ private void maybeUpdatePointerIcon(MotionEvent event) {
+ if (event.getPointerCount() == 1 && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
+ || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
+ // Other apps or the window manager may change the icon type outside of
+ // this app, therefore the icon type has to be reset on enter/exit event.
+ mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ }
+
+ if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
+ if (!updatePointerIcon(event) &&
+ event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
+ mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ }
+ }
+ }
+ }
+
+ private int processTrackballEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
+ if (!hasPointerCapture() || mView.dispatchCapturedPointerEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ }
+
+ if (mView.dispatchTrackballEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ return FORWARD;
+ }
+
+ private int processGenericMotionEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+
+ // Deliver the event to the view.
+ if (mView.dispatchGenericMotionEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ return FORWARD;
+ }
+ }
+
+ private void resetPointerIcon(MotionEvent event) {
+ mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
+ updatePointerIcon(event);
+ }
+
+ private boolean updatePointerIcon(MotionEvent event) {
+ final int pointerIndex = 0;
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ if (mView == null) {
+ // E.g. click outside a popup to dismiss it
+ Slog.d(mTag, "updatePointerIcon called after view was removed");
+ return false;
+ }
+ if (x < 0 || x >= mView.getWidth() || y < 0 || y >= mView.getHeight()) {
+ // E.g. when moving window divider with mouse
+ Slog.d(mTag, "updatePointerIcon called with position out of bounds");
+ return false;
+ }
+ final PointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
+ final int pointerType = (pointerIcon != null) ?
+ pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;
+
+ if (mPointerIconType != pointerType) {
+ mPointerIconType = pointerType;
+ mCustomPointerIcon = null;
+ if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
+ InputManager.getInstance().setPointerIconType(pointerType);
+ return true;
+ }
+ }
+ if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
+ !pointerIcon.equals(mCustomPointerIcon)) {
+ mCustomPointerIcon = pointerIcon;
+ InputManager.getInstance().setCustomPointerIcon(mCustomPointerIcon);
+ }
+ return true;
+ }
+
+ private void maybeUpdateTooltip(MotionEvent event) {
+ if (event.getPointerCount() != 1) {
+ return;
+ }
+ final int action = event.getActionMasked();
+ if (action != MotionEvent.ACTION_HOVER_ENTER
+ && action != MotionEvent.ACTION_HOVER_MOVE
+ && action != MotionEvent.ACTION_HOVER_EXIT) {
+ return;
+ }
+ AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+ if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
+ return;
+ }
+ if (mView == null) {
+ Slog.d(mTag, "maybeUpdateTooltip called after view was removed");
+ return;
+ }
+ mView.dispatchTooltipHoverEvent(event);
+ }
+
+ /**
+ * Performs synthesis of new input events from unhandled input events.
+ */
+ final class SyntheticInputStage extends InputStage {
+ private final SyntheticTrackballHandler mTrackball = new SyntheticTrackballHandler();
+ private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
+ private final SyntheticTouchNavigationHandler mTouchNavigation =
+ new SyntheticTouchNavigationHandler();
+ private final SyntheticKeyboardHandler mKeyboard = new SyntheticKeyboardHandler();
+
+ public SyntheticInputStage() {
+ super(null);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
+ if (q.mEvent instanceof MotionEvent) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ mTrackball.process(event);
+ return FINISH_HANDLED;
+ } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ mJoystick.process(event);
+ return FINISH_HANDLED;
+ } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
+ == InputDevice.SOURCE_TOUCH_NAVIGATION) {
+ mTouchNavigation.process(event);
+ return FINISH_HANDLED;
+ }
+ } else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) {
+ mKeyboard.process((KeyEvent)q.mEvent);
+ return FINISH_HANDLED;
+ }
+
+ return FORWARD;
+ }
+
+ @Override
+ protected void onDeliverToNext(QueuedInputEvent q) {
+ if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) {
+ // Cancel related synthetic events if any prior stage has handled the event.
+ if (q.mEvent instanceof MotionEvent) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ mTrackball.cancel(event);
+ } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ mJoystick.cancel(event);
+ } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
+ == InputDevice.SOURCE_TOUCH_NAVIGATION) {
+ mTouchNavigation.cancel(event);
+ }
+ }
+ }
+ super.onDeliverToNext(q);
+ }
+ }
+
+ /**
+ * Creates dpad events from unhandled trackball movements.
+ */
+ final class SyntheticTrackballHandler {
+ private final TrackballAxis mX = new TrackballAxis();
+ private final TrackballAxis mY = new TrackballAxis();
+ private long mLastTime;
+
+ public void process(MotionEvent event) {
+ // Translate the trackball event into DPAD keys and try to deliver those.
+ long curTime = SystemClock.uptimeMillis();
+ if ((mLastTime + MAX_TRACKBALL_DELAY) < curTime) {
+ // It has been too long since the last movement,
+ // so restart at the beginning.
+ mX.reset(0);
+ mY.reset(0);
+ mLastTime = curTime;
+ }
+
+ final int action = event.getAction();
+ final int metaState = event.getMetaState();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mX.reset(2);
+ mY.reset(2);
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ break;
+ case MotionEvent.ACTION_UP:
+ mX.reset(2);
+ mY.reset(2);
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ break;
+ }
+
+ if (DEBUG_TRACKBALL) Log.v(mTag, "TB X=" + mX.position + " step="
+ + mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
+ + " move=" + event.getX()
+ + " / Y=" + mY.position + " step="
+ + mY.step + " dir=" + mY.dir + " acc=" + mY.acceleration
+ + " move=" + event.getY());
+ final float xOff = mX.collect(event.getX(), event.getEventTime(), "X");
+ final float yOff = mY.collect(event.getY(), event.getEventTime(), "Y");
+
+ // Generate DPAD events based on the trackball movement.
+ // We pick the axis that has moved the most as the direction of
+ // the DPAD. When we generate DPAD events for one axis, then the
+ // other axis is reset -- we don't want to perform DPAD jumps due
+ // to slight movements in the trackball when making major movements
+ // along the other axis.
+ int keycode = 0;
+ int movement = 0;
+ float accel = 1;
+ if (xOff > yOff) {
+ movement = mX.generate();
+ if (movement != 0) {
+ keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
+ : KeyEvent.KEYCODE_DPAD_LEFT;
+ accel = mX.acceleration;
+ mY.reset(2);
+ }
+ } else if (yOff > 0) {
+ movement = mY.generate();
+ if (movement != 0) {
+ keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
+ : KeyEvent.KEYCODE_DPAD_UP;
+ accel = mY.acceleration;
+ mX.reset(2);
+ }
+ }
+
+ if (keycode != 0) {
+ if (movement < 0) movement = -movement;
+ int accelMovement = (int)(movement * accel);
+ if (DEBUG_TRACKBALL) Log.v(mTag, "Move: movement=" + movement
+ + " accelMovement=" + accelMovement
+ + " accel=" + accel);
+ if (accelMovement > movement) {
+ if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+ + keycode);
+ movement--;
+ int repeatCount = accelMovement - movement;
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ }
+ while (movement > 0) {
+ if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+ + keycode);
+ movement--;
+ curTime = SystemClock.uptimeMillis();
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_DOWN, keycode, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_UP, keycode, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ }
+ mLastTime = curTime;
+ }
+ }
+
+ public void cancel(MotionEvent event) {
+ mLastTime = Integer.MIN_VALUE;
+
+ // If we reach this, we consumed a trackball event.
+ // Because we will not translate the trackball event into a key event,
+ // touch mode will not exit, so we exit touch mode here.
+ if (mView != null && mAdded) {
+ ensureTouchMode(false);
+ }
+ }
+ }
+
+ /**
+ * Maintains state information for a single trackball axis, generating
+ * discrete (DPAD) movements based on raw trackball motion.
+ */
+ static final class TrackballAxis {
+ /**
+ * The maximum amount of acceleration we will apply.
+ */
+ static final float MAX_ACCELERATION = 20;
+
+ /**
+ * The maximum amount of time (in milliseconds) between events in order
+ * for us to consider the user to be doing fast trackball movements,
+ * and thus apply an acceleration.
+ */
+ static final long FAST_MOVE_TIME = 150;
+
+ /**
+ * Scaling factor to the time (in milliseconds) between events to how
+ * much to multiple/divide the current acceleration. When movement
+ * is < FAST_MOVE_TIME this multiplies the acceleration; when >
+ * FAST_MOVE_TIME it divides it.
+ */
+ static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
+
+ static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
+ static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
+ static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
+
+ float position;
+ float acceleration = 1;
+ long lastMoveTime = 0;
+ int step;
+ int dir;
+ int nonAccelMovement;
+
+ void reset(int _step) {
+ position = 0;
+ acceleration = 1;
+ lastMoveTime = 0;
+ step = _step;
+ dir = 0;
+ }
+
+ /**
+ * Add trackball movement into the state. If the direction of movement
+ * has been reversed, the state is reset before adding the
+ * movement (so that you don't have to compensate for any previously
+ * collected movement before see the result of the movement in the
+ * new direction).
+ *
+ * @return Returns the absolute value of the amount of movement
+ * collected so far.
+ */
+ float collect(float off, long time, String axis) {
+ long normTime;
+ if (off > 0) {
+ normTime = (long)(off * FAST_MOVE_TIME);
+ if (dir < 0) {
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
+ position = 0;
+ step = 0;
+ acceleration = 1;
+ lastMoveTime = 0;
+ }
+ dir = 1;
+ } else if (off < 0) {
+ normTime = (long)((-off) * FAST_MOVE_TIME);
+ if (dir > 0) {
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
+ position = 0;
+ step = 0;
+ acceleration = 1;
+ lastMoveTime = 0;
+ }
+ dir = -1;
+ } else {
+ normTime = 0;
+ }
+
+ // The number of milliseconds between each movement that is
+ // considered "normal" and will not result in any acceleration
+ // or deceleration, scaled by the offset we have here.
+ if (normTime > 0) {
+ long delta = time - lastMoveTime;
+ lastMoveTime = time;
+ float acc = acceleration;
+ if (delta < normTime) {
+ // The user is scrolling rapidly, so increase acceleration.
+ float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
+ if (scale > 1) acc *= scale;
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+ + off + " normTime=" + normTime + " delta=" + delta
+ + " scale=" + scale + " acc=" + acc);
+ acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
+ } else {
+ // The user is scrolling slowly, so decrease acceleration.
+ float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
+ if (scale > 1) acc /= scale;
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+ + off + " normTime=" + normTime + " delta=" + delta
+ + " scale=" + scale + " acc=" + acc);
+ acceleration = acc > 1 ? acc : 1;
+ }
+ }
+ position += off;
+ return Math.abs(position);
+ }
+
+ /**
+ * Generate the number of discrete movement events appropriate for
+ * the currently collected trackball movement.
+ *
+ * @return Returns the number of discrete movements, either positive
+ * or negative, or 0 if there is not enough trackball movement yet
+ * for a discrete movement.
+ */
+ int generate() {
+ int movement = 0;
+ nonAccelMovement = 0;
+ do {
+ final int dir = position >= 0 ? 1 : -1;
+ switch (step) {
+ // If we are going to execute the first step, then we want
+ // to do this as soon as possible instead of waiting for
+ // a full movement, in order to make things look responsive.
+ case 0:
+ if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
+ return movement;
+ }
+ movement += dir;
+ nonAccelMovement += dir;
+ step = 1;
+ break;
+ // If we have generated the first movement, then we need
+ // to wait for the second complete trackball motion before
+ // generating the second discrete movement.
+ case 1:
+ if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
+ return movement;
+ }
+ movement += dir;
+ nonAccelMovement += dir;
+ position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
+ step = 2;
+ break;
+ // After the first two, we generate discrete movements
+ // consistently with the trackball, applying an acceleration
+ // if the trackball is moving quickly. This is a simple
+ // acceleration on top of what we already compute based
+ // on how quickly the wheel is being turned, to apply
+ // a longer increasing acceleration to continuous movement
+ // in one direction.
+ default:
+ if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
+ return movement;
+ }
+ movement += dir;
+ position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
+ float acc = acceleration;
+ acc *= 1.1f;
+ acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
+ break;
+ }
+ } while (true);
+ }
+ }
+
+ /**
+ * Creates dpad events from unhandled joystick movements.
+ */
+ final class SyntheticJoystickHandler extends Handler {
+ private final static String TAG = "SyntheticJoystickHandler";
+ private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
+ private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
+
+ private int mLastXDirection;
+ private int mLastYDirection;
+ private int mLastXKeyCode;
+ private int mLastYKeyCode;
+
+ public SyntheticJoystickHandler() {
+ super(true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
+ case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
+ KeyEvent oldEvent = (KeyEvent)msg.obj;
+ KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+ SystemClock.uptimeMillis(),
+ oldEvent.getRepeatCount() + 1);
+ if (mAttachInfo.mHasWindowFocus) {
+ enqueueInputEvent(e);
+ Message m = obtainMessage(msg.what, e);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatDelay());
+ }
+ } break;
+ }
+ }
+
+ public void process(MotionEvent event) {
+ switch(event.getActionMasked()) {
+ case MotionEvent.ACTION_CANCEL:
+ cancel(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ update(event, true);
+ break;
+ default:
+ Log.w(mTag, "Unexpected action: " + event.getActionMasked());
+ }
+ }
+
+ private void cancel(MotionEvent event) {
+ removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
+ removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
+ update(event, false);
+ }
+
+ private void update(MotionEvent event, boolean synthesizeNewKeys) {
+ final long time = event.getEventTime();
+ final int metaState = event.getMetaState();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+
+ int xDirection = joystickAxisValueToDirection(
+ event.getAxisValue(MotionEvent.AXIS_HAT_X));
+ if (xDirection == 0) {
+ xDirection = joystickAxisValueToDirection(event.getX());
+ }
+
+ int yDirection = joystickAxisValueToDirection(
+ event.getAxisValue(MotionEvent.AXIS_HAT_Y));
+ if (yDirection == 0) {
+ yDirection = joystickAxisValueToDirection(event.getY());
+ }
+
+ if (xDirection != mLastXDirection) {
+ if (mLastXKeyCode != 0) {
+ removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
+ enqueueInputEvent(new KeyEvent(time, time,
+ KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ mLastXKeyCode = 0;
+ }
+
+ mLastXDirection = xDirection;
+
+ if (xDirection != 0 && synthesizeNewKeys) {
+ mLastXKeyCode = xDirection > 0
+ ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
+ final KeyEvent e = new KeyEvent(time, time,
+ KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(e);
+ Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ }
+ }
+
+ if (yDirection != mLastYDirection) {
+ if (mLastYKeyCode != 0) {
+ removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
+ enqueueInputEvent(new KeyEvent(time, time,
+ KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ mLastYKeyCode = 0;
+ }
+
+ mLastYDirection = yDirection;
+
+ if (yDirection != 0 && synthesizeNewKeys) {
+ mLastYKeyCode = yDirection > 0
+ ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
+ final KeyEvent e = new KeyEvent(time, time,
+ KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(e);
+ Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ }
+ }
+ }
+
+ private int joystickAxisValueToDirection(float value) {
+ if (value >= 0.5f) {
+ return 1;
+ } else if (value <= -0.5f) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Creates dpad events from unhandled touch navigation movements.
+ */
+ final class SyntheticTouchNavigationHandler extends Handler {
+ private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler";
+ private static final boolean LOCAL_DEBUG = false;
+
+ // Assumed nominal width and height in millimeters of a touch navigation pad,
+ // if no resolution information is available from the input system.
+ private static final float DEFAULT_WIDTH_MILLIMETERS = 48;
+ private static final float DEFAULT_HEIGHT_MILLIMETERS = 48;
+
+ /* TODO: These constants should eventually be moved to ViewConfiguration. */
+
+ // The nominal distance traveled to move by one unit.
+ private static final int TICK_DISTANCE_MILLIMETERS = 12;
+
+ // Minimum and maximum fling velocity in ticks per second.
+ // The minimum velocity should be set such that we perform enough ticks per
+ // second that the fling appears to be fluid. For example, if we set the minimum
+ // to 2 ticks per second, then there may be up to half a second delay between the next
+ // to last and last ticks which is noticeably discrete and jerky. This value should
+ // probably not be set to anything less than about 4.
+ // If fling accuracy is a problem then consider tuning the tick distance instead.
+ private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f;
+ private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f;
+
+ // Fling velocity decay factor applied after each new key is emitted.
+ // This parameter controls the deceleration and overall duration of the fling.
+ // The fling stops automatically when its velocity drops below the minimum
+ // fling velocity defined above.
+ private static final float FLING_TICK_DECAY = 0.8f;
+
+ /* The input device that we are tracking. */
+
+ private int mCurrentDeviceId = -1;
+ private int mCurrentSource;
+ private boolean mCurrentDeviceSupported;
+
+ /* Configuration for the current input device. */
+
+ // The scaled tick distance. A movement of this amount should generally translate
+ // into a single dpad event in a given direction.
+ private float mConfigTickDistance;
+
+ // The minimum and maximum scaled fling velocity.
+ private float mConfigMinFlingVelocity;
+ private float mConfigMaxFlingVelocity;
+
+ /* Tracking state. */
+
+ // The velocity tracker for detecting flings.
+ private VelocityTracker mVelocityTracker;
+
+ // The active pointer id, or -1 if none.
+ private int mActivePointerId = -1;
+
+ // Location where tracking started.
+ private float mStartX;
+ private float mStartY;
+
+ // Most recently observed position.
+ private float mLastX;
+ private float mLastY;
+
+ // Accumulated movement delta since the last direction key was sent.
+ private float mAccumulatedX;
+ private float mAccumulatedY;
+
+ // Set to true if any movement was delivered to the app.
+ // Implies that tap slop was exceeded.
+ private boolean mConsumedMovement;
+
+ // The most recently sent key down event.
+ // The keycode remains set until the direction changes or a fling ends
+ // so that repeated key events may be generated as required.
+ private long mPendingKeyDownTime;
+ private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ private int mPendingKeyRepeatCount;
+ private int mPendingKeyMetaState;
+
+ // The current fling velocity while a fling is in progress.
+ private boolean mFlinging;
+ private float mFlingVelocity;
+
+ public SyntheticTouchNavigationHandler() {
+ super(true);
+ }
+
+ public void process(MotionEvent event) {
+ // Update the current device information.
+ final long time = event.getEventTime();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+ if (mCurrentDeviceId != deviceId || mCurrentSource != source) {
+ finishKeys(time);
+ finishTracking(time);
+ mCurrentDeviceId = deviceId;
+ mCurrentSource = source;
+ mCurrentDeviceSupported = false;
+ InputDevice device = event.getDevice();
+ if (device != null) {
+ // In order to support an input device, we must know certain
+ // characteristics about it, such as its size and resolution.
+ InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X);
+ InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y);
+ if (xRange != null && yRange != null) {
+ mCurrentDeviceSupported = true;
+
+ // Infer the resolution if it not actually known.
+ float xRes = xRange.getResolution();
+ if (xRes <= 0) {
+ xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS;
+ }
+ float yRes = yRange.getResolution();
+ if (yRes <= 0) {
+ yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS;
+ }
+ float nominalRes = (xRes + yRes) * 0.5f;
+
+ // Precompute all of the configuration thresholds we will need.
+ mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes;
+ mConfigMinFlingVelocity =
+ MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+ mConfigMaxFlingVelocity =
+ MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId
+ + " (" + Integer.toHexString(mCurrentSource) + "): "
+ + ", mConfigTickDistance=" + mConfigTickDistance
+ + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity
+ + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity);
+ }
+ }
+ }
+ }
+ if (!mCurrentDeviceSupported) {
+ return;
+ }
+
+ // Handle the event.
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ boolean caughtFling = mFlinging;
+ finishKeys(time);
+ finishTracking(time);
+ mActivePointerId = event.getPointerId(0);
+ mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(event);
+ mStartX = event.getX();
+ mStartY = event.getY();
+ mLastX = mStartX;
+ mLastY = mStartY;
+ mAccumulatedX = 0;
+ mAccumulatedY = 0;
+
+ // If we caught a fling, then pretend that the tap slop has already
+ // been exceeded to suppress taps whose only purpose is to stop the fling.
+ mConsumedMovement = caughtFling;
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP: {
+ if (mActivePointerId < 0) {
+ break;
+ }
+ final int index = event.findPointerIndex(mActivePointerId);
+ if (index < 0) {
+ finishKeys(time);
+ finishTracking(time);
+ break;
+ }
+
+ mVelocityTracker.addMovement(event);
+ final float x = event.getX(index);
+ final float y = event.getY(index);
+ mAccumulatedX += x - mLastX;
+ mAccumulatedY += y - mLastY;
+ mLastX = x;
+ mLastY = y;
+
+ // Consume any accumulated movement so far.
+ final int metaState = event.getMetaState();
+ consumeAccumulatedMovement(time, metaState);
+
+ // Detect taps and flings.
+ if (action == MotionEvent.ACTION_UP) {
+ if (mConsumedMovement && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ // It might be a fling.
+ mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity);
+ final float vx = mVelocityTracker.getXVelocity(mActivePointerId);
+ final float vy = mVelocityTracker.getYVelocity(mActivePointerId);
+ if (!startFling(time, vx, vy)) {
+ finishKeys(time);
+ }
+ }
+ finishTracking(time);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL: {
+ finishKeys(time);
+ finishTracking(time);
+ break;
+ }
+ }
+ }
+
+ public void cancel(MotionEvent event) {
+ if (mCurrentDeviceId == event.getDeviceId()
+ && mCurrentSource == event.getSource()) {
+ final long time = event.getEventTime();
+ finishKeys(time);
+ finishTracking(time);
+ }
+ }
+
+ private void finishKeys(long time) {
+ cancelFling();
+ sendKeyUp(time);
+ }
+
+ private void finishTracking(long time) {
+ if (mActivePointerId >= 0) {
+ mActivePointerId = -1;
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void consumeAccumulatedMovement(long time, int metaState) {
+ final float absX = Math.abs(mAccumulatedX);
+ final float absY = Math.abs(mAccumulatedY);
+ if (absX >= absY) {
+ if (absX >= mConfigTickDistance) {
+ mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX,
+ KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT);
+ mAccumulatedY = 0;
+ mConsumedMovement = true;
+ }
+ } else {
+ if (absY >= mConfigTickDistance) {
+ mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY,
+ KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN);
+ mAccumulatedX = 0;
+ mConsumedMovement = true;
+ }
+ }
+ }
+
+ private float consumeAccumulatedMovement(long time, int metaState,
+ float accumulator, int negativeKeyCode, int positiveKeyCode) {
+ while (accumulator <= -mConfigTickDistance) {
+ sendKeyDownOrRepeat(time, negativeKeyCode, metaState);
+ accumulator += mConfigTickDistance;
+ }
+ while (accumulator >= mConfigTickDistance) {
+ sendKeyDownOrRepeat(time, positiveKeyCode, metaState);
+ accumulator -= mConfigTickDistance;
+ }
+ return accumulator;
+ }
+
+ private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) {
+ if (mPendingKeyCode != keyCode) {
+ sendKeyUp(time);
+ mPendingKeyDownTime = time;
+ mPendingKeyCode = keyCode;
+ mPendingKeyRepeatCount = 0;
+ } else {
+ mPendingKeyRepeatCount += 1;
+ }
+ mPendingKeyMetaState = metaState;
+
+ // Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1
+ // but it doesn't quite make sense when simulating the events in this way.
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode
+ + ", repeatCount=" + mPendingKeyRepeatCount
+ + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+ }
+ enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+ KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount,
+ mPendingKeyMetaState, mCurrentDeviceId,
+ KeyEvent.FLAG_FALLBACK, mCurrentSource));
+ }
+
+ private void sendKeyUp(long time) {
+ if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode
+ + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+ }
+ enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+ KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState,
+ mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK,
+ mCurrentSource));
+ mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ }
+ }
+
+ private boolean startFling(long time, float vx, float vy) {
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy
+ + ", min=" + mConfigMinFlingVelocity);
+ }
+
+ // Flings must be oriented in the same direction as the preceding movements.
+ switch (mPendingKeyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (-vx >= mConfigMinFlingVelocity
+ && Math.abs(vy) < mConfigMinFlingVelocity) {
+ mFlingVelocity = -vx;
+ break;
+ }
+ return false;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (vx >= mConfigMinFlingVelocity
+ && Math.abs(vy) < mConfigMinFlingVelocity) {
+ mFlingVelocity = vx;
+ break;
+ }
+ return false;
+
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (-vy >= mConfigMinFlingVelocity
+ && Math.abs(vx) < mConfigMinFlingVelocity) {
+ mFlingVelocity = -vy;
+ break;
+ }
+ return false;
+
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (vy >= mConfigMinFlingVelocity
+ && Math.abs(vx) < mConfigMinFlingVelocity) {
+ mFlingVelocity = vy;
+ break;
+ }
+ return false;
+ }
+
+ // Post the first fling event.
+ mFlinging = postFling(time);
+ return mFlinging;
+ }
+
+ private boolean postFling(long time) {
+ // The idea here is to estimate the time when the pointer would have
+ // traveled one tick distance unit given the current fling velocity.
+ // This effect creates continuity of motion.
+ if (mFlingVelocity >= mConfigMinFlingVelocity) {
+ long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000);
+ postAtTime(mFlingRunnable, time + delay);
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Posted fling: velocity="
+ + mFlingVelocity + ", delay=" + delay
+ + ", keyCode=" + mPendingKeyCode);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void cancelFling() {
+ if (mFlinging) {
+ removeCallbacks(mFlingRunnable);
+ mFlinging = false;
+ }
+ }
+
+ private final Runnable mFlingRunnable = new Runnable() {
+ @Override
+ public void run() {
+ final long time = SystemClock.uptimeMillis();
+ sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState);
+ mFlingVelocity *= FLING_TICK_DECAY;
+ if (!postFling(time)) {
+ mFlinging = false;
+ finishKeys(time);
+ }
+ }
+ };
+ }
+
+ final class SyntheticKeyboardHandler {
+ public void process(KeyEvent event) {
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
+ return;
+ }
+
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+
+ // Check for fallback actions specified by the key character map.
+ KeyCharacterMap.FallbackAction fallbackAction =
+ kcm.getFallbackAction(keyCode, metaState);
+ if (fallbackAction != null) {
+ final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
+ KeyEvent fallbackEvent = KeyEvent.obtain(
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), fallbackAction.keyCode,
+ event.getRepeatCount(), fallbackAction.metaState,
+ event.getDeviceId(), event.getScanCode(),
+ flags, event.getSource(), null);
+ fallbackAction.recycle();
+ enqueueInputEvent(fallbackEvent);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the key is used for keyboard navigation.
+ * @param keyEvent The key event.
+ * @return True if the key is used for keyboard navigation.
+ */
+ private static boolean isNavigationKey(KeyEvent keyEvent) {
+ switch (keyEvent.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_PAGE_UP:
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ case KeyEvent.KEYCODE_MOVE_END:
+ case KeyEvent.KEYCODE_TAB:
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_ENTER:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the key is used for typing.
+ * @param keyEvent The key event.
+ * @return True if the key is used for typing.
+ */
+ private static boolean isTypingKey(KeyEvent keyEvent) {
+ return keyEvent.getUnicodeChar() > 0;
+ }
+
+ /**
+ * See if the key event means we should leave touch mode (and leave touch mode if so).
+ * @param event The key event.
+ * @return Whether this key event should be consumed (meaning the act of
+ * leaving touch mode alone is considered the event).
+ */
+ private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) {
+ // Only relevant in touch mode.
+ if (!mAttachInfo.mInTouchMode) {
+ return false;
+ }
+
+ // Only consider leaving touch mode on DOWN or MULTIPLE actions, never on UP.
+ final int action = event.getAction();
+ if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_MULTIPLE) {
+ return false;
+ }
+
+ // Don't leave touch mode if the IME told us not to.
+ if ((event.getFlags() & KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
+ return false;
+ }
+
+ // If the key can be used for keyboard navigation then leave touch mode
+ // and select a focused view if needed (in ensureTouchMode).
+ // When a new focused view is selected, we consume the navigation key because
+ // navigation doesn't make much sense unless a view already has focus so
+ // the key's purpose is to set focus.
+ if (isNavigationKey(event)) {
+ return ensureTouchMode(false);
+ }
+
+ // If the key can be used for typing then leave touch mode
+ // and select a focused view if needed (in ensureTouchMode).
+ // Always allow the view to process the typing key.
+ if (isTypingKey(event)) {
+ ensureTouchMode(false);
+ return false;
+ }
+
+ return false;
+ }
+
+ /* drag/drop */
+ void setLocalDragState(Object obj) {
+ mLocalDragState = obj;
+ }
+
+ private void handleDragEvent(DragEvent event) {
+ // From the root, only drag start/end/location are dispatched. entered/exited
+ // are determined and dispatched by the viewgroup hierarchy, who then report
+ // that back here for ultimate reporting back to the framework.
+ if (mView != null && mAdded) {
+ final int what = event.mAction;
+
+ // Cache the drag description when the operation starts, then fill it in
+ // on subsequent calls as a convenience
+ if (what == DragEvent.ACTION_DRAG_STARTED) {
+ mCurrentDragView = null; // Start the current-recipient tracking
+ mDragDescription = event.mClipDescription;
+ } else {
+ if (what == DragEvent.ACTION_DRAG_ENDED) {
+ mDragDescription = null;
+ }
+ event.mClipDescription = mDragDescription;
+ }
+
+ if (what == DragEvent.ACTION_DRAG_EXITED) {
+ // A direct EXITED event means that the window manager knows we've just crossed
+ // a window boundary, so the current drag target within this one must have
+ // just been exited. Send the EXITED notification to the current drag view, if any.
+ if (View.sCascadedDragDrop) {
+ mView.dispatchDragEnterExitInPreN(event);
+ }
+ setDragFocus(null, event);
+ } else {
+ // For events with a [screen] location, translate into window coordinates
+ if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
+ mDragPoint.set(event.mX, event.mY);
+ if (mTranslator != null) {
+ mTranslator.translatePointInScreenToAppWindow(mDragPoint);
+ }
+
+ if (mCurScrollY != 0) {
+ mDragPoint.offset(0, mCurScrollY);
+ }
+
+ event.mX = mDragPoint.x;
+ event.mY = mDragPoint.y;
+ }
+
+ // Remember who the current drag target is pre-dispatch
+ final View prevDragView = mCurrentDragView;
+
+ if (what == DragEvent.ACTION_DROP && event.mClipData != null) {
+ event.mClipData.prepareToEnterProcess();
+ }
+
+ // Now dispatch the drag/drop event
+ boolean result = mView.dispatchDragEvent(event);
+
+ if (what == DragEvent.ACTION_DRAG_LOCATION && !event.mEventHandlerWasCalled) {
+ // If the LOCATION event wasn't delivered to any handler, no view now has a drag
+ // focus.
+ setDragFocus(null, event);
+ }
+
+ // If we changed apparent drag target, tell the OS about it
+ if (prevDragView != mCurrentDragView) {
+ try {
+ if (prevDragView != null) {
+ mWindowSession.dragRecipientExited(mWindow);
+ }
+ if (mCurrentDragView != null) {
+ mWindowSession.dragRecipientEntered(mWindow);
+ }
+ } catch (RemoteException e) {
+ Slog.e(mTag, "Unable to note drag target change");
+ }
+ }
+
+ // Report the drop result when we're done
+ if (what == DragEvent.ACTION_DROP) {
+ try {
+ Log.i(mTag, "Reporting drop result: " + result);
+ mWindowSession.reportDropResult(mWindow, result);
+ } catch (RemoteException e) {
+ Log.e(mTag, "Unable to report drop result");
+ }
+ }
+
+ // When the drag operation ends, reset drag-related state
+ if (what == DragEvent.ACTION_DRAG_ENDED) {
+ mCurrentDragView = null;
+ setLocalDragState(null);
+ mAttachInfo.mDragToken = null;
+ if (mAttachInfo.mDragSurface != null) {
+ mAttachInfo.mDragSurface.release();
+ mAttachInfo.mDragSurface = null;
+ }
+ }
+ }
+ }
+ event.recycle();
+ }
+
+ public void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
+ if (mSeq != args.seq) {
+ // The sequence has changed, so we need to update our value and make
+ // sure to do a traversal afterward so the window manager is given our
+ // most recent data.
+ mSeq = args.seq;
+ mAttachInfo.mForceReportNewAttributes = true;
+ scheduleTraversals();
+ }
+ if (mView == null) return;
+ if (args.localChanges != 0) {
+ mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
+ }
+
+ int visibility = args.globalVisibility&View.SYSTEM_UI_CLEARABLE_FLAGS;
+ if (visibility != mAttachInfo.mGlobalSystemUiVisibility) {
+ mAttachInfo.mGlobalSystemUiVisibility = visibility;
+ mView.dispatchSystemUiVisibilityChanged(visibility);
+ }
+ }
+
+ /**
+ * Notify that the window title changed
+ */
+ public void onWindowTitleChanged() {
+ mAttachInfo.mForceReportNewAttributes = true;
+ }
+
+ public void handleDispatchWindowShown() {
+ mAttachInfo.mTreeObserver.dispatchOnWindowShown();
+ }
+
+ public void handleRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+ Bundle data = new Bundle();
+ ArrayList<KeyboardShortcutGroup> list = new ArrayList<>();
+ if (mView != null) {
+ mView.requestKeyboardShortcuts(list, deviceId);
+ }
+ data.putParcelableArrayList(WindowManager.PARCEL_KEY_SHORTCUTS_ARRAY, list);
+ try {
+ receiver.send(0, data);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void getLastTouchPoint(Point outLocation) {
+ outLocation.x = (int) mLastTouchPoint.x;
+ outLocation.y = (int) mLastTouchPoint.y;
+ }
+
+ public int getLastTouchSource() {
+ return mLastTouchSource;
+ }
+
+ public void setDragFocus(View newDragTarget, DragEvent event) {
+ if (mCurrentDragView != newDragTarget && !View.sCascadedDragDrop) {
+ // Send EXITED and ENTERED notifications to the old and new drag focus views.
+
+ final float tx = event.mX;
+ final float ty = event.mY;
+ final int action = event.mAction;
+ final ClipData td = event.mClipData;
+ // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
+ event.mX = 0;
+ event.mY = 0;
+ event.mClipData = null;
+
+ if (mCurrentDragView != null) {
+ event.mAction = DragEvent.ACTION_DRAG_EXITED;
+ mCurrentDragView.callDragEventHandler(event);
+ }
+
+ if (newDragTarget != null) {
+ event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+ newDragTarget.callDragEventHandler(event);
+ }
+
+ event.mAction = action;
+ event.mX = tx;
+ event.mY = ty;
+ event.mClipData = td;
+ }
+
+ mCurrentDragView = newDragTarget;
+ }
+
+ private AudioManager getAudioManager() {
+ if (mView == null) {
+ throw new IllegalStateException("getAudioManager called when there is no mView");
+ }
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE);
+ }
+ return mAudioManager;
+ }
+
+ public AccessibilityInteractionController getAccessibilityInteractionController() {
+ if (mView == null) {
+ throw new IllegalStateException("getAccessibilityInteractionController"
+ + " called when there is no mView");
+ }
+ if (mAccessibilityInteractionController == null) {
+ mAccessibilityInteractionController = new AccessibilityInteractionController(this);
+ }
+ return mAccessibilityInteractionController;
+ }
+
+ private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
+ boolean insetsPending) throws RemoteException {
+
+ float appScale = mAttachInfo.mApplicationScale;
+ boolean restore = false;
+ if (params != null && mTranslator != null) {
+ restore = true;
+ params.backup();
+ mTranslator.translateWindowLayout(params);
+ }
+ if (params != null) {
+ if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
+ }
+
+ //Log.d(mTag, ">>>>>> CALLING relayout");
+ if (params != null && mOrigWindowType != params.type) {
+ // For compatibility with old apps, don't crash here.
+ if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ Slog.w(mTag, "Window type can not be changed after "
+ + "the window is added; ignoring change of " + mView);
+ params.type = mOrigWindowType;
+ }
+ }
+ int relayoutResult = mWindowSession.relayout(
+ mWindow, mSeq, params,
+ (int) (mView.getMeasuredWidth() * appScale + 0.5f),
+ (int) (mView.getMeasuredHeight() * appScale + 0.5f),
+ viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
+ mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
+ mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame,
+ mPendingMergedConfiguration, mSurface);
+
+ mPendingAlwaysConsumeNavBar =
+ (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
+
+ //Log.d(mTag, "<<<<<< BACK FROM relayout");
+ if (restore) {
+ params.restore();
+ }
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
+ mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets);
+ mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
+ mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
+ mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets);
+ }
+ return relayoutResult;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void playSoundEffect(int effectId) {
+ checkThread();
+
+ try {
+ final AudioManager audioManager = getAudioManager();
+
+ switch (effectId) {
+ case SoundEffectConstants.CLICK:
+ audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+ return;
+ case SoundEffectConstants.NAVIGATION_DOWN:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
+ return;
+ case SoundEffectConstants.NAVIGATION_LEFT:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
+ return;
+ case SoundEffectConstants.NAVIGATION_RIGHT:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
+ return;
+ case SoundEffectConstants.NAVIGATION_UP:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
+ return;
+ default:
+ throw new IllegalArgumentException("unknown effect id " + effectId +
+ " not defined in " + SoundEffectConstants.class.getCanonicalName());
+ }
+ } catch (IllegalStateException e) {
+ // Exception thrown by getAudioManager() when mView is null
+ Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean performHapticFeedback(int effectId, boolean always) {
+ try {
+ return mWindowSession.performHapticFeedback(mWindow, effectId, always);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View focusSearch(View focused, int direction) {
+ checkThread();
+ if (!(mView instanceof ViewGroup)) {
+ return null;
+ }
+ return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View keyboardNavigationClusterSearch(View currentCluster,
+ @FocusDirection int direction) {
+ checkThread();
+ return FocusFinder.getInstance().findNextKeyboardNavigationCluster(
+ mView, currentCluster, direction);
+ }
+
+ public void debug() {
+ mView.debug();
+ }
+
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ String innerPrefix = prefix + " ";
+ writer.print(prefix); writer.println("ViewRoot:");
+ writer.print(innerPrefix); writer.print("mAdded="); writer.print(mAdded);
+ writer.print(" mRemoved="); writer.println(mRemoved);
+ writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled=");
+ writer.println(mConsumeBatchedInputScheduled);
+ writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled=");
+ writer.println(mConsumeBatchedInputImmediatelyScheduled);
+ writer.print(innerPrefix); writer.print("mPendingInputEventCount=");
+ writer.println(mPendingInputEventCount);
+ writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled=");
+ writer.println(mProcessInputEventsScheduled);
+ writer.print(innerPrefix); writer.print("mTraversalScheduled=");
+ writer.print(mTraversalScheduled);
+ writer.print(innerPrefix); writer.print("mIsAmbientMode=");
+ writer.print(mIsAmbientMode);
+ if (mTraversalScheduled) {
+ writer.print(" (barrier="); writer.print(mTraversalBarrier); writer.println(")");
+ } else {
+ writer.println();
+ }
+ mFirstInputStage.dump(innerPrefix, writer);
+
+ mChoreographer.dump(prefix, writer);
+
+ writer.print(prefix); writer.println("View Hierarchy:");
+ dumpViewHierarchy(innerPrefix, writer, mView);
+ }
+
+ private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
+ writer.print(prefix);
+ if (view == null) {
+ writer.println("null");
+ return;
+ }
+ writer.println(view.toString());
+ if (!(view instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup grp = (ViewGroup)view;
+ final int N = grp.getChildCount();
+ if (N <= 0) {
+ return;
+ }
+ prefix = prefix + " ";
+ for (int i=0; i<N; i++) {
+ dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
+ }
+ }
+
+ public void dumpGfxInfo(int[] info) {
+ info[0] = info[1] = 0;
+ if (mView != null) {
+ getGfxInfo(mView, info);
+ }
+ }
+
+ private static void getGfxInfo(View view, int[] info) {
+ RenderNode renderNode = view.mRenderNode;
+ info[0]++;
+ if (renderNode != null) {
+ info[1] += renderNode.getDebugSize();
+ }
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+
+ int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ getGfxInfo(group.getChildAt(i), info);
+ }
+ }
+ }
+
+ /**
+ * @param immediate True, do now if not in traversal. False, put on queue and do later.
+ * @return True, request has been queued. False, request has been completed.
+ */
+ boolean die(boolean immediate) {
+ // Make sure we do execute immediately if we are in the middle of a traversal or the damage
+ // done by dispatchDetachedFromWindow will cause havoc on return.
+ if (immediate && !mIsInTraversal) {
+ doDie();
+ return false;
+ }
+
+ if (!mIsDrawing) {
+ destroyHardwareRenderer();
+ } else {
+ Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
+ " window=" + this + ", title=" + mWindowAttributes.getTitle());
+ }
+ mHandler.sendEmptyMessage(MSG_DIE);
+ return true;
+ }
+
+ void doDie() {
+ checkThread();
+ if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
+ synchronized (this) {
+ if (mRemoved) {
+ return;
+ }
+ mRemoved = true;
+ if (mAdded) {
+ dispatchDetachedFromWindow();
+ }
+
+ if (mAdded && !mFirst) {
+ destroyHardwareRenderer();
+
+ if (mView != null) {
+ int viewVisibility = mView.getVisibility();
+ boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
+ if (mWindowAttributesChanged || viewVisibilityChanged) {
+ // If layout params have been changed, first give them
+ // to the window manager to make sure it has the correct
+ // animation info.
+ try {
+ if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
+ & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
+ mWindowSession.finishDrawing(mWindow);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ mSurface.release();
+ }
+ }
+
+ mAdded = false;
+ }
+ WindowManagerGlobal.getInstance().doRemoveView(this);
+ }
+
+ public void requestUpdateConfiguration(Configuration config) {
+ Message msg = mHandler.obtainMessage(MSG_UPDATE_CONFIGURATION, config);
+ mHandler.sendMessage(msg);
+ }
+
+ public void loadSystemProperties() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Profiling
+ mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
+ profileRendering(mAttachInfo.mHasWindowFocus);
+
+ // Hardware rendering
+ if (mAttachInfo.mThreadedRenderer != null) {
+ if (mAttachInfo.mThreadedRenderer.loadSystemProperties()) {
+ invalidate();
+ }
+ }
+
+ // Layout debugging
+ boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false);
+ if (layout != mAttachInfo.mDebugLayout) {
+ mAttachInfo.mDebugLayout = layout;
+ if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
+ mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
+ }
+ }
+ }
+ });
+ }
+
+ private void destroyHardwareRenderer() {
+ ThreadedRenderer hardwareRenderer = mAttachInfo.mThreadedRenderer;
+
+ if (hardwareRenderer != null) {
+ if (mView != null) {
+ hardwareRenderer.destroyHardwareResources(mView);
+ }
+ hardwareRenderer.destroy();
+ hardwareRenderer.setRequested(false);
+
+ mAttachInfo.mThreadedRenderer = null;
+ mAttachInfo.mHardwareAccelerated = false;
+ }
+ }
+
+ private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
+ Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
+ MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
+ boolean alwaysConsumeNavBar, int displayId) {
+ if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString()
+ + " contentInsets=" + contentInsets.toShortString()
+ + " visibleInsets=" + visibleInsets.toShortString()
+ + " reportDraw=" + reportDraw
+ + " backDropFrame=" + backDropFrame);
+
+ // Tell all listeners that we are resizing the window so that the chrome can get
+ // updated as fast as possible on a separate thread,
+ if (mDragResizing) {
+ boolean fullscreen = frame.equals(backDropFrame);
+ synchronized (mWindowCallbacks) {
+ for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+ mWindowCallbacks.get(i).onWindowSizeIsChanging(backDropFrame, fullscreen,
+ visibleInsets, stableInsets);
+ }
+ }
+ }
+
+ Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(frame);
+ mTranslator.translateRectInScreenToAppWindow(overscanInsets);
+ mTranslator.translateRectInScreenToAppWindow(contentInsets);
+ mTranslator.translateRectInScreenToAppWindow(visibleInsets);
+ }
+ SomeArgs args = SomeArgs.obtain();
+ final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
+ args.arg1 = sameProcessCall ? new Rect(frame) : frame;
+ args.arg2 = sameProcessCall ? new Rect(contentInsets) : contentInsets;
+ args.arg3 = sameProcessCall ? new Rect(visibleInsets) : visibleInsets;
+ args.arg4 = sameProcessCall && mergedConfiguration != null
+ ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration;
+ args.arg5 = sameProcessCall ? new Rect(overscanInsets) : overscanInsets;
+ args.arg6 = sameProcessCall ? new Rect(stableInsets) : stableInsets;
+ args.arg7 = sameProcessCall ? new Rect(outsets) : outsets;
+ args.arg8 = sameProcessCall ? new Rect(backDropFrame) : backDropFrame;
+ args.argi1 = forceLayout ? 1 : 0;
+ args.argi2 = alwaysConsumeNavBar ? 1 : 0;
+ args.argi3 = displayId;
+ msg.obj = args;
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchMoved(int newX, int newY) {
+ if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
+ if (mTranslator != null) {
+ PointF point = new PointF(newX, newY);
+ mTranslator.translatePointInScreenToAppWindow(point);
+ newX = (int) (point.x + 0.5);
+ newY = (int) (point.y + 0.5);
+ }
+ Message msg = mHandler.obtainMessage(MSG_WINDOW_MOVED, newX, newY);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Represents a pending input event that is waiting in a queue.
+ *
+ * Input events are processed in serial order by the timestamp specified by
+ * {@link InputEvent#getEventTimeNano()}. In general, the input dispatcher delivers
+ * one input event to the application at a time and waits for the application
+ * to finish handling it before delivering the next one.
+ *
+ * However, because the application or IME can synthesize and inject multiple
+ * key events at a time without going through the input dispatcher, we end up
+ * needing a queue on the application's side.
+ */
+ private static final class QueuedInputEvent {
+ public static final int FLAG_DELIVER_POST_IME = 1 << 0;
+ public static final int FLAG_DEFERRED = 1 << 1;
+ public static final int FLAG_FINISHED = 1 << 2;
+ public static final int FLAG_FINISHED_HANDLED = 1 << 3;
+ public static final int FLAG_RESYNTHESIZED = 1 << 4;
+ public static final int FLAG_UNHANDLED = 1 << 5;
+
+ public QueuedInputEvent mNext;
+
+ public InputEvent mEvent;
+ public InputEventReceiver mReceiver;
+ public int mFlags;
+
+ public boolean shouldSkipIme() {
+ if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
+ return true;
+ }
+ return mEvent instanceof MotionEvent
+ && (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
+ || mEvent.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER));
+ }
+
+ public boolean shouldSendToSynthesizer() {
+ if ((mFlags & FLAG_UNHANDLED) != 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("QueuedInputEvent{flags=");
+ boolean hasPrevious = false;
+ hasPrevious = flagToString("DELIVER_POST_IME", FLAG_DELIVER_POST_IME, hasPrevious, sb);
+ hasPrevious = flagToString("DEFERRED", FLAG_DEFERRED, hasPrevious, sb);
+ hasPrevious = flagToString("FINISHED", FLAG_FINISHED, hasPrevious, sb);
+ hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb);
+ hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb);
+ hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb);
+ if (!hasPrevious) {
+ sb.append("0");
+ }
+ sb.append(", hasNextQueuedEvent=" + (mEvent != null ? "true" : "false"));
+ sb.append(", hasInputEventReceiver=" + (mReceiver != null ? "true" : "false"));
+ sb.append(", mEvent=" + mEvent + "}");
+ return sb.toString();
+ }
+
+ private boolean flagToString(String name, int flag,
+ boolean hasPrevious, StringBuilder sb) {
+ if ((mFlags & flag) != 0) {
+ if (hasPrevious) {
+ sb.append("|");
+ }
+ sb.append(name);
+ return true;
+ }
+ return hasPrevious;
+ }
+ }
+
+ private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
+ InputEventReceiver receiver, int flags) {
+ QueuedInputEvent q = mQueuedInputEventPool;
+ if (q != null) {
+ mQueuedInputEventPoolSize -= 1;
+ mQueuedInputEventPool = q.mNext;
+ q.mNext = null;
+ } else {
+ q = new QueuedInputEvent();
+ }
+
+ q.mEvent = event;
+ q.mReceiver = receiver;
+ q.mFlags = flags;
+ return q;
+ }
+
+ private void recycleQueuedInputEvent(QueuedInputEvent q) {
+ q.mEvent = null;
+ q.mReceiver = null;
+
+ if (mQueuedInputEventPoolSize < MAX_QUEUED_INPUT_EVENT_POOL_SIZE) {
+ mQueuedInputEventPoolSize += 1;
+ q.mNext = mQueuedInputEventPool;
+ mQueuedInputEventPool = q;
+ }
+ }
+
+ void enqueueInputEvent(InputEvent event) {
+ enqueueInputEvent(event, null, 0, false);
+ }
+
+ void enqueueInputEvent(InputEvent event,
+ InputEventReceiver receiver, int flags, boolean processImmediately) {
+ adjustInputEventForCompatibility(event);
+ QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
+
+ // Always enqueue the input event in order, regardless of its time stamp.
+ // We do this because the application or the IME may inject key events
+ // in response to touch events and we want to ensure that the injected keys
+ // are processed in the order they were received and we cannot trust that
+ // the time stamp of injected events are monotonic.
+ QueuedInputEvent last = mPendingInputEventTail;
+ if (last == null) {
+ mPendingInputEventHead = q;
+ mPendingInputEventTail = q;
+ } else {
+ last.mNext = q;
+ mPendingInputEventTail = q;
+ }
+ mPendingInputEventCount += 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
+ mPendingInputEventCount);
+
+ if (processImmediately) {
+ doProcessInputEvents();
+ } else {
+ scheduleProcessInputEvents();
+ }
+ }
+
+ private void scheduleProcessInputEvents() {
+ if (!mProcessInputEventsScheduled) {
+ mProcessInputEventsScheduled = true;
+ Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ void doProcessInputEvents() {
+ // Deliver all pending input events in the queue.
+ while (mPendingInputEventHead != null) {
+ QueuedInputEvent q = mPendingInputEventHead;
+ mPendingInputEventHead = q.mNext;
+ if (mPendingInputEventHead == null) {
+ mPendingInputEventTail = null;
+ }
+ q.mNext = null;
+
+ mPendingInputEventCount -= 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
+ mPendingInputEventCount);
+
+ long eventTime = q.mEvent.getEventTimeNano();
+ long oldestEventTime = eventTime;
+ if (q.mEvent instanceof MotionEvent) {
+ MotionEvent me = (MotionEvent)q.mEvent;
+ if (me.getHistorySize() > 0) {
+ oldestEventTime = me.getHistoricalEventTimeNano(0);
+ }
+ }
+ mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
+
+ deliverInputEvent(q);
+ }
+
+ // We are done processing all input events that we can process right now
+ // so we can clear the pending flag immediately.
+ if (mProcessInputEventsScheduled) {
+ mProcessInputEventsScheduled = false;
+ mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
+ }
+ }
+
+ private void deliverInputEvent(QueuedInputEvent q) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
+ q.mEvent.getSequenceNumber());
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
+ }
+
+ InputStage stage;
+ if (q.shouldSendToSynthesizer()) {
+ stage = mSyntheticInputStage;
+ } else {
+ stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
+ }
+
+ if (stage != null) {
+ stage.deliver(q);
+ } else {
+ finishInputEvent(q);
+ }
+ }
+
+ private void finishInputEvent(QueuedInputEvent q) {
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
+ q.mEvent.getSequenceNumber());
+
+ if (q.mReceiver != null) {
+ boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
+ q.mReceiver.finishInputEvent(q.mEvent, handled);
+ } else {
+ q.mEvent.recycleIfNeededAfterDispatch();
+ }
+
+ recycleQueuedInputEvent(q);
+ }
+
+ private void adjustInputEventForCompatibility(InputEvent e) {
+ if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
+ MotionEvent motion = (MotionEvent) e;
+ final int mask =
+ MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
+ final int buttonState = motion.getButtonState();
+ final int compatButtonState = (buttonState & mask) >> 4;
+ if (compatButtonState != 0) {
+ motion.setButtonState(buttonState | compatButtonState);
+ }
+ }
+ }
+
+ static boolean isTerminalInputEvent(InputEvent event) {
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ return keyEvent.getAction() == KeyEvent.ACTION_UP;
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ final int action = motionEvent.getAction();
+ return action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL
+ || action == MotionEvent.ACTION_HOVER_EXIT;
+ }
+ }
+
+ void scheduleConsumeBatchedInput() {
+ if (!mConsumeBatchedInputScheduled) {
+ mConsumeBatchedInputScheduled = true;
+ mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
+ mConsumedBatchedInputRunnable, null);
+ }
+ }
+
+ void unscheduleConsumeBatchedInput() {
+ if (mConsumeBatchedInputScheduled) {
+ mConsumeBatchedInputScheduled = false;
+ mChoreographer.removeCallbacks(Choreographer.CALLBACK_INPUT,
+ mConsumedBatchedInputRunnable, null);
+ }
+ }
+
+ void scheduleConsumeBatchedInputImmediately() {
+ if (!mConsumeBatchedInputImmediatelyScheduled) {
+ unscheduleConsumeBatchedInput();
+ mConsumeBatchedInputImmediatelyScheduled = true;
+ mHandler.post(mConsumeBatchedInputImmediatelyRunnable);
+ }
+ }
+
+ void doConsumeBatchedInput(long frameTimeNanos) {
+ if (mConsumeBatchedInputScheduled) {
+ mConsumeBatchedInputScheduled = false;
+ if (mInputEventReceiver != null) {
+ if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
+ && frameTimeNanos != -1) {
+ // If we consumed a batch here, we want to go ahead and schedule the
+ // consumption of batched input events on the next frame. Otherwise, we would
+ // wait until we have more input events pending and might get starved by other
+ // things occurring in the process. If the frame time is -1, however, then
+ // we're in a non-batching mode, so there's no need to schedule this.
+ scheduleConsumeBatchedInput();
+ }
+ }
+ doProcessInputEvents();
+ }
+ }
+
+ final class TraversalRunnable implements Runnable {
+ @Override
+ public void run() {
+ doTraversal();
+ }
+ }
+ final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
+
+ final class WindowInputEventReceiver extends InputEventReceiver {
+ public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event, int displayId) {
+ enqueueInputEvent(event, this, 0, true);
+ }
+
+ @Override
+ public void onBatchedInputEventPending() {
+ if (mUnbufferedInputDispatch) {
+ super.onBatchedInputEventPending();
+ } else {
+ scheduleConsumeBatchedInput();
+ }
+ }
+
+ @Override
+ public void dispose() {
+ unscheduleConsumeBatchedInput();
+ super.dispose();
+ }
+ }
+ WindowInputEventReceiver mInputEventReceiver;
+
+ final class ConsumeBatchedInputRunnable implements Runnable {
+ @Override
+ public void run() {
+ doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+ }
+ }
+ final ConsumeBatchedInputRunnable mConsumedBatchedInputRunnable =
+ new ConsumeBatchedInputRunnable();
+ boolean mConsumeBatchedInputScheduled;
+
+ final class ConsumeBatchedInputImmediatelyRunnable implements Runnable {
+ @Override
+ public void run() {
+ doConsumeBatchedInput(-1);
+ }
+ }
+ final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable =
+ new ConsumeBatchedInputImmediatelyRunnable();
+ boolean mConsumeBatchedInputImmediatelyScheduled;
+
+ final class InvalidateOnAnimationRunnable implements Runnable {
+ private boolean mPosted;
+ private final ArrayList<View> mViews = new ArrayList<View>();
+ private final ArrayList<AttachInfo.InvalidateInfo> mViewRects =
+ new ArrayList<AttachInfo.InvalidateInfo>();
+ private View[] mTempViews;
+ private AttachInfo.InvalidateInfo[] mTempViewRects;
+
+ public void addView(View view) {
+ synchronized (this) {
+ mViews.add(view);
+ postIfNeededLocked();
+ }
+ }
+
+ public void addViewRect(AttachInfo.InvalidateInfo info) {
+ synchronized (this) {
+ mViewRects.add(info);
+ postIfNeededLocked();
+ }
+ }
+
+ public void removeView(View view) {
+ synchronized (this) {
+ mViews.remove(view);
+
+ for (int i = mViewRects.size(); i-- > 0; ) {
+ AttachInfo.InvalidateInfo info = mViewRects.get(i);
+ if (info.target == view) {
+ mViewRects.remove(i);
+ info.recycle();
+ }
+ }
+
+ if (mPosted && mViews.isEmpty() && mViewRects.isEmpty()) {
+ mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, this, null);
+ mPosted = false;
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ final int viewCount;
+ final int viewRectCount;
+ synchronized (this) {
+ mPosted = false;
+
+ viewCount = mViews.size();
+ if (viewCount != 0) {
+ mTempViews = mViews.toArray(mTempViews != null
+ ? mTempViews : new View[viewCount]);
+ mViews.clear();
+ }
+
+ viewRectCount = mViewRects.size();
+ if (viewRectCount != 0) {
+ mTempViewRects = mViewRects.toArray(mTempViewRects != null
+ ? mTempViewRects : new AttachInfo.InvalidateInfo[viewRectCount]);
+ mViewRects.clear();
+ }
+ }
+
+ for (int i = 0; i < viewCount; i++) {
+ mTempViews[i].invalidate();
+ mTempViews[i] = null;
+ }
+
+ for (int i = 0; i < viewRectCount; i++) {
+ final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
+ info.target.invalidate(info.left, info.top, info.right, info.bottom);
+ info.recycle();
+ }
+ }
+
+ private void postIfNeededLocked() {
+ if (!mPosted) {
+ mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+ mPosted = true;
+ }
+ }
+ }
+ final InvalidateOnAnimationRunnable mInvalidateOnAnimationRunnable =
+ new InvalidateOnAnimationRunnable();
+
+ public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
+ Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
+ mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ }
+
+ public void dispatchInvalidateRectDelayed(AttachInfo.InvalidateInfo info,
+ long delayMilliseconds) {
+ final Message msg = mHandler.obtainMessage(MSG_INVALIDATE_RECT, info);
+ mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ }
+
+ public void dispatchInvalidateOnAnimation(View view) {
+ mInvalidateOnAnimationRunnable.addView(view);
+ }
+
+ public void dispatchInvalidateRectOnAnimation(AttachInfo.InvalidateInfo info) {
+ mInvalidateOnAnimationRunnable.addViewRect(info);
+ }
+
+ public void cancelInvalidate(View view) {
+ mHandler.removeMessages(MSG_INVALIDATE, view);
+ // fixme: might leak the AttachInfo.InvalidateInfo objects instead of returning
+ // them to the pool
+ mHandler.removeMessages(MSG_INVALIDATE_RECT, view);
+ mInvalidateOnAnimationRunnable.removeView(view);
+ }
+
+ public void dispatchInputEvent(InputEvent event) {
+ dispatchInputEvent(event, null);
+ }
+
+ public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = event;
+ args.arg2 = receiver;
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
+ public void synthesizeInputEvent(InputEvent event) {
+ Message msg = mHandler.obtainMessage(MSG_SYNTHESIZE_INPUT_EVENT, event);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchKeyFromIme(KeyEvent event) {
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Reinject unhandled {@link InputEvent}s in order to synthesize fallbacks events.
+ *
+ * Note that it is the responsibility of the caller of this API to recycle the InputEvent it
+ * passes in.
+ */
+ public void dispatchUnhandledInputEvent(InputEvent event) {
+ if (event instanceof MotionEvent) {
+ event = MotionEvent.obtain((MotionEvent) event);
+ }
+ synthesizeInputEvent(event);
+ }
+
+ public void dispatchAppVisibility(boolean visible) {
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY);
+ msg.arg1 = visible ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchGetNewSurface() {
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_GET_NEW_SURFACE);
+ mHandler.sendMessage(msg);
+ }
+
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+ Message msg = Message.obtain();
+ msg.what = MSG_WINDOW_FOCUS_CHANGED;
+ msg.arg1 = hasFocus ? 1 : 0;
+ msg.arg2 = inTouchMode ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchWindowShown() {
+ mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN);
+ }
+
+ public void dispatchCloseSystemDialogs(String reason) {
+ Message msg = Message.obtain();
+ msg.what = MSG_CLOSE_SYSTEM_DIALOGS;
+ msg.obj = reason;
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchDragEvent(DragEvent event) {
+ final int what;
+ if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
+ what = MSG_DISPATCH_DRAG_LOCATION_EVENT;
+ mHandler.removeMessages(what);
+ } else {
+ what = MSG_DISPATCH_DRAG_EVENT;
+ }
+ Message msg = mHandler.obtainMessage(what, event);
+ mHandler.sendMessage(msg);
+ }
+
+ public void updatePointerIcon(float x, float y) {
+ final int what = MSG_UPDATE_POINTER_ICON;
+ mHandler.removeMessages(what);
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent event = MotionEvent.obtain(
+ 0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
+ Message msg = mHandler.obtainMessage(what, event);
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+ int localValue, int localChanges) {
+ SystemUiVisibilityInfo args = new SystemUiVisibilityInfo();
+ args.seq = seq;
+ args.globalVisibility = globalVisibility;
+ args.localValue = localValue;
+ args.localChanges = localChanges;
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
+ }
+
+ public void dispatchCheckFocus() {
+ if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
+ // This will result in a call to checkFocus() below.
+ mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);
+ }
+ }
+
+ public void dispatchRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+ mHandler.obtainMessage(
+ MSG_REQUEST_KEYBOARD_SHORTCUTS, deviceId, 0, receiver).sendToTarget();
+ }
+
+ public void dispatchPointerCaptureChanged(boolean on) {
+ final int what = MSG_POINTER_CAPTURE_CHANGED;
+ mHandler.removeMessages(what);
+ Message msg = mHandler.obtainMessage(what);
+ msg.arg1 = on ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Post a callback to send a
+ * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
+ * This event is send at most once every
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
+ */
+ private void postSendWindowContentChangedCallback(View source, int changeType) {
+ if (mSendWindowContentChangedAccessibilityEvent == null) {
+ mSendWindowContentChangedAccessibilityEvent =
+ new SendWindowContentChangedAccessibilityEvent();
+ }
+ mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType);
+ }
+
+ /**
+ * Remove a posted callback to send a
+ * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
+ */
+ private void removeSendWindowContentChangedCallback() {
+ if (mSendWindowContentChangedAccessibilityEvent != null) {
+ mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
+ }
+ }
+
+ @Override
+ public boolean showContextMenuForChild(View originalView) {
+ return false;
+ }
+
+ @Override
+ public boolean showContextMenuForChild(View originalView, float x, float y) {
+ return false;
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+ return null;
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(
+ View originalView, ActionMode.Callback callback, int type) {
+ return null;
+ }
+
+ @Override
+ public void createContextMenu(ContextMenu menu) {
+ }
+
+ @Override
+ public void childDrawableStateChanged(View child) {
+ }
+
+ @Override
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (mView == null || mStopped || mPausedForTransition) {
+ return false;
+ }
+
+ // Immediately flush pending content changed event (if any) to preserve event order
+ if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ && mSendWindowContentChangedAccessibilityEvent != null
+ && mSendWindowContentChangedAccessibilityEvent.mSource != null) {
+ mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();
+ }
+
+ // Intercept accessibility focus events fired by virtual nodes to keep
+ // track of accessibility focus position in such nodes.
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+ final long sourceNodeId = event.getSourceNodeId();
+ final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+ sourceNodeId);
+ View source = mView.findViewByAccessibilityId(accessibilityViewId);
+ if (source != null) {
+ AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
+ if (provider != null) {
+ final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+ sourceNodeId);
+ final AccessibilityNodeInfo node;
+ node = provider.createAccessibilityNodeInfo(virtualNodeId);
+ setAccessibilityFocus(source, node);
+ }
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ final long sourceNodeId = event.getSourceNodeId();
+ final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+ sourceNodeId);
+ View source = mView.findViewByAccessibilityId(accessibilityViewId);
+ if (source != null) {
+ AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
+ if (provider != null) {
+ setAccessibilityFocus(null, null);
+ }
+ }
+ } break;
+
+
+ case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
+ handleWindowContentChangedEvent(event);
+ } break;
+ }
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ return true;
+ }
+
+ /**
+ * Updates the focused virtual view, when necessary, in response to a
+ * content changed event.
+ * <p>
+ * This is necessary to get updated bounds after a position change.
+ *
+ * @param event an accessibility event of type
+ * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+ */
+ private void handleWindowContentChangedEvent(AccessibilityEvent event) {
+ final View focusedHost = mAccessibilityFocusedHost;
+ if (focusedHost == null || mAccessibilityFocusedVirtualView == null) {
+ // No virtual view focused, nothing to do here.
+ return;
+ }
+
+ final AccessibilityNodeProvider provider = focusedHost.getAccessibilityNodeProvider();
+ if (provider == null) {
+ // Error state: virtual view with no provider. Clear focus.
+ mAccessibilityFocusedHost = null;
+ mAccessibilityFocusedVirtualView = null;
+ focusedHost.clearAccessibilityFocusNoCallbacks(0);
+ return;
+ }
+
+ // We only care about change types that may affect the bounds of the
+ // focused virtual view.
+ final int changes = event.getContentChangeTypes();
+ if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) == 0
+ && changes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) {
+ return;
+ }
+
+ final long eventSourceNodeId = event.getSourceNodeId();
+ final int changedViewId = AccessibilityNodeInfo.getAccessibilityViewId(eventSourceNodeId);
+
+ // Search up the tree for subtree containment.
+ boolean hostInSubtree = false;
+ View root = mAccessibilityFocusedHost;
+ while (root != null && !hostInSubtree) {
+ if (changedViewId == root.getAccessibilityViewId()) {
+ hostInSubtree = true;
+ } else {
+ final ViewParent parent = root.getParent();
+ if (parent instanceof View) {
+ root = (View) parent;
+ } else {
+ root = null;
+ }
+ }
+ }
+
+ // We care only about changes in subtrees containing the host view.
+ if (!hostInSubtree) {
+ return;
+ }
+
+ final long focusedSourceNodeId = mAccessibilityFocusedVirtualView.getSourceNodeId();
+ int focusedChildId = AccessibilityNodeInfo.getVirtualDescendantId(focusedSourceNodeId);
+
+ // Refresh the node for the focused virtual view.
+ final Rect oldBounds = mTempRect;
+ mAccessibilityFocusedVirtualView.getBoundsInScreen(oldBounds);
+ mAccessibilityFocusedVirtualView = provider.createAccessibilityNodeInfo(focusedChildId);
+ if (mAccessibilityFocusedVirtualView == null) {
+ // Error state: The node no longer exists. Clear focus.
+ mAccessibilityFocusedHost = null;
+ focusedHost.clearAccessibilityFocusNoCallbacks(0);
+
+ // This will probably fail, but try to keep the provider's internal
+ // state consistent by clearing focus.
+ provider.performAction(focusedChildId,
+ AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), null);
+ invalidateRectOnScreen(oldBounds);
+ } else {
+ // The node was refreshed, invalidate bounds if necessary.
+ final Rect newBounds = mAccessibilityFocusedVirtualView.getBoundsInScreen();
+ if (!oldBounds.equals(newBounds)) {
+ oldBounds.union(newBounds);
+ invalidateRectOnScreen(oldBounds);
+ }
+ }
+ }
+
+ @Override
+ public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+ postSendWindowContentChangedCallback(Preconditions.checkNotNull(source), changeType);
+ }
+
+ @Override
+ public boolean canResolveLayoutDirection() {
+ return true;
+ }
+
+ @Override
+ public boolean isLayoutDirectionResolved() {
+ return true;
+ }
+
+ @Override
+ public int getLayoutDirection() {
+ return View.LAYOUT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ @Override
+ public boolean canResolveTextDirection() {
+ return true;
+ }
+
+ @Override
+ public boolean isTextDirectionResolved() {
+ return true;
+ }
+
+ @Override
+ public int getTextDirection() {
+ return View.TEXT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ @Override
+ public boolean canResolveTextAlignment() {
+ return true;
+ }
+
+ @Override
+ public boolean isTextAlignmentResolved() {
+ return true;
+ }
+
+ @Override
+ public int getTextAlignment() {
+ return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ }
+
+ private View getCommonPredecessor(View first, View second) {
+ if (mTempHashSet == null) {
+ mTempHashSet = new HashSet<View>();
+ }
+ HashSet<View> seen = mTempHashSet;
+ seen.clear();
+ View firstCurrent = first;
+ while (firstCurrent != null) {
+ seen.add(firstCurrent);
+ ViewParent firstCurrentParent = firstCurrent.mParent;
+ if (firstCurrentParent instanceof View) {
+ firstCurrent = (View) firstCurrentParent;
+ } else {
+ firstCurrent = null;
+ }
+ }
+ View secondCurrent = second;
+ while (secondCurrent != null) {
+ if (seen.contains(secondCurrent)) {
+ seen.clear();
+ return secondCurrent;
+ }
+ ViewParent secondCurrentParent = secondCurrent.mParent;
+ if (secondCurrentParent instanceof View) {
+ secondCurrent = (View) secondCurrentParent;
+ } else {
+ secondCurrent = null;
+ }
+ }
+ seen.clear();
+ return null;
+ }
+
+ void checkThread() {
+ if (mThread != Thread.currentThread()) {
+ throw new CalledFromWrongThreadException(
+ "Only the original thread that created a view hierarchy can touch its views.");
+ }
+ }
+
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ // ViewAncestor never intercepts touch event, so this can be a no-op
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ if (rectangle == null) {
+ return scrollToRectOrFocus(null, immediate);
+ }
+ rectangle.offset(child.getLeft() - child.getScrollX(),
+ child.getTop() - child.getScrollY());
+ final boolean scrolled = scrollToRectOrFocus(rectangle, immediate);
+ mTempRect.set(rectangle);
+ mTempRect.offset(0, -mCurScrollY);
+ mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+ try {
+ mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ return scrolled;
+ }
+
+ @Override
+ public void childHasTransientStateChanged(View child, boolean hasTransientState) {
+ // Do nothing.
+ }
+
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return false;
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ }
+
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ return false;
+ }
+
+ @Override
+ public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+ return false;
+ }
+
+ @Override
+ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
+ return false;
+ }
+
+
+ private void reportNextDraw() {
+ if (mReportNextDraw == false) {
+ drawPending();
+ }
+ mReportNextDraw = true;
+ }
+
+ /**
+ * Force the window to report its next draw.
+ * <p>
+ * This method is only supposed to be used to speed up the interaction from SystemUI and window
+ * manager when waiting for the first frame to be drawn when turning on the screen. DO NOT USE
+ * unless you fully understand this interaction.
+ * @hide
+ */
+ public void setReportNextDraw() {
+ reportNextDraw();
+ invalidate();
+ }
+
+ void changeCanvasOpacity(boolean opaque) {
+ Log.d(mTag, "changeCanvasOpacity: opaque=" + opaque);
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.setOpaque(opaque);
+ }
+ }
+
+ class TakenSurfaceHolder extends BaseSurfaceHolder {
+ @Override
+ public boolean onAllowLockCanvas() {
+ return mDrawingAllowed;
+ }
+
+ @Override
+ public void onRelayoutContainer() {
+ // Not currently interesting -- from changing between fixed and layout size.
+ }
+
+ @Override
+ public void setFormat(int format) {
+ ((RootViewSurfaceTaker)mView).setSurfaceFormat(format);
+ }
+
+ @Override
+ public void setType(int type) {
+ ((RootViewSurfaceTaker)mView).setSurfaceType(type);
+ }
+
+ @Override
+ public void onUpdateSurface() {
+ // We take care of format and type changes on our own.
+ throw new IllegalStateException("Shouldn't be here");
+ }
+
+ @Override
+ public boolean isCreating() {
+ return mIsCreating;
+ }
+
+ @Override
+ public void setFixedSize(int width, int height) {
+ throw new UnsupportedOperationException(
+ "Currently only support sizing from layout");
+ }
+
+ @Override
+ public void setKeepScreenOn(boolean screenOn) {
+ ((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn);
+ }
+ }
+
+ static class W extends IWindow.Stub {
+ private final WeakReference<ViewRootImpl> mViewAncestor;
+ private final IWindowSession mWindowSession;
+
+ W(ViewRootImpl viewAncestor) {
+ mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
+ mWindowSession = viewAncestor.mWindowSession;
+ }
+
+ @Override
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
+ Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
+ MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
+ boolean alwaysConsumeNavBar, int displayId) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
+ visibleInsets, stableInsets, outsets, reportDraw, mergedConfiguration,
+ backDropFrame, forceLayout, alwaysConsumeNavBar, displayId);
+ }
+ }
+
+ @Override
+ public void moved(int newX, int newY) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchMoved(newX, newY);
+ }
+ }
+
+ @Override
+ public void dispatchAppVisibility(boolean visible) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchAppVisibility(visible);
+ }
+ }
+
+ @Override
+ public void dispatchGetNewSurface() {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchGetNewSurface();
+ }
+ }
+
+ @Override
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
+ }
+ }
+
+ private static int checkCallingPermission(String permission) {
+ try {
+ return ActivityManager.getService().checkPermission(
+ permission, Binder.getCallingPid(), Binder.getCallingUid());
+ } catch (RemoteException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+
+ @Override
+ public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ final View view = viewAncestor.mView;
+ if (view != null) {
+ if (checkCallingPermission(Manifest.permission.DUMP) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Insufficient permissions to invoke"
+ + " executeCommand() from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ }
+
+ OutputStream clientStream = null;
+ try {
+ clientStream = new ParcelFileDescriptor.AutoCloseOutputStream(out);
+ ViewDebug.dispatchCommand(view, command, parameters, clientStream);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (clientStream != null) {
+ try {
+ clientStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void closeSystemDialogs(String reason) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchCloseSystemDialogs(reason);
+ }
+ }
+
+ @Override
+ public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+ boolean sync) {
+ if (sync) {
+ try {
+ mWindowSession.wallpaperOffsetsComplete(asBinder());
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void dispatchWallpaperCommand(String action, int x, int y,
+ int z, Bundle extras, boolean sync) {
+ if (sync) {
+ try {
+ mWindowSession.wallpaperCommandComplete(asBinder(), null);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /* Drag/drop */
+ @Override
+ public void dispatchDragEvent(DragEvent event) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchDragEvent(event);
+ }
+ }
+
+ @Override
+ public void updatePointerIcon(float x, float y) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.updatePointerIcon(x, y);
+ }
+ }
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+ int localValue, int localChanges) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchSystemUiVisibilityChanged(seq, globalVisibility,
+ localValue, localChanges);
+ }
+ }
+
+ @Override
+ public void dispatchWindowShown() {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchWindowShown();
+ }
+ }
+
+ @Override
+ public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+ ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchRequestKeyboardShortcuts(receiver, deviceId);
+ }
+ }
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
+ }
+
+ public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
+ public CalledFromWrongThreadException(String msg) {
+ super(msg);
+ }
+ }
+
+ static HandlerActionQueue getRunQueue() {
+ HandlerActionQueue rq = sRunQueues.get();
+ if (rq != null) {
+ return rq;
+ }
+ rq = new HandlerActionQueue();
+ sRunQueues.set(rq);
+ return rq;
+ }
+
+ /**
+ * Start a drag resizing which will inform all listeners that a window resize is taking place.
+ */
+ private void startDragResizing(Rect initialBounds, boolean fullscreen, Rect systemInsets,
+ Rect stableInsets, int resizeMode) {
+ if (!mDragResizing) {
+ mDragResizing = true;
+ for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+ mWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds, fullscreen,
+ systemInsets, stableInsets, resizeMode);
+ }
+ mFullRedrawNeeded = true;
+ }
+ }
+
+ /**
+ * End a drag resize which will inform all listeners that a window resize has ended.
+ */
+ private void endDragResizing() {
+ if (mDragResizing) {
+ mDragResizing = false;
+ for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+ mWindowCallbacks.get(i).onWindowDragResizeEnd();
+ }
+ mFullRedrawNeeded = true;
+ }
+ }
+
+ private boolean updateContentDrawBounds() {
+ boolean updated = false;
+ for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+ updated |= mWindowCallbacks.get(i).onContentDrawn(
+ mWindowAttributes.surfaceInsets.left,
+ mWindowAttributes.surfaceInsets.top,
+ mWidth, mHeight);
+ }
+ return updated | (mDragResizing && mReportNextDraw);
+ }
+
+ private void requestDrawWindow() {
+ if (mReportNextDraw) {
+ mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size());
+ }
+ for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+ mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw);
+ }
+ }
+
+ /**
+ * Tells this instance that its corresponding activity has just relaunched. In this case, we
+ * need to force a relayout of the window to make sure we get the correct bounds from window
+ * manager.
+ */
+ public void reportActivityRelaunched() {
+ mActivityRelaunched = true;
+ }
+
+ /**
+ * Class for managing the accessibility interaction connection
+ * based on the global accessibility state.
+ */
+ final class AccessibilityInteractionConnectionManager
+ implements AccessibilityStateChangeListener {
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ if (enabled) {
+ ensureConnection();
+ if (mAttachInfo.mHasWindowFocus) {
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ View focusedView = mView.findFocus();
+ if (focusedView != null && focusedView != mView) {
+ focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ }
+ } else {
+ ensureNoConnection();
+ mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget();
+ }
+ }
+
+ public void ensureConnection() {
+ final boolean registered = mAttachInfo.mAccessibilityWindowId
+ != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ if (!registered) {
+ mAttachInfo.mAccessibilityWindowId =
+ mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
+ new AccessibilityInteractionConnection(ViewRootImpl.this));
+ }
+ }
+
+ public void ensureNoConnection() {
+ final boolean registered = mAttachInfo.mAccessibilityWindowId
+ != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ if (registered) {
+ mAttachInfo.mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
+ }
+ }
+ }
+
+ final class HighContrastTextManager implements HighTextContrastChangeListener {
+ HighContrastTextManager() {
+ ThreadedRenderer.setHighContrastText(mAccessibilityManager.isHighTextContrastEnabled());
+ }
+ @Override
+ public void onHighTextContrastStateChanged(boolean enabled) {
+ ThreadedRenderer.setHighContrastText(enabled);
+
+ // Destroy Displaylists so they can be recreated with high contrast recordings
+ destroyHardwareResources();
+
+ // Schedule redraw, which will rerecord + redraw all text
+ invalidate();
+ }
+ }
+
+ /**
+ * This class is an interface this ViewAncestor provides to the
+ * AccessibilityManagerService to the latter can interact with
+ * the view hierarchy in this ViewAncestor.
+ */
+ static final class AccessibilityInteractionConnection
+ extends IAccessibilityInteractionConnection.Stub {
+ private final WeakReference<ViewRootImpl> mViewRootImpl;
+
+ AccessibilityInteractionConnection(ViewRootImpl viewRootImpl) {
+ mViewRootImpl = new WeakReference<ViewRootImpl>(viewRootImpl);
+ }
+
+ @Override
+ public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
+ Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
+ interactiveRegion, interactionId, callback, flags, interrogatingPid,
+ interrogatingTid, spec, args);
+ } else {
+ // We cannot make the call and notify the caller so it does not wait.
+ try {
+ callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
+
+ @Override
+ public void performAccessibilityAction(long accessibilityNodeId, int action,
+ Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .performAccessibilityActionClientThread(accessibilityNodeId, action, arguments,
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ } else {
+ // We cannot make the call and notify the caller so it does not wait.
+ try {
+ callback.setPerformAccessibilityActionResult(false, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
+
+ @Override
+ public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+ String viewId, Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByViewIdClientThread(accessibilityNodeId,
+ viewId, interactiveRegion, interactionId, callback, flags,
+ interrogatingPid, interrogatingTid, spec);
+ } else {
+ // We cannot make the call and notify the caller so it does not wait.
+ try {
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
+
+ @Override
+ public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+ Region interactiveRegion, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
+ interactiveRegion, interactionId, callback, flags, interrogatingPid,
+ interrogatingTid, spec);
+ } else {
+ // We cannot make the call and notify the caller so it does not wait.
+ try {
+ callback.setFindAccessibilityNodeInfosResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
+
+ @Override
+ public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .findFocusClientThread(accessibilityNodeId, focusType, interactiveRegion,
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
+ } else {
+ // We cannot make the call and notify the caller so it does not wait.
+ try {
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
+
+ @Override
+ public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .focusSearchClientThread(accessibilityNodeId, direction, interactiveRegion,
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
+ } else {
+ // We cannot make the call and notify the caller so it does not wait.
+ try {
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
+ }
+
+ private class SendWindowContentChangedAccessibilityEvent implements Runnable {
+ private int mChangeTypes = 0;
+
+ public View mSource;
+ public long mLastEventTimeMillis;
+
+ @Override
+ public void run() {
+ // Protect against re-entrant code and attempt to do the right thing in the case that
+ // we're multithreaded.
+ View source = mSource;
+ mSource = null;
+ if (source == null) {
+ Log.e(TAG, "Accessibility content change has no source");
+ return;
+ }
+ // The accessibility may be turned off while we were waiting so check again.
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ mLastEventTimeMillis = SystemClock.uptimeMillis();
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(mChangeTypes);
+ source.sendAccessibilityEventUnchecked(event);
+ } else {
+ mLastEventTimeMillis = 0;
+ }
+ // In any case reset to initial state.
+ source.resetSubtreeAccessibilityStateChanged();
+ mChangeTypes = 0;
+ }
+
+ public void runOrPost(View source, int changeType) {
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the "
+ + "original thread that created a view hierarchy can touch its views.");
+ // TODO: Throw the exception
+ Log.e(TAG, "Accessibility content change on non-UI thread. Future Android "
+ + "versions will throw an exception.", e);
+ // Attempt to recover. This code does not eliminate the thread safety issue, but
+ // it should force any issues to happen near the above log.
+ mHandler.removeCallbacks(this);
+ if (mSource != null) {
+ // Dispatch whatever was pending. It's still possible that the runnable started
+ // just before we removed the callbacks, and bad things will happen, but at
+ // least they should happen very close to the logged error.
+ run();
+ }
+ }
+ if (mSource != null) {
+ // If there is no common predecessor, then mSource points to
+ // a removed view, hence in this case always prefer the source.
+ View predecessor = getCommonPredecessor(mSource, source);
+ mSource = (predecessor != null) ? predecessor : source;
+ mChangeTypes |= changeType;
+ return;
+ }
+ mSource = source;
+ mChangeTypes = changeType;
+ final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
+ final long minEventIntevalMillis =
+ ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
+ if (timeSinceLastMillis >= minEventIntevalMillis) {
+ removeCallbacksAndRun();
+ } else {
+ mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
+ }
+ }
+
+ public void removeCallbacksAndRun() {
+ mHandler.removeCallbacks(this);
+ run();
+ }
+ }
+}
diff --git a/android/view/ViewRootImpl_Accessor.java b/android/view/ViewRootImpl_Accessor.java
new file mode 100644
index 00000000..0e15b972
--- /dev/null
+++ b/android/view/ViewRootImpl_Accessor.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * Accessor to allow layoutlib to call {@link ViewRootImpl#dispatchApplyInsets} directly.
+ */
+public class ViewRootImpl_Accessor {
+ public static void dispatchApplyInsets(ViewRootImpl viewRoot, View host) {
+ viewRoot.dispatchApplyInsets(host);
+ }
+}
diff --git a/android/view/ViewRootImpl_Delegate.java b/android/view/ViewRootImpl_Delegate.java
new file mode 100644
index 00000000..14b84ef5
--- /dev/null
+++ b/android/view/ViewRootImpl_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewRootImpl}
+ *
+ * Through the layoutlib_create tool, the original methods of ViewRootImpl have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ViewRootImpl_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isInTouchMode() {
+ return false; // this allows displaying selection.
+ }
+}
diff --git a/android/view/ViewShowHidePerfTest.java b/android/view/ViewShowHidePerfTest.java
new file mode 100644
index 00000000..6159da4f
--- /dev/null
+++ b/android/view/ViewShowHidePerfTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2016 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 static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.view.View.MeasureSpec;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+@LargeTest
+public class ViewShowHidePerfTest {
+
+ @Rule
+ public ActivityTestRule mActivityRule = new ActivityTestRule(StubActivity.class);
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ public Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ static abstract class SubTreeFactory {
+ String mName;
+ SubTreeFactory(String name) { mName = name; }
+
+ abstract View create(Context context, int depth);
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
+
+ private static SubTreeFactory[] sSubTreeFactories = new SubTreeFactory[] {
+ new SubTreeFactory("NestedLinearLayoutTree") {
+ private int mColorToggle = 0;
+
+ private void createNestedLinearLayoutTree(Context context, LinearLayout parent,
+ int remainingDepth) {
+ if (remainingDepth <= 0) {
+ mColorToggle = (mColorToggle + 1) % 4;
+ parent.setBackgroundColor((mColorToggle < 2) ? Color.RED : Color.BLUE);
+ return;
+ }
+
+ boolean vertical = remainingDepth % 2 == 0;
+ parent.setOrientation(vertical ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+
+ for (int i = 0; i < 2; i++) {
+ LinearLayout child = new LinearLayout(context);
+ // vertical: match parent in x axis, horizontal: y axis.
+ parent.addView(child, new LinearLayout.LayoutParams(
+ (vertical ? ViewGroup.LayoutParams.MATCH_PARENT : 0),
+ (vertical ? 0 : ViewGroup.LayoutParams.MATCH_PARENT),
+ 1.0f));
+
+ createNestedLinearLayoutTree(context, child, remainingDepth - 1);
+ }
+ }
+
+ @Override
+ public View create(Context context, int depth) {
+ LinearLayout root = new LinearLayout(context);
+ createNestedLinearLayoutTree(context, root, depth - 1);
+ return root;
+ }
+ },
+ new SubTreeFactory("ImageViewList") {
+ @Override
+ public View create(Context context, int depth) {
+ LinearLayout root = new LinearLayout(context);
+ root.setOrientation(LinearLayout.HORIZONTAL);
+ int childCount = (int) Math.pow(2, depth);
+ for (int i = 0; i < childCount; i++) {
+ ImageView imageView = new ImageView(context);
+ root.addView(imageView, new LinearLayout.LayoutParams(
+ 0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f));
+ imageView.setImageDrawable(new ColorDrawable(Color.RED));
+ }
+ return root;
+ }
+ },
+ };
+
+
+ @Parameterized.Parameters(name = "Factory:{0},depth:{1}")
+ public static Iterable<Object[]> params() {
+ List<Object[]> params = new ArrayList<>();
+ for (int depth : new int[] { 6 }) {
+ for (SubTreeFactory subTreeFactory : sSubTreeFactories) {
+ params.add(new Object[]{ subTreeFactory, depth });
+ }
+ }
+ return params;
+ }
+
+ private final View mChild;
+
+ public ViewShowHidePerfTest(SubTreeFactory subTreeFactory, int depth) {
+ mChild = subTreeFactory.create(getContext(), depth);
+ }
+
+ interface TestCallback {
+ void run(BenchmarkState state, int width, int height, ViewGroup parent, View child);
+ }
+
+ private void testParentWithChild(TestCallback callback) throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ FrameLayout parent = new FrameLayout(getContext());
+ mActivityRule.getActivity().setContentView(parent);
+
+ final int width = 1000;
+ final int height = 1000;
+ layout(width, height, parent);
+
+ callback.run(state, width, height, parent, mChild);
+ });
+ }
+
+ private void updateAndValidateDisplayList(View view) {
+ boolean hasDisplayList = view.updateDisplayListIfDirty().isValid();
+ assertTrue(hasDisplayList);
+ }
+
+ private void layout(int width, int height, View view) {
+ view.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ view.layout(0, 0, height, width);
+ }
+
+ @Test
+ public void testRemove() throws Throwable {
+ testParentWithChild((state, width, height, parent, child) -> {
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed
+ parent.addView(child);
+ layout(width, height, child);
+ updateAndValidateDisplayList(parent);
+ state.resumeTiming();
+
+ parent.removeAllViews();
+ }
+ });
+ }
+
+ @Test
+ public void testAdd() throws Throwable {
+ testParentWithChild((state, width, height, parent, child) -> {
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ layout(width, height, child); // Note, done to be safe, likely not needed
+ updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed
+ parent.removeAllViews();
+ updateAndValidateDisplayList(parent);
+ state.resumeTiming();
+
+ parent.addView(child);
+ }
+ });
+ }
+
+ @Test
+ public void testRecordAfterAdd() throws Throwable {
+ testParentWithChild((state, width, height, parent, child) -> {
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ parent.removeAllViews();
+ updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed
+ parent.addView(child);
+ layout(width, height, child);
+ state.resumeTiming();
+
+ updateAndValidateDisplayList(parent);
+ }
+ });
+ }
+
+ private void testVisibility(int fromVisibility, int toVisibility) throws Throwable {
+ testParentWithChild((state, width, height, parent, child) -> {
+ parent.addView(child);
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ layout(width, height, parent);
+ updateAndValidateDisplayList(parent);
+ child.setVisibility(fromVisibility);
+ layout(width, height, parent);
+ updateAndValidateDisplayList(parent);
+ state.resumeTiming();
+
+ child.setVisibility(toVisibility);
+ }
+ });
+ }
+
+ @Test
+ public void testInvisibleToVisible() throws Throwable {
+ testVisibility(View.INVISIBLE, View.VISIBLE);
+ }
+
+ @Test
+ public void testVisibleToInvisible() throws Throwable {
+ testVisibility(View.VISIBLE, View.INVISIBLE);
+ }
+ @Test
+ public void testGoneToVisible() throws Throwable {
+ testVisibility(View.GONE, View.VISIBLE);
+ }
+
+ @Test
+ public void testVisibleToGone() throws Throwable {
+ testVisibility(View.VISIBLE, View.GONE);
+ }
+}
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
new file mode 100644
index 00000000..0ecd20da
--- /dev/null
+++ b/android/view/ViewStructure.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2015 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.Matrix;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import java.util.List;
+
+/**
+ * Container for storing additional per-view data generated by {@link View#onProvideStructure
+ * View.onProvideStructure} and {@link View#onProvideAutofillStructure
+ * View.onProvideAutofillStructure}.
+ */
+public abstract class ViewStructure {
+
+ /**
+ * Set the identifier for this view.
+ *
+ * @param id The view's identifier, as per {@link View#getId View.getId()}.
+ * @param packageName The package name of the view's identifier, or null if there is none.
+ * @param typeName The type name of the view's identifier, or null if there is none.
+ * @param entryName The entry name of the view's identifier, or null if there is none.
+ */
+ public abstract void setId(int id, String packageName, String typeName, String entryName);
+
+ /**
+ * Set the basic dimensions of this view.
+ *
+ * @param left The view's left position, in pixels relative to its parent's left edge.
+ * @param top The view's top position, in pixels relative to its parent's top edge.
+ * @param scrollX How much the view's x coordinate space has been scrolled, in pixels.
+ * @param scrollY How much the view's y coordinate space has been scrolled, in pixels.
+ * @param width The view's visible width, in pixels. This is the width visible on screen,
+ * not the total data width of a scrollable view.
+ * @param height The view's visible height, in pixels. This is the height visible on
+ * screen, not the total data height of a scrollable view.
+ */
+ public abstract void setDimens(int left, int top, int scrollX, int scrollY, int width,
+ int height);
+
+ /**
+ * Set the transformation matrix associated with this view, as per
+ * {@link View#getMatrix View.getMatrix()}, or null if there is none.
+ */
+ public abstract void setTransformation(Matrix matrix);
+
+ /**
+ * Set the visual elevation (shadow) of the view, as per
+ * {@link View#getZ View.getZ()}. Note this is <em>not</em> related
+ * to the physical Z-ordering of this view relative to its other siblings (that is how
+ * they overlap when drawing), it is only the visual representation for shadowing.
+ */
+ public abstract void setElevation(float elevation);
+
+ /**
+ * Set an alpha transformation that is applied to this view, as per
+ * {@link View#getAlpha View.getAlpha()}. Value ranges from 0
+ * (completely transparent) to 1 (completely opaque); the default is 1, which means
+ * no transformation.
+ */
+ public abstract void setAlpha(float alpha);
+
+ /**
+ * Set the visibility state of this view, as per
+ * {@link View#getVisibility View.getVisibility()}.
+ */
+ public abstract void setVisibility(int visibility);
+
+ /** @hide */
+ public abstract void setAssistBlocked(boolean state);
+
+ /**
+ * Set the enabled state of this view, as per {@link View#isEnabled View.isEnabled()}.
+ */
+ public abstract void setEnabled(boolean state);
+
+ /**
+ * Set the clickable state of this view, as per {@link View#isClickable View.isClickable()}.
+ */
+ public abstract void setClickable(boolean state);
+
+ /**
+ * Set the long clickable state of this view, as per
+ * {@link View#isLongClickable View.isLongClickable()}.
+ */
+ public abstract void setLongClickable(boolean state);
+
+ /**
+ * Set the context clickable state of this view, as per
+ * {@link View#isContextClickable View.isContextClickable()}.
+ */
+ public abstract void setContextClickable(boolean state);
+
+ /**
+ * Set the focusable state of this view, as per {@link View#isFocusable View.isFocusable()}.
+ */
+ public abstract void setFocusable(boolean state);
+
+ /**
+ * Set the focused state of this view, as per {@link View#isFocused View.isFocused()}.
+ */
+ public abstract void setFocused(boolean state);
+
+ /**
+ * Set the accessibility focused state of this view, as per
+ * {@link View#isAccessibilityFocused View.isAccessibilityFocused()}.
+ */
+ public abstract void setAccessibilityFocused(boolean state);
+
+ /**
+ * Set the checkable state of this view, such as whether it implements the
+ * {@link android.widget.Checkable} interface.
+ */
+ public abstract void setCheckable(boolean state);
+
+ /**
+ * Set the checked state of this view, such as
+ * {@link android.widget.Checkable#isChecked Checkable.isChecked()}.
+ */
+ public abstract void setChecked(boolean state);
+
+ /**
+ * Set the selected state of this view, as per {@link View#isSelected View.isSelected()}.
+ */
+ public abstract void setSelected(boolean state);
+
+ /**
+ * Set the activated state of this view, as per {@link View#isActivated View.isActivated()}.
+ */
+ public abstract void setActivated(boolean state);
+
+ /**
+ * Set the opaque state of this view, as per {@link View#isOpaque View.isOpaque()}.
+ */
+ public abstract void setOpaque(boolean opaque);
+
+ /**
+ * Set the class name of the view, as per
+ * {@link View#getAccessibilityClassName View.getAccessibilityClassName()}.
+ */
+ public abstract void setClassName(String className);
+
+ /**
+ * Set the content description of the view, as per
+ * {@link View#getContentDescription View.getContentDescription()}.
+ */
+ public abstract void setContentDescription(CharSequence contentDescription);
+
+ /**
+ * Set the text that is associated with this view. There is no selection
+ * associated with the text. The text may have style spans to supply additional
+ * display and semantic information.
+ */
+ public abstract void setText(CharSequence text);
+
+ /**
+ * Like {@link #setText(CharSequence)} but with an active selection
+ * extending from <var>selectionStart</var> through <var>selectionEnd</var>.
+ */
+ public abstract void setText(CharSequence text, int selectionStart, int selectionEnd);
+
+ /**
+ * Explicitly set default global style information for text that was previously set with
+ * {@link #setText}.
+ *
+ * @param size The size, in pixels, of the text.
+ * @param fgColor The foreground color, packed as 0xAARRGGBB.
+ * @param bgColor The background color, packed as 0xAARRGGBB.
+ * @param style Style flags, as defined by {@link android.app.assist.AssistStructure.ViewNode}.
+ */
+ public abstract void setTextStyle(float size, int fgColor, int bgColor, int style);
+
+ /**
+ * Set line information for test that was previously supplied through
+ * {@link #setText(CharSequence)}. This provides the line breaking of the text as it
+ * is shown on screen. This function takes ownership of the provided arrays; you should
+ * not make further modification to them.
+ *
+ * @param charOffsets The offset in to {@link #setText} where a line starts.
+ * @param baselines The baseline where the line is drawn on screen.
+ */
+ public abstract void setTextLines(int[] charOffsets, int[] baselines);
+
+ /**
+ * Set optional hint text associated with this view; this is for example the text that is
+ * shown by an EditText when it is empty to indicate to the user the kind of text to input.
+ */
+ public abstract void setHint(CharSequence hint);
+
+ /**
+ * Retrieve the last {@link #setText(CharSequence)}.
+ */
+ public abstract CharSequence getText();
+
+ /**
+ * Retrieve the last selection start set by {@link #setText(CharSequence, int, int)}.
+ */
+ public abstract int getTextSelectionStart();
+
+ /**
+ * Retrieve the last selection end set by {@link #setText(CharSequence, int, int)}.
+ */
+ public abstract int getTextSelectionEnd();
+
+ /**
+ * Retrieve the last hint set by {@link #setHint}.
+ */
+ public abstract CharSequence getHint();
+
+ /**
+ * Get extra data associated with this view structure; the returned Bundle is mutable,
+ * allowing you to view and modify its contents. Keys placed in the Bundle should use
+ * an appropriate namespace prefix (such as com.google.MY_KEY) to avoid conflicts.
+ */
+ public abstract Bundle getExtras();
+
+ /**
+ * Returns true if {@link #getExtras} has been used to create extra content.
+ */
+ public abstract boolean hasExtras();
+
+ /**
+ * Set the number of children of this view, which defines the range of indices you can
+ * use with {@link #newChild} and {@link #asyncNewChild}. Calling this method again
+ * resets all of the child state of the view, removing any children that had previously
+ * been added.
+ */
+ public abstract void setChildCount(int num);
+
+ /**
+ * Add to this view's child count. This increases the current child count by
+ * <var>num</var> children beyond what was last set by {@link #setChildCount}
+ * or {@link #addChildCount}. The index at which the new child starts in the child
+ * array is returned.
+ *
+ * @param num The number of new children to add.
+ * @return Returns the index in the child array at which the new children start.
+ */
+ public abstract int addChildCount(int num);
+
+ /**
+ * Return the child count as set by {@link #setChildCount}.
+ */
+ public abstract int getChildCount();
+
+ /**
+ * Create a new child {@link ViewStructure} in this view, putting into the list of
+ * children at <var>index</var>.
+ *
+ * <p><b>NOTE: </b>you must pre-allocate space for the child first, by calling either
+ * {@link #addChildCount(int)} or {@link #setChildCount(int)}.
+ *
+ * @return Returns an fresh {@link ViewStructure} ready to be filled in.
+ */
+ public abstract ViewStructure newChild(int index);
+
+ /**
+ * Like {@link #newChild}, but allows the caller to asynchronously populate the returned
+ * child. It can transfer the returned {@link ViewStructure} to another thread for it
+ * to build its content (and children etc). Once done, some thread must call
+ * {@link #asyncCommit} to tell the containing {@link ViewStructure} that the async
+ * population is done.
+ *
+ * <p><b>NOTE: </b>you must pre-allocate space for the child first, by calling either
+ * {@link #addChildCount(int)} or {@link #setChildCount(int)}.
+ *
+ * @return Returns an fresh {@link ViewStructure} ready to be filled in.
+ */
+ public abstract ViewStructure asyncNewChild(int index);
+
+ /**
+ * Gets the {@link AutofillId} associated with this node.
+ */
+ @Nullable
+ public abstract AutofillId getAutofillId();
+
+ /**
+ * Sets the {@link AutofillId} associated with this node.
+ */
+ public abstract void setAutofillId(@NonNull AutofillId id);
+
+ /**
+ * Sets the {@link AutofillId} for this virtual node.
+ *
+ * @param parentId id of the parent node.
+ * @param virtualId an opaque ID to the Android System; it's the same id used on
+ * {@link View#autofill(android.util.SparseArray)}.
+ */
+ public abstract void setAutofillId(@NonNull AutofillId parentId, int virtualId);
+
+ /**
+ * Sets the {@link View#getAutofillType()} that can be used to autofill this node.
+ */
+ public abstract void setAutofillType(@View.AutofillType int type);
+
+ /**
+ * Sets the a hints that helps the autofill service to select the appropriate data to fill the
+ * view.
+ */
+ public abstract void setAutofillHints(@Nullable String[] hint);
+
+ /**
+ * Sets the {@link AutofillValue} representing the current value of this node.
+ */
+ public abstract void setAutofillValue(AutofillValue value);
+
+ /**
+ * Sets the options that can be used to autofill this node.
+ *
+ * <p>Typically used by nodes whose {@link View#getAutofillType()} is a list to indicate the
+ * meaning of each possible value in the list.
+ */
+ public abstract void setAutofillOptions(CharSequence[] options);
+
+ /**
+ * Sets the {@link android.text.InputType} bits of this node.
+ *
+ * @param inputType inputType bits as defined by {@link android.text.InputType}.
+ */
+ public abstract void setInputType(int inputType);
+
+ /**
+ * Sets whether the data on this node is sensitive; if it is, then its content (text, autofill
+ * value, etc..) is striped before calls to {@link
+ * android.service.autofill.AutofillService#onFillRequest(android.service.autofill.FillRequest,
+ * android.os.CancellationSignal, android.service.autofill.FillCallback)}.
+ *
+ * <p>By default, all nodes are assumed to be sensitive, and only nodes that does not have PII
+ * (Personally Identifiable Information - sensitive data such as email addresses, credit card
+ * numbers, passwords, etc...) should be marked as non-sensitive; a good rule of thumb is to
+ * mark as non-sensitive nodes whose value were statically set from resources.
+ *
+ * <p>Notice that the content of even sensitive nodes are sent to the service (through the
+ * {@link
+ * android.service.autofill.AutofillService#onSaveRequest(android.service.autofill.SaveRequest,
+ * android.service.autofill.SaveCallback)} call) when the user consented to save
+ * thedata, so it is important to set the content of sensitive nodes as well, but mark them as
+ * sensitive.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for Assist.
+ */
+ public abstract void setDataIsSensitive(boolean sensitive);
+
+ /**
+ * Call when done populating a {@link ViewStructure} returned by
+ * {@link #asyncNewChild}.
+ */
+ public abstract void asyncCommit();
+
+ /** @hide */
+ public abstract Rect getTempRect();
+
+ /**
+ * Sets the Web domain represented by this node.
+ *
+ * <p>Typically used when the view is a container for an HTML document.
+ *
+ * @param domain URL representing the domain; only the host part will be used.
+ */
+ public abstract void setWebDomain(@Nullable String domain);
+
+ /**
+ * Sets the the list of locales associated with this node.
+ */
+ public abstract void setLocaleList(LocaleList localeList);
+
+ /**
+ * Creates a new {@link HtmlInfo.Builder} for the given HTML tag.
+ *
+ * @param tagName name of the HTML tag.
+ * @return a new builder.
+ */
+ public abstract HtmlInfo.Builder newHtmlInfoBuilder(@NonNull String tagName);
+
+ /**
+ * Sets the HTML properties of this node when it represents an HTML element.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for assist.
+ *
+ * @param htmlInfo HTML properties.
+ */
+ public abstract void setHtmlInfo(@NonNull HtmlInfo htmlInfo);
+
+ /**
+ * Simplified representation of the HTML properties of a node that represents an HTML element.
+ */
+ public abstract static class HtmlInfo {
+
+ /**
+ * Gets the HTML tag.
+ */
+ @NonNull
+ public abstract String getTag();
+
+ /**
+ * Gets the list of HTML attributes.
+ *
+ * @return list of key/value pairs; could contain pairs with the same keys.
+ */
+ @Nullable
+ public abstract List<Pair<String, String>> getAttributes();
+
+ /**
+ * Builder for {@link HtmlInfo} objects.
+ */
+ public abstract static class Builder {
+
+ /**
+ * Adds an HTML attribute.
+ *
+ * @param name name of the attribute.
+ * @param value value of the attribute.
+ * @return same builder, for chaining.
+ */
+ public abstract Builder addAttribute(@NonNull String name, @NonNull String value);
+
+ /**
+ * Builds the {@link HtmlInfo} object.
+ *
+ * @return a new {@link HtmlInfo} instance.
+ */
+ public abstract HtmlInfo build();
+ }
+ }
+}
diff --git a/android/view/ViewStub.java b/android/view/ViewStub.java
new file mode 100644
index 00000000..e9d1b878
--- /dev/null
+++ b/android/view/ViewStub.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2008 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.IdRes;
+import android.annotation.LayoutRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.RemoteViews.RemoteView;
+
+import com.android.internal.R;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate
+ * layout resources at runtime.
+ *
+ * When a ViewStub is made visible, or when {@link #inflate()} is invoked, the layout resource
+ * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
+ * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or
+ * {@link #inflate()} is invoked.
+ *
+ * The inflated View is added to the ViewStub's parent with the ViewStub's layout
+ * parameters. Similarly, you can define/override the inflate View's id by using the
+ * ViewStub's inflatedId property. For instance:
+ *
+ * <pre>
+ * &lt;ViewStub android:id="@+id/stub"
+ * android:inflatedId="@+id/subTree"
+ * android:layout="@layout/mySubTree"
+ * android:layout_width="120dip"
+ * android:layout_height="40dip" /&gt;
+ * </pre>
+ *
+ * The ViewStub thus defined can be found using the id "stub." After inflation of
+ * the layout resource "mySubTree," the ViewStub is removed from its parent. The
+ * View created by inflating the layout resource "mySubTree" can be found using the
+ * id "subTree," specified by the inflatedId property. The inflated View is finally
+ * assigned a width of 120dip and a height of 40dip.
+ *
+ * The preferred way to perform the inflation of the layout resource is the following:
+ *
+ * <pre>
+ * ViewStub stub = findViewById(R.id.stub);
+ * View inflated = stub.inflate();
+ * </pre>
+ *
+ * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated View
+ * and the inflated View is returned. This lets applications get a reference to the
+ * inflated View without executing an extra findViewById().
+ *
+ * @attr ref android.R.styleable#ViewStub_inflatedId
+ * @attr ref android.R.styleable#ViewStub_layout
+ */
+@RemoteView
+public final class ViewStub extends View {
+ private int mInflatedId;
+ private int mLayoutResource;
+
+ private WeakReference<View> mInflatedViewRef;
+
+ private LayoutInflater mInflater;
+ private OnInflateListener mInflateListener;
+
+ public ViewStub(Context context) {
+ this(context, 0);
+ }
+
+ /**
+ * Creates a new ViewStub with the specified layout resource.
+ *
+ * @param context The application's environment.
+ * @param layoutResource The reference to a layout resource that will be inflated.
+ */
+ public ViewStub(Context context, @LayoutRes int layoutResource) {
+ this(context, null);
+
+ mLayoutResource = layoutResource;
+ }
+
+ public ViewStub(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.ViewStub, defStyleAttr, defStyleRes);
+ mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
+ mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
+ mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
+ a.recycle();
+
+ setVisibility(GONE);
+ setWillNotDraw(true);
+ }
+
+ /**
+ * Returns the id taken by the inflated view. If the inflated id is
+ * {@link View#NO_ID}, the inflated view keeps its original id.
+ *
+ * @return A positive integer used to identify the inflated view or
+ * {@link #NO_ID} if the inflated view should keep its id.
+ *
+ * @see #setInflatedId(int)
+ * @attr ref android.R.styleable#ViewStub_inflatedId
+ */
+ @IdRes
+ public int getInflatedId() {
+ return mInflatedId;
+ }
+
+ /**
+ * Defines the id taken by the inflated view. If the inflated id is
+ * {@link View#NO_ID}, the inflated view keeps its original id.
+ *
+ * @param inflatedId A positive integer used to identify the inflated view or
+ * {@link #NO_ID} if the inflated view should keep its id.
+ *
+ * @see #getInflatedId()
+ * @attr ref android.R.styleable#ViewStub_inflatedId
+ */
+ @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
+ public void setInflatedId(@IdRes int inflatedId) {
+ mInflatedId = inflatedId;
+ }
+
+ /** @hide **/
+ public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
+ mInflatedId = inflatedId;
+ return null;
+ }
+
+ /**
+ * Returns the layout resource that will be used by {@link #setVisibility(int)} or
+ * {@link #inflate()} to replace this StubbedView
+ * in its parent by another view.
+ *
+ * @return The layout resource identifier used to inflate the new View.
+ *
+ * @see #setLayoutResource(int)
+ * @see #setVisibility(int)
+ * @see #inflate()
+ * @attr ref android.R.styleable#ViewStub_layout
+ */
+ @LayoutRes
+ public int getLayoutResource() {
+ return mLayoutResource;
+ }
+
+ /**
+ * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
+ * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
+ * used to replace this StubbedView in its parent.
+ *
+ * @param layoutResource A valid layout resource identifier (different from 0.)
+ *
+ * @see #getLayoutResource()
+ * @see #setVisibility(int)
+ * @see #inflate()
+ * @attr ref android.R.styleable#ViewStub_layout
+ */
+ @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
+ public void setLayoutResource(@LayoutRes int layoutResource) {
+ mLayoutResource = layoutResource;
+ }
+
+ /** @hide **/
+ public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
+ mLayoutResource = layoutResource;
+ return null;
+ }
+
+ /**
+ * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}
+ * to use the default.
+ */
+ public void setLayoutInflater(LayoutInflater inflater) {
+ mInflater = inflater;
+ }
+
+ /**
+ * Get current {@link LayoutInflater} used in {@link #inflate()}.
+ */
+ public LayoutInflater getLayoutInflater() {
+ return mInflater;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(0, 0);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ }
+
+ /**
+ * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
+ * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
+ * by the inflated layout resource. After that calls to this function are passed
+ * through to the inflated view.
+ *
+ * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
+ *
+ * @see #inflate()
+ */
+ @Override
+ @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
+ public void setVisibility(int visibility) {
+ if (mInflatedViewRef != null) {
+ View view = mInflatedViewRef.get();
+ if (view != null) {
+ view.setVisibility(visibility);
+ } else {
+ throw new IllegalStateException("setVisibility called on un-referenced view");
+ }
+ } else {
+ super.setVisibility(visibility);
+ if (visibility == VISIBLE || visibility == INVISIBLE) {
+ inflate();
+ }
+ }
+ }
+
+ /** @hide **/
+ public Runnable setVisibilityAsync(int visibility) {
+ if (visibility == VISIBLE || visibility == INVISIBLE) {
+ ViewGroup parent = (ViewGroup) getParent();
+ return new ViewReplaceRunnable(inflateViewNoAdd(parent));
+ } else {
+ return null;
+ }
+ }
+
+ private View inflateViewNoAdd(ViewGroup parent) {
+ final LayoutInflater factory;
+ if (mInflater != null) {
+ factory = mInflater;
+ } else {
+ factory = LayoutInflater.from(mContext);
+ }
+ final View view = factory.inflate(mLayoutResource, parent, false);
+
+ if (mInflatedId != NO_ID) {
+ view.setId(mInflatedId);
+ }
+ return view;
+ }
+
+ private void replaceSelfWithView(View view, ViewGroup parent) {
+ final int index = parent.indexOfChild(this);
+ parent.removeViewInLayout(this);
+
+ final ViewGroup.LayoutParams layoutParams = getLayoutParams();
+ if (layoutParams != null) {
+ parent.addView(view, index, layoutParams);
+ } else {
+ parent.addView(view, index);
+ }
+ }
+
+ /**
+ * Inflates the layout resource identified by {@link #getLayoutResource()}
+ * and replaces this StubbedView in its parent by the inflated layout resource.
+ *
+ * @return The inflated layout resource.
+ *
+ */
+ public View inflate() {
+ final ViewParent viewParent = getParent();
+
+ if (viewParent != null && viewParent instanceof ViewGroup) {
+ if (mLayoutResource != 0) {
+ final ViewGroup parent = (ViewGroup) viewParent;
+ final View view = inflateViewNoAdd(parent);
+ replaceSelfWithView(view, parent);
+
+ mInflatedViewRef = new WeakReference<>(view);
+ if (mInflateListener != null) {
+ mInflateListener.onInflate(this, view);
+ }
+
+ return view;
+ } else {
+ throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
+ }
+ } else {
+ throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
+ }
+ }
+
+ /**
+ * Specifies the inflate listener to be notified after this ViewStub successfully
+ * inflated its layout resource.
+ *
+ * @param inflateListener The OnInflateListener to notify of successful inflation.
+ *
+ * @see android.view.ViewStub.OnInflateListener
+ */
+ public void setOnInflateListener(OnInflateListener inflateListener) {
+ mInflateListener = inflateListener;
+ }
+
+ /**
+ * Listener used to receive a notification after a ViewStub has successfully
+ * inflated its layout resource.
+ *
+ * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
+ */
+ public static interface OnInflateListener {
+ /**
+ * Invoked after a ViewStub successfully inflated its layout resource.
+ * This method is invoked after the inflated view was added to the
+ * hierarchy but before the layout pass.
+ *
+ * @param stub The ViewStub that initiated the inflation.
+ * @param inflated The inflated View.
+ */
+ void onInflate(ViewStub stub, View inflated);
+ }
+
+ /** @hide **/
+ public class ViewReplaceRunnable implements Runnable {
+ public final View view;
+
+ ViewReplaceRunnable(View view) {
+ this.view = view;
+ }
+
+ @Override
+ public void run() {
+ replaceSelfWithView(view, (ViewGroup) getParent());
+ }
+ }
+}
diff --git a/android/view/ViewTreeObserver.java b/android/view/ViewTreeObserver.java
new file mode 100644
index 00000000..0973d0aa
--- /dev/null
+++ b/android/view/ViewTreeObserver.java
@@ -0,0 +1,1196 @@
+/*
+ * Copyright (C) 2008 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.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A view tree observer is used to register listeners that can be notified of global
+ * changes in the view tree. Such global events include, but are not limited to,
+ * layout of the whole tree, beginning of the drawing pass, touch mode change....
+ *
+ * A ViewTreeObserver should never be instantiated by applications as it is provided
+ * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
+ * for more information.
+ */
+public final class ViewTreeObserver {
+ // Recursive listeners use CopyOnWriteArrayList
+ private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
+ private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
+ private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
+ private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
+ private CopyOnWriteArrayList<OnEnterAnimationCompleteListener>
+ mOnEnterAnimationCompleteListeners;
+
+ // Non-recursive listeners use CopyOnWriteArray
+ // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
+ private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
+ private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+ private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
+ private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
+ private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
+
+ // These listeners cannot be mutated during dispatch
+ private boolean mInDispatchOnDraw;
+ private ArrayList<OnDrawListener> mOnDrawListeners;
+ private static boolean sIllegalOnDrawModificationIsFatal;
+
+ /** Remains false until #dispatchOnWindowShown() is called. If a listener registers after
+ * that the listener will be immediately called. */
+ private boolean mWindowShown;
+
+ private boolean mAlive = true;
+
+ /**
+ * Interface definition for a callback to be invoked when the view hierarchy is
+ * attached to and detached from its window.
+ */
+ public interface OnWindowAttachListener {
+ /**
+ * Callback method to be invoked when the view hierarchy is attached to a window
+ */
+ public void onWindowAttached();
+
+ /**
+ * Callback method to be invoked when the view hierarchy is detached from a window
+ */
+ public void onWindowDetached();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the view hierarchy's window
+ * focus state changes.
+ */
+ public interface OnWindowFocusChangeListener {
+ /**
+ * Callback method to be invoked when the window focus changes in the view tree.
+ *
+ * @param hasFocus Set to true if the window is gaining focus, false if it is
+ * losing focus.
+ */
+ public void onWindowFocusChanged(boolean hasFocus);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the focus state within
+ * the view tree changes.
+ */
+ public interface OnGlobalFocusChangeListener {
+ /**
+ * Callback method to be invoked when the focus changes in the view tree. When
+ * the view tree transitions from touch mode to non-touch mode, oldFocus is null.
+ * When the view tree transitions from non-touch mode to touch mode, newFocus is
+ * null. When focus changes in non-touch mode (without transition from or to
+ * touch mode) either oldFocus or newFocus can be null.
+ *
+ * @param oldFocus The previously focused view, if any.
+ * @param newFocus The newly focused View, if any.
+ */
+ public void onGlobalFocusChanged(View oldFocus, View newFocus);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the global layout state
+ * or the visibility of views within the view tree changes.
+ */
+ public interface OnGlobalLayoutListener {
+ /**
+ * Callback method to be invoked when the global layout state or the visibility of views
+ * within the view tree changes
+ */
+ public void onGlobalLayout();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the view tree is about to be drawn.
+ */
+ public interface OnPreDrawListener {
+ /**
+ * Callback method to be invoked when the view tree is about to be drawn. At this point, all
+ * views in the tree have been measured and given a frame. Clients can use this to adjust
+ * their scroll bounds or even to request a new layout before drawing occurs.
+ *
+ * @return Return true to proceed with the current drawing pass, or false to cancel.
+ *
+ * @see android.view.View#onMeasure
+ * @see android.view.View#onLayout
+ * @see android.view.View#onDraw
+ */
+ public boolean onPreDraw();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the view tree is about to be drawn.
+ */
+ public interface OnDrawListener {
+ /**
+ * <p>Callback method to be invoked when the view tree is about to be drawn. At this point,
+ * views cannot be modified in any way.</p>
+ *
+ * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the
+ * current drawing pass.</p>
+ *
+ * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong>
+ * from this method.</p>
+ *
+ * @see android.view.View#onMeasure
+ * @see android.view.View#onLayout
+ * @see android.view.View#onDraw
+ */
+ public void onDraw();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the touch mode changes.
+ */
+ public interface OnTouchModeChangeListener {
+ /**
+ * Callback method to be invoked when the touch mode changes.
+ *
+ * @param isInTouchMode True if the view hierarchy is now in touch mode, false otherwise.
+ */
+ public void onTouchModeChanged(boolean isInTouchMode);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when
+ * something in the view tree has been scrolled.
+ */
+ public interface OnScrollChangedListener {
+ /**
+ * Callback method to be invoked when something in the view tree
+ * has been scrolled.
+ */
+ public void onScrollChanged();
+ }
+
+ /**
+ * Interface definition for a callback noting when a system window has been displayed.
+ * This is only used for non-Activity windows. Activity windows can use
+ * Activity.onEnterAnimationComplete() to get the same signal.
+ * @hide
+ */
+ public interface OnWindowShownListener {
+ /**
+ * Callback method to be invoked when a non-activity window is fully shown.
+ */
+ void onWindowShown();
+ }
+
+ /**
+ * Parameters used with OnComputeInternalInsetsListener.
+ *
+ * We are not yet ready to commit to this API and support it, so
+ * @hide
+ */
+ public final static class InternalInsetsInfo {
+ /**
+ * Offsets from the frame of the window at which the content of
+ * windows behind it should be placed.
+ */
+ public final Rect contentInsets = new Rect();
+
+ /**
+ * Offsets from the frame of the window at which windows behind it
+ * are visible.
+ */
+ public final Rect visibleInsets = new Rect();
+
+ /**
+ * Touchable region defined relative to the origin of the frame of the window.
+ * Only used when {@link #setTouchableInsets(int)} is called with
+ * the option {@link #TOUCHABLE_INSETS_REGION}.
+ */
+ public final Region touchableRegion = new Region();
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the entire window frame
+ * can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_FRAME = 0;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the content insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_CONTENT = 1;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the visible insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_VISIBLE = 2;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the provided touchable region in {@link #touchableRegion} can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_REGION = 3;
+
+ /**
+ * Set which parts of the window can be touched: either
+ * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
+ * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
+ */
+ public void setTouchableInsets(int val) {
+ mTouchableInsets = val;
+ }
+
+ int mTouchableInsets;
+
+ void reset() {
+ contentInsets.setEmpty();
+ visibleInsets.setEmpty();
+ touchableRegion.setEmpty();
+ mTouchableInsets = TOUCHABLE_INSETS_FRAME;
+ }
+
+ boolean isEmpty() {
+ return contentInsets.isEmpty()
+ && visibleInsets.isEmpty()
+ && touchableRegion.isEmpty()
+ && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = contentInsets.hashCode();
+ result = 31 * result + visibleInsets.hashCode();
+ result = 31 * result + touchableRegion.hashCode();
+ result = 31 * result + mTouchableInsets;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ InternalInsetsInfo other = (InternalInsetsInfo)o;
+ return mTouchableInsets == other.mTouchableInsets &&
+ contentInsets.equals(other.contentInsets) &&
+ visibleInsets.equals(other.visibleInsets) &&
+ touchableRegion.equals(other.touchableRegion);
+ }
+
+ void set(InternalInsetsInfo other) {
+ contentInsets.set(other.contentInsets);
+ visibleInsets.set(other.visibleInsets);
+ touchableRegion.set(other.touchableRegion);
+ mTouchableInsets = other.mTouchableInsets;
+ }
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when layout has
+ * completed and the client can compute its interior insets.
+ *
+ * We are not yet ready to commit to this API and support it, so
+ * @hide
+ */
+ public interface OnComputeInternalInsetsListener {
+ /**
+ * Callback method to be invoked when layout has completed and the
+ * client can compute its interior insets.
+ *
+ * @param inoutInfo Should be filled in by the implementation with
+ * the information about the insets of the window. This is called
+ * with whatever values the previous OnComputeInternalInsetsListener
+ * returned, if there are multiple such listeners in the window.
+ */
+ public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
+ }
+
+ /**
+ * @hide
+ */
+ public interface OnEnterAnimationCompleteListener {
+ public void onEnterAnimationComplete();
+ }
+
+ /**
+ * Creates a new ViewTreeObserver. This constructor should not be called
+ */
+ ViewTreeObserver(Context context) {
+ sIllegalOnDrawModificationIsFatal =
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
+ }
+
+ /**
+ * Merges all the listeners registered on the specified observer with the listeners
+ * registered on this object. After this method is invoked, the specified observer
+ * will return false in {@link #isAlive()} and should not be used anymore.
+ *
+ * @param observer The ViewTreeObserver whose listeners must be added to this observer
+ */
+ void merge(ViewTreeObserver observer) {
+ if (observer.mOnWindowAttachListeners != null) {
+ if (mOnWindowAttachListeners != null) {
+ mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
+ } else {
+ mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
+ }
+ }
+
+ if (observer.mOnWindowFocusListeners != null) {
+ if (mOnWindowFocusListeners != null) {
+ mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
+ } else {
+ mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
+ }
+ }
+
+ if (observer.mOnGlobalFocusListeners != null) {
+ if (mOnGlobalFocusListeners != null) {
+ mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
+ } else {
+ mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
+ }
+ }
+
+ if (observer.mOnGlobalLayoutListeners != null) {
+ if (mOnGlobalLayoutListeners != null) {
+ mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
+ } else {
+ mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
+ }
+ }
+
+ if (observer.mOnPreDrawListeners != null) {
+ if (mOnPreDrawListeners != null) {
+ mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
+ } else {
+ mOnPreDrawListeners = observer.mOnPreDrawListeners;
+ }
+ }
+
+ if (observer.mOnDrawListeners != null) {
+ if (mOnDrawListeners != null) {
+ mOnDrawListeners.addAll(observer.mOnDrawListeners);
+ } else {
+ mOnDrawListeners = observer.mOnDrawListeners;
+ }
+ }
+
+ if (observer.mOnTouchModeChangeListeners != null) {
+ if (mOnTouchModeChangeListeners != null) {
+ mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
+ } else {
+ mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
+ }
+ }
+
+ if (observer.mOnComputeInternalInsetsListeners != null) {
+ if (mOnComputeInternalInsetsListeners != null) {
+ mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
+ } else {
+ mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
+ }
+ }
+
+ if (observer.mOnScrollChangedListeners != null) {
+ if (mOnScrollChangedListeners != null) {
+ mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
+ } else {
+ mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
+ }
+ }
+
+ if (observer.mOnWindowShownListeners != null) {
+ if (mOnWindowShownListeners != null) {
+ mOnWindowShownListeners.addAll(observer.mOnWindowShownListeners);
+ } else {
+ mOnWindowShownListeners = observer.mOnWindowShownListeners;
+ }
+ }
+
+ observer.kill();
+ }
+
+ /**
+ * Register a callback to be invoked when the view hierarchy is attached to a window.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnWindowAttachListener(OnWindowAttachListener listener) {
+ checkIsAlive();
+
+ if (mOnWindowAttachListeners == null) {
+ mOnWindowAttachListeners
+ = new CopyOnWriteArrayList<OnWindowAttachListener>();
+ }
+
+ mOnWindowAttachListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed window attach callback.
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
+ */
+ public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
+ checkIsAlive();
+ if (mOnWindowAttachListeners == null) {
+ return;
+ }
+ mOnWindowAttachListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the window focus state within the view tree changes.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
+ checkIsAlive();
+
+ if (mOnWindowFocusListeners == null) {
+ mOnWindowFocusListeners
+ = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
+ }
+
+ mOnWindowFocusListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed window focus change callback.
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
+ */
+ public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
+ checkIsAlive();
+ if (mOnWindowFocusListeners == null) {
+ return;
+ }
+ mOnWindowFocusListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the focus state within the view tree changes.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
+ checkIsAlive();
+
+ if (mOnGlobalFocusListeners == null) {
+ mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
+ }
+
+ mOnGlobalFocusListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed focus change callback.
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
+ */
+ public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
+ checkIsAlive();
+ if (mOnGlobalFocusListeners == null) {
+ return;
+ }
+ mOnGlobalFocusListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the global layout state or the visibility of views
+ * within the view tree changes
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
+ checkIsAlive();
+
+ if (mOnGlobalLayoutListeners == null) {
+ mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
+ }
+
+ mOnGlobalLayoutListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed global layout callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @deprecated Use #removeOnGlobalLayoutListener instead
+ *
+ * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
+ */
+ @Deprecated
+ public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
+ removeOnGlobalLayoutListener(victim);
+ }
+
+ /**
+ * Remove a previously installed global layout callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
+ */
+ public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
+ checkIsAlive();
+ if (mOnGlobalLayoutListeners == null) {
+ return;
+ }
+ mOnGlobalLayoutListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the view tree is about to be drawn
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnPreDrawListener(OnPreDrawListener listener) {
+ checkIsAlive();
+
+ if (mOnPreDrawListeners == null) {
+ mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
+ }
+
+ mOnPreDrawListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed pre-draw callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnPreDrawListener(OnPreDrawListener)
+ */
+ public void removeOnPreDrawListener(OnPreDrawListener victim) {
+ checkIsAlive();
+ if (mOnPreDrawListeners == null) {
+ return;
+ }
+ mOnPreDrawListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the view tree window has been shown
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ * @hide
+ */
+ public void addOnWindowShownListener(OnWindowShownListener listener) {
+ checkIsAlive();
+
+ if (mOnWindowShownListeners == null) {
+ mOnWindowShownListeners = new CopyOnWriteArray<OnWindowShownListener>();
+ }
+
+ mOnWindowShownListeners.add(listener);
+ if (mWindowShown) {
+ listener.onWindowShown();
+ }
+ }
+
+ /**
+ * Remove a previously installed window shown callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnWindowShownListener(OnWindowShownListener)
+ * @hide
+ */
+ public void removeOnWindowShownListener(OnWindowShownListener victim) {
+ checkIsAlive();
+ if (mOnWindowShownListeners == null) {
+ return;
+ }
+ mOnWindowShownListeners.remove(victim);
+ }
+
+ /**
+ * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
+ * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
+ * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnDrawListener(OnDrawListener listener) {
+ checkIsAlive();
+
+ if (mOnDrawListeners == null) {
+ mOnDrawListeners = new ArrayList<OnDrawListener>();
+ }
+
+ if (mInDispatchOnDraw) {
+ IllegalStateException ex = new IllegalStateException(
+ "Cannot call addOnDrawListener inside of onDraw");
+ if (sIllegalOnDrawModificationIsFatal) {
+ throw ex;
+ } else {
+ Log.e("ViewTreeObserver", ex.getMessage(), ex);
+ }
+ }
+ mOnDrawListeners.add(listener);
+ }
+
+ /**
+ * <p>Remove a previously installed pre-draw callback.</p>
+ * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
+ * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnDrawListener(OnDrawListener)
+ */
+ public void removeOnDrawListener(OnDrawListener victim) {
+ checkIsAlive();
+ if (mOnDrawListeners == null) {
+ return;
+ }
+ if (mInDispatchOnDraw) {
+ IllegalStateException ex = new IllegalStateException(
+ "Cannot call removeOnDrawListener inside of onDraw");
+ if (sIllegalOnDrawModificationIsFatal) {
+ throw ex;
+ } else {
+ Log.e("ViewTreeObserver", ex.getMessage(), ex);
+ }
+ }
+ mOnDrawListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when a view has been scrolled.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnScrollChangedListener(OnScrollChangedListener listener) {
+ checkIsAlive();
+
+ if (mOnScrollChangedListeners == null) {
+ mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
+ }
+
+ mOnScrollChangedListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed scroll-changed callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnScrollChangedListener(OnScrollChangedListener)
+ */
+ public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
+ checkIsAlive();
+ if (mOnScrollChangedListeners == null) {
+ return;
+ }
+ mOnScrollChangedListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the invoked when the touch mode changes.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
+ checkIsAlive();
+
+ if (mOnTouchModeChangeListeners == null) {
+ mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
+ }
+
+ mOnTouchModeChangeListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed touch mode change callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
+ */
+ public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
+ checkIsAlive();
+ if (mOnTouchModeChangeListeners == null) {
+ return;
+ }
+ mOnTouchModeChangeListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the invoked when it is time to
+ * compute the window's internal insets.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * We are not yet ready to commit to this API and support it, so
+ * @hide
+ */
+ public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
+ checkIsAlive();
+
+ if (mOnComputeInternalInsetsListeners == null) {
+ mOnComputeInternalInsetsListeners =
+ new CopyOnWriteArray<OnComputeInternalInsetsListener>();
+ }
+
+ mOnComputeInternalInsetsListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed internal insets computation callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
+ *
+ * We are not yet ready to commit to this API and support it, so
+ * @hide
+ */
+ public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
+ checkIsAlive();
+ if (mOnComputeInternalInsetsListeners == null) {
+ return;
+ }
+ mOnComputeInternalInsetsListeners.remove(victim);
+ }
+
+ /**
+ * @hide
+ */
+ public void addOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
+ checkIsAlive();
+ if (mOnEnterAnimationCompleteListeners == null) {
+ mOnEnterAnimationCompleteListeners =
+ new CopyOnWriteArrayList<OnEnterAnimationCompleteListener>();
+ }
+ mOnEnterAnimationCompleteListeners.add(listener);
+ }
+
+ /**
+ * @hide
+ */
+ public void removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
+ checkIsAlive();
+ if (mOnEnterAnimationCompleteListeners == null) {
+ return;
+ }
+ mOnEnterAnimationCompleteListeners.remove(listener);
+ }
+
+ private void checkIsAlive() {
+ if (!mAlive) {
+ throw new IllegalStateException("This ViewTreeObserver is not alive, call "
+ + "getViewTreeObserver() again");
+ }
+ }
+
+ /**
+ * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
+ * any call to a method (except this one) will throw an exception.
+ *
+ * If an application keeps a long-lived reference to this ViewTreeObserver, it should
+ * always check for the result of this method before calling any other method.
+ *
+ * @return True if this object is alive and be used, false otherwise.
+ */
+ public boolean isAlive() {
+ return mAlive;
+ }
+
+ /**
+ * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
+ * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
+ *
+ * @hide
+ */
+ private void kill() {
+ mAlive = false;
+ }
+
+ /**
+ * Notifies registered listeners that window has been attached/detached.
+ */
+ final void dispatchOnWindowAttachedChange(boolean attached) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArrayList<OnWindowAttachListener> listeners
+ = mOnWindowAttachListeners;
+ if (listeners != null && listeners.size() > 0) {
+ for (OnWindowAttachListener listener : listeners) {
+ if (attached) listener.onWindowAttached();
+ else listener.onWindowDetached();
+ }
+ }
+ }
+
+ /**
+ * Notifies registered listeners that window focus has changed.
+ */
+ final void dispatchOnWindowFocusChange(boolean hasFocus) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
+ = mOnWindowFocusListeners;
+ if (listeners != null && listeners.size() > 0) {
+ for (OnWindowFocusChangeListener listener : listeners) {
+ listener.onWindowFocusChanged(hasFocus);
+ }
+ }
+ }
+
+ /**
+ * Notifies registered listeners that focus has changed.
+ */
+ final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
+ if (listeners != null && listeners.size() > 0) {
+ for (OnGlobalFocusChangeListener listener : listeners) {
+ listener.onGlobalFocusChanged(oldFocus, newFocus);
+ }
+ }
+ }
+
+ /**
+ * Notifies registered listeners that a global layout happened. This can be called
+ * manually if you are forcing a layout on a View or a hierarchy of Views that are
+ * not attached to a Window or in the GONE state.
+ */
+ public final void dispatchOnGlobalLayout() {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onGlobalLayout();
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ }
+
+ /**
+ * Returns whether there are listeners for on pre-draw events.
+ */
+ final boolean hasOnPreDrawListeners() {
+ return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0;
+ }
+
+ /**
+ * Notifies registered listeners that the drawing pass is about to start. If a
+ * listener returns true, then the drawing pass is canceled and rescheduled. This can
+ * be called manually if you are forcing the drawing on a View or a hierarchy of Views
+ * that are not attached to a Window or in the GONE state.
+ *
+ * @return True if the current draw should be canceled and resceduled, false otherwise.
+ */
+ @SuppressWarnings("unchecked")
+ public final boolean dispatchOnPreDraw() {
+ boolean cancelDraw = false;
+ final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ cancelDraw |= !(access.get(i).onPreDraw());
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ return cancelDraw;
+ }
+
+ /**
+ * Notifies registered listeners that the window is now shown
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ public final void dispatchOnWindowShown() {
+ mWindowShown = true;
+ final CopyOnWriteArray<OnWindowShownListener> listeners = mOnWindowShownListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<OnWindowShownListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onWindowShown();
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ }
+
+ /**
+ * Notifies registered listeners that the drawing pass is about to start.
+ */
+ public final void dispatchOnDraw() {
+ if (mOnDrawListeners != null) {
+ mInDispatchOnDraw = true;
+ final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ listeners.get(i).onDraw();
+ }
+ mInDispatchOnDraw = false;
+ }
+ }
+
+ /**
+ * Notifies registered listeners that the touch mode has changed.
+ *
+ * @param inTouchMode True if the touch mode is now enabled, false otherwise.
+ */
+ final void dispatchOnTouchModeChanged(boolean inTouchMode) {
+ final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
+ mOnTouchModeChangeListeners;
+ if (listeners != null && listeners.size() > 0) {
+ for (OnTouchModeChangeListener listener : listeners) {
+ listener.onTouchModeChanged(inTouchMode);
+ }
+ }
+ }
+
+ /**
+ * Notifies registered listeners that something has scrolled.
+ */
+ final void dispatchOnScrollChanged() {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onScrollChanged();
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ }
+
+ /**
+ * Returns whether there are listeners for computing internal insets.
+ */
+ final boolean hasComputeInternalInsetsListeners() {
+ final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
+ mOnComputeInternalInsetsListeners;
+ return (listeners != null && listeners.size() > 0);
+ }
+
+ /**
+ * Calls all listeners to compute the current insets.
+ */
+ final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
+ mOnComputeInternalInsetsListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onComputeInternalInsets(inoutInfo);
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public final void dispatchOnEnterAnimationComplete() {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArrayList<OnEnterAnimationCompleteListener> listeners =
+ mOnEnterAnimationCompleteListeners;
+ if (listeners != null && !listeners.isEmpty()) {
+ for (OnEnterAnimationCompleteListener listener : listeners) {
+ listener.onEnterAnimationComplete();
+ }
+ }
+ }
+
+ /**
+ * 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
+ * until a concurrent modification happens.
+ *
+ * Usage:
+ *
+ * CopyOnWriteArray.Access<MyData> access = array.start();
+ * try {
+ * for (int i = 0; i < access.size(); i++) {
+ * MyData d = access.get(i);
+ * }
+ * } finally {
+ * access.end();
+ * }
+ */
+ static class CopyOnWriteArray<T> {
+ private ArrayList<T> mData = new ArrayList<T>();
+ private ArrayList<T> mDataCopy;
+
+ private final Access<T> mAccess = new Access<T>();
+
+ private boolean mStart;
+
+ static class Access<T> {
+ private ArrayList<T> mData;
+ private int mSize;
+
+ T get(int index) {
+ return mData.get(index);
+ }
+
+ int size() {
+ return mSize;
+ }
+ }
+
+ CopyOnWriteArray() {
+ }
+
+ private ArrayList<T> getArray() {
+ if (mStart) {
+ if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
+ return mDataCopy;
+ }
+ return mData;
+ }
+
+ Access<T> start() {
+ if (mStart) throw new IllegalStateException("Iteration already started");
+ mStart = true;
+ mDataCopy = null;
+ mAccess.mData = mData;
+ mAccess.mSize = mData.size();
+ return mAccess;
+ }
+
+ void end() {
+ if (!mStart) throw new IllegalStateException("Iteration not started");
+ mStart = false;
+ if (mDataCopy != null) {
+ mData = mDataCopy;
+ mAccess.mData.clear();
+ mAccess.mSize = 0;
+ }
+ mDataCopy = null;
+ }
+
+ int size() {
+ return getArray().size();
+ }
+
+ void add(T item) {
+ getArray().add(item);
+ }
+
+ void addAll(CopyOnWriteArray<T> array) {
+ getArray().addAll(array.mData);
+ }
+
+ void remove(T item) {
+ getArray().remove(item);
+ }
+
+ void clear() {
+ getArray().clear();
+ }
+ }
+}
diff --git a/android/view/View_Delegate.java b/android/view/View_Delegate.java
new file mode 100644
index 00000000..408ec549
--- /dev/null
+++ b/android/view/View_Delegate.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 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 com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.os.IBinder;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link View}
+ *
+ * Through the layoutlib_create tool, the original methods of View have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class View_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isInEditMode(View thisView) {
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static IBinder getWindowToken(View thisView) {
+ Context baseContext = BridgeContext.getBaseContext(thisView.getContext());
+ if (baseContext instanceof BridgeContext) {
+ return ((BridgeContext) baseContext).getBinder();
+ }
+ return null;
+ }
+}
diff --git a/android/view/Window.java b/android/view/Window.java
new file mode 100644
index 00000000..176927fe
--- /dev/null
+++ b/android/view/Window.java
@@ -0,0 +1,2331 @@
+/*
+ * Copyright (C) 2006 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 static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.annotation.SystemApi;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.List;
+
+/**
+ * Abstract base class for a top-level window look and behavior policy. An
+ * instance of this class should be used as the top-level view added to the
+ * window manager. It provides standard UI policies such as a background, title
+ * area, default key processing, etc.
+ *
+ * <p>The only existing implementation of this abstract class is
+ * android.view.PhoneWindow, which you should instantiate when needing a
+ * Window.
+ */
+public abstract class Window {
+ /** Flag for the "options panel" feature. This is enabled by default. */
+ public static final int FEATURE_OPTIONS_PANEL = 0;
+ /** Flag for the "no title" feature, turning off the title at the top
+ * of the screen. */
+ public static final int FEATURE_NO_TITLE = 1;
+
+ /**
+ * Flag for the progress indicator feature.
+ *
+ * @deprecated No longer supported starting in API 21.
+ */
+ @Deprecated
+ public static final int FEATURE_PROGRESS = 2;
+
+ /** Flag for having an icon on the left side of the title bar */
+ public static final int FEATURE_LEFT_ICON = 3;
+ /** Flag for having an icon on the right side of the title bar */
+ public static final int FEATURE_RIGHT_ICON = 4;
+
+ /**
+ * Flag for indeterminate progress.
+ *
+ * @deprecated No longer supported starting in API 21.
+ */
+ @Deprecated
+ public static final int FEATURE_INDETERMINATE_PROGRESS = 5;
+
+ /** Flag for the context menu. This is enabled by default. */
+ public static final int FEATURE_CONTEXT_MENU = 6;
+ /** Flag for custom title. You cannot combine this feature with other title features. */
+ public static final int FEATURE_CUSTOM_TITLE = 7;
+ /**
+ * Flag for enabling the Action Bar.
+ * This is enabled by default for some devices. The Action Bar
+ * replaces the title bar and provides an alternate location
+ * for an on-screen menu button on some devices.
+ */
+ public static final int FEATURE_ACTION_BAR = 8;
+ /**
+ * Flag for requesting an Action Bar that overlays window content.
+ * Normally an Action Bar will sit in the space above window content, but if this
+ * feature is requested along with {@link #FEATURE_ACTION_BAR} it will be layered over
+ * the window content itself. This is useful if you would like your app to have more control
+ * over how the Action Bar is displayed, such as letting application content scroll beneath
+ * an Action Bar with a transparent background or otherwise displaying a transparent/translucent
+ * Action Bar over application content.
+ *
+ * <p>This mode is especially useful with {@link View#SYSTEM_UI_FLAG_FULLSCREEN
+ * View.SYSTEM_UI_FLAG_FULLSCREEN}, which allows you to seamlessly hide the
+ * action bar in conjunction with other screen decorations.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, when an
+ * ActionBar is in this mode it will adjust the insets provided to
+ * {@link View#fitSystemWindows(android.graphics.Rect) View.fitSystemWindows(Rect)}
+ * to include the content covered by the action bar, so you can do layout within
+ * that space.
+ */
+ public static final int FEATURE_ACTION_BAR_OVERLAY = 9;
+ /**
+ * Flag for specifying the behavior of action modes when an Action Bar is not present.
+ * If overlay is enabled, the action mode UI will be allowed to cover existing window content.
+ */
+ public static final int FEATURE_ACTION_MODE_OVERLAY = 10;
+ /**
+ * Flag for requesting a decoration-free window that is dismissed by swiping from the left.
+ */
+ public static final int FEATURE_SWIPE_TO_DISMISS = 11;
+ /**
+ * Flag for requesting that window content changes should be animated using a
+ * TransitionManager.
+ *
+ * <p>The TransitionManager is set using
+ * {@link #setTransitionManager(android.transition.TransitionManager)}. If none is set,
+ * a default TransitionManager will be used.</p>
+ *
+ * @see #setContentView
+ */
+ public static final int FEATURE_CONTENT_TRANSITIONS = 12;
+
+ /**
+ * Enables Activities to run Activity Transitions either through sending or receiving
+ * ActivityOptions bundle created with
+ * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,
+ * android.util.Pair[])} or {@link android.app.ActivityOptions#makeSceneTransitionAnimation(
+ * android.app.Activity, View, String)}.
+ */
+ public static final int FEATURE_ACTIVITY_TRANSITIONS = 13;
+
+ /**
+ * Max value used as a feature ID
+ * @hide
+ */
+ public static final int FEATURE_MAX = FEATURE_ACTIVITY_TRANSITIONS;
+
+ /**
+ * Flag for setting the progress bar's visibility to VISIBLE.
+ *
+ * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+ * supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_VISIBILITY_ON = -1;
+
+ /**
+ * Flag for setting the progress bar's visibility to GONE.
+ *
+ * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+ * supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_VISIBILITY_OFF = -2;
+
+ /**
+ * Flag for setting the progress bar's indeterminate mode on.
+ *
+ * @deprecated {@link #FEATURE_INDETERMINATE_PROGRESS} and related methods
+ * are no longer supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_INDETERMINATE_ON = -3;
+
+ /**
+ * Flag for setting the progress bar's indeterminate mode off.
+ *
+ * @deprecated {@link #FEATURE_INDETERMINATE_PROGRESS} and related methods
+ * are no longer supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_INDETERMINATE_OFF = -4;
+
+ /**
+ * Starting value for the (primary) progress.
+ *
+ * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+ * supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_START = 0;
+
+ /**
+ * Ending value for the (primary) progress.
+ *
+ * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+ * supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_END = 10000;
+
+ /**
+ * Lowest possible value for the secondary progress.
+ *
+ * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+ * supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_SECONDARY_START = 20000;
+
+ /**
+ * Highest possible value for the secondary progress.
+ *
+ * @deprecated {@link #FEATURE_PROGRESS} and related methods are no longer
+ * supported starting in API 21.
+ */
+ @Deprecated
+ public static final int PROGRESS_SECONDARY_END = 30000;
+
+ /**
+ * The transitionName for the status bar background View when a custom background is used.
+ * @see android.view.Window#setStatusBarColor(int)
+ */
+ public static final String STATUS_BAR_BACKGROUND_TRANSITION_NAME = "android:status:background";
+
+ /**
+ * The transitionName for the navigation bar background View when a custom background is used.
+ * @see android.view.Window#setNavigationBarColor(int)
+ */
+ public static final String NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME =
+ "android:navigation:background";
+
+ /**
+ * The default features enabled.
+ * @deprecated use {@link #getDefaultFeatures(android.content.Context)} instead.
+ */
+ @Deprecated
+ @SuppressWarnings({"PointlessBitwiseExpression"})
+ protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) |
+ (1 << FEATURE_CONTEXT_MENU);
+
+ /**
+ * The ID that the main layout in the XML layout file should have.
+ */
+ public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
+
+ private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw";
+
+ /**
+ * Flag for letting the theme drive the color of the window caption controls. Use with
+ * {@link #setDecorCaptionShade(int)}. This is the default value.
+ */
+ public static final int DECOR_CAPTION_SHADE_AUTO = 0;
+ /**
+ * Flag for setting light-color controls on the window caption. Use with
+ * {@link #setDecorCaptionShade(int)}.
+ */
+ public static final int DECOR_CAPTION_SHADE_LIGHT = 1;
+ /**
+ * Flag for setting dark-color controls on the window caption. Use with
+ * {@link #setDecorCaptionShade(int)}.
+ */
+ public static final int DECOR_CAPTION_SHADE_DARK = 2;
+
+ private final Context mContext;
+
+ private TypedArray mWindowStyle;
+ private Callback mCallback;
+ private OnWindowDismissedCallback mOnWindowDismissedCallback;
+ private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback;
+ private WindowControllerCallback mWindowControllerCallback;
+ private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
+ private Rect mRestrictedCaptionAreaRect;
+ private WindowManager mWindowManager;
+ private IBinder mAppToken;
+ private String mAppName;
+ private boolean mHardwareAccelerated;
+ private Window mContainer;
+ private Window mActiveChild;
+ private boolean mIsActive = false;
+ private boolean mHasChildren = false;
+ private boolean mCloseOnTouchOutside = false;
+ private boolean mSetCloseOnTouchOutside = false;
+ private int mForcedWindowFlags = 0;
+
+ private int mFeatures;
+ private int mLocalFeatures;
+
+ private boolean mHaveWindowFormat = false;
+ private boolean mHaveDimAmount = false;
+ private int mDefaultWindowFormat = PixelFormat.OPAQUE;
+
+ private boolean mHasSoftInputMode = false;
+
+ private boolean mDestroyed;
+
+ private boolean mOverlayWithDecorCaptionEnabled = false;
+ private boolean mCloseOnSwipeEnabled = false;
+
+ // The current window attributes.
+ private final WindowManager.LayoutParams mWindowAttributes =
+ new WindowManager.LayoutParams();
+
+ /**
+ * API from a Window back to its caller. This allows the client to
+ * intercept key dispatching, panels and menus, etc.
+ */
+ public interface Callback {
+ /**
+ * Called to process key events. At the very least your
+ * implementation must call
+ * {@link android.view.Window#superDispatchKeyEvent} to do the
+ * standard key processing.
+ *
+ * @param event The key event.
+ *
+ * @return boolean Return true if this event was consumed.
+ */
+ public boolean dispatchKeyEvent(KeyEvent event);
+
+ /**
+ * Called to process a key shortcut event.
+ * At the very least your implementation must call
+ * {@link android.view.Window#superDispatchKeyShortcutEvent} to do the
+ * standard key shortcut processing.
+ *
+ * @param event The key shortcut event.
+ * @return True if this event was consumed.
+ */
+ public boolean dispatchKeyShortcutEvent(KeyEvent event);
+
+ /**
+ * Called to process touch screen events. At the very least your
+ * implementation must call
+ * {@link android.view.Window#superDispatchTouchEvent} to do the
+ * standard touch screen processing.
+ *
+ * @param event The touch screen event.
+ *
+ * @return boolean Return true if this event was consumed.
+ */
+ public boolean dispatchTouchEvent(MotionEvent event);
+
+ /**
+ * Called to process trackball events. At the very least your
+ * implementation must call
+ * {@link android.view.Window#superDispatchTrackballEvent} to do the
+ * standard trackball processing.
+ *
+ * @param event The trackball event.
+ *
+ * @return boolean Return true if this event was consumed.
+ */
+ public boolean dispatchTrackballEvent(MotionEvent event);
+
+ /**
+ * Called to process generic motion events. At the very least your
+ * implementation must call
+ * {@link android.view.Window#superDispatchGenericMotionEvent} to do the
+ * standard processing.
+ *
+ * @param event The generic motion event.
+ *
+ * @return boolean Return true if this event was consumed.
+ */
+ public boolean dispatchGenericMotionEvent(MotionEvent event);
+
+ /**
+ * Called to process population of {@link AccessibilityEvent}s.
+ *
+ * @param event The event.
+ *
+ * @return boolean Return true if event population was completed.
+ */
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
+
+ /**
+ * Instantiate the view to display in the panel for 'featureId'.
+ * You can return null, in which case the default content (typically
+ * a menu) will be created for you.
+ *
+ * @param featureId Which panel is being created.
+ *
+ * @return view The top-level view to place in the panel.
+ *
+ * @see #onPreparePanel
+ */
+ @Nullable
+ public View onCreatePanelView(int featureId);
+
+ /**
+ * Initialize the contents of the menu for panel 'featureId'. This is
+ * called if onCreatePanelView() returns null, giving you a standard
+ * menu in which you can place your items. It is only called once for
+ * the panel, the first time it is shown.
+ *
+ * <p>You can safely hold on to <var>menu</var> (and any items created
+ * from it), making modifications to it as desired, until the next
+ * time onCreatePanelMenu() is called for this feature.
+ *
+ * @param featureId The panel being created.
+ * @param menu The menu inside the panel.
+ *
+ * @return boolean You must return true for the panel to be displayed;
+ * if you return false it will not be shown.
+ */
+ public boolean onCreatePanelMenu(int featureId, Menu menu);
+
+ /**
+ * Prepare a panel to be displayed. This is called right before the
+ * panel window is shown, every time it is shown.
+ *
+ * @param featureId The panel that is being displayed.
+ * @param view The View that was returned by onCreatePanelView().
+ * @param menu If onCreatePanelView() returned null, this is the Menu
+ * being displayed in the panel.
+ *
+ * @return boolean You must return true for the panel to be displayed;
+ * if you return false it will not be shown.
+ *
+ * @see #onCreatePanelView
+ */
+ public boolean onPreparePanel(int featureId, View view, Menu menu);
+
+ /**
+ * Called when a panel's menu is opened by the user. This may also be
+ * called when the menu is changing from one type to another (for
+ * example, from the icon menu to the expanded menu).
+ *
+ * @param featureId The panel that the menu is in.
+ * @param menu The menu that is opened.
+ * @return Return true to allow the menu to open, or false to prevent
+ * the menu from opening.
+ */
+ public boolean onMenuOpened(int featureId, Menu menu);
+
+ /**
+ * Called when a panel's menu item has been selected by the user.
+ *
+ * @param featureId The panel that the menu is in.
+ * @param item The menu item that was selected.
+ *
+ * @return boolean Return true to finish processing of selection, or
+ * false to perform the normal menu handling (calling its
+ * Runnable or sending a Message to its target Handler).
+ */
+ public boolean onMenuItemSelected(int featureId, MenuItem item);
+
+ /**
+ * This is called whenever the current window attributes change.
+ *
+ */
+ public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
+
+ /**
+ * This hook is called whenever the content view of the screen changes
+ * (due to a call to
+ * {@link Window#setContentView(View, android.view.ViewGroup.LayoutParams)
+ * Window.setContentView} or
+ * {@link Window#addContentView(View, android.view.ViewGroup.LayoutParams)
+ * Window.addContentView}).
+ */
+ public void onContentChanged();
+
+ /**
+ * This hook is called whenever the window focus changes. See
+ * {@link View#onWindowFocusChanged(boolean)
+ * View.onWindowFocusChangedNotLocked(boolean)} for more information.
+ *
+ * @param hasFocus Whether the window now has focus.
+ */
+ public void onWindowFocusChanged(boolean hasFocus);
+
+ /**
+ * Called when the window has been attached to the window manager.
+ * See {@link View#onAttachedToWindow() View.onAttachedToWindow()}
+ * for more information.
+ */
+ public void onAttachedToWindow();
+
+ /**
+ * Called when the window has been detached from the window manager.
+ * See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()}
+ * for more information.
+ */
+ public void onDetachedFromWindow();
+
+ /**
+ * Called when a panel is being closed. If another logical subsequent
+ * panel is being opened (and this panel is being closed to make room for the subsequent
+ * panel), this method will NOT be called.
+ *
+ * @param featureId The panel that is being displayed.
+ * @param menu If onCreatePanelView() returned null, this is the Menu
+ * being displayed in the panel.
+ */
+ public void onPanelClosed(int featureId, Menu menu);
+
+ /**
+ * Called when the user signals the desire to start a search.
+ *
+ * @return true if search launched, false if activity refuses (blocks)
+ *
+ * @see android.app.Activity#onSearchRequested()
+ */
+ public boolean onSearchRequested();
+
+ /**
+ * Called when the user signals the desire to start a search.
+ *
+ * @param searchEvent A {@link SearchEvent} describing the signal to
+ * start a search.
+ * @return true if search launched, false if activity refuses (blocks)
+ */
+ public boolean onSearchRequested(SearchEvent searchEvent);
+
+ /**
+ * Called when an action mode is being started for this window. Gives the
+ * callback an opportunity to handle the action mode in its own unique and
+ * beautiful way. If this method returns null the system can choose a way
+ * to present the mode or choose not to start the mode at all. This is equivalent
+ * to {@link #onWindowStartingActionMode(android.view.ActionMode.Callback, int)}
+ * with type {@link ActionMode#TYPE_PRIMARY}.
+ *
+ * @param callback Callback to control the lifecycle of this action mode
+ * @return The ActionMode that was started, or null if the system should present it
+ */
+ @Nullable
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);
+
+ /**
+ * Called when an action mode is being started for this window. Gives the
+ * callback an opportunity to handle the action mode in its own unique and
+ * beautiful way. If this method returns null the system can choose a way
+ * to present the mode or choose not to start the mode at all.
+ *
+ * @param callback Callback to control the lifecycle of this action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The ActionMode that was started, or null if the system should present it
+ */
+ @Nullable
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type);
+
+ /**
+ * Called when an action mode has been started. The appropriate mode callback
+ * method will have already been invoked.
+ *
+ * @param mode The new mode that has just been started.
+ */
+ public void onActionModeStarted(ActionMode mode);
+
+ /**
+ * Called when an action mode has been finished. The appropriate mode callback
+ * method will have already been invoked.
+ *
+ * @param mode The mode that was just finished.
+ */
+ public void onActionModeFinished(ActionMode mode);
+
+ /**
+ * Called when Keyboard Shortcuts are requested for the current window.
+ *
+ * @param data The data list to populate with shortcuts.
+ * @param menu The current menu, which may be null.
+ * @param deviceId The id for the connected device the shortcuts should be provided for.
+ */
+ default public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) { };
+
+ /**
+ * Called when pointer capture is enabled or disabled for the current window.
+ *
+ * @param hasCapture True if the window has pointer capture.
+ */
+ default public void onPointerCaptureChanged(boolean hasCapture) { };
+ }
+
+ /** @hide */
+ public interface OnWindowDismissedCallback {
+ /**
+ * Called when a window is dismissed. This informs the callback that the
+ * window is gone, and it should finish itself.
+ * @param finishTask True if the task should also be finished.
+ * @param suppressWindowTransition True if the resulting exit and enter window transition
+ * animations should be suppressed.
+ */
+ void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition);
+ }
+
+ /** @hide */
+ public interface OnWindowSwipeDismissedCallback {
+ /**
+ * Called when a window is swipe dismissed. This informs the callback that the
+ * window is gone, and it should finish itself.
+ * @param finishTask True if the task should also be finished.
+ * @param suppressWindowTransition True if the resulting exit and enter window transition
+ * animations should be suppressed.
+ */
+ void onWindowSwipeDismissed();
+ }
+
+ /** @hide */
+ public interface WindowControllerCallback {
+ /**
+ * Moves the activity from
+ * Moves the activity from {@link WindowConfiguration#WINDOWING_MODE_FREEFORM} windowing
+ * mode to {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+ */
+ void exitFreeformMode() throws RemoteException;
+
+ /**
+ * Puts the activity in picture-in-picture mode if the activity supports.
+ * @see android.R.attr#supportsPictureInPicture
+ */
+ void enterPictureInPictureModeIfPossible();
+
+ /** Returns whether the window belongs to the task root. */
+ boolean isTaskRoot();
+ }
+
+ /**
+ * Callback for clients that want to be aware of where caption draws content.
+ */
+ public interface OnRestrictedCaptionAreaChangedListener {
+ /**
+ * Called when the area where caption draws content changes.
+ *
+ * @param rect The area where caption content is positioned, relative to the top view.
+ */
+ void onRestrictedCaptionAreaChanged(Rect rect);
+ }
+
+ /**
+ * Callback for clients that want frame timing information for each
+ * frame rendered by the Window.
+ */
+ public interface OnFrameMetricsAvailableListener {
+ /**
+ * Called when information is available for the previously rendered frame.
+ *
+ * Reports can be dropped if this callback takes too
+ * long to execute, as the report producer cannot wait for the consumer to
+ * complete.
+ *
+ * It is highly recommended that clients copy the passed in FrameMetrics
+ * via {@link FrameMetrics#FrameMetrics(FrameMetrics)} within this method and defer
+ * additional computation or storage to another thread to avoid unnecessarily
+ * dropping reports.
+ *
+ * @param window The {@link Window} on which the frame was displayed.
+ * @param frameMetrics the available metrics. This object is reused on every call
+ * and thus <strong>this reference is not valid outside the scope of this method</strong>.
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * this callback was invoked.
+ */
+ void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
+ int dropCountSinceLastInvocation);
+ }
+
+
+ public Window(Context context) {
+ mContext = context;
+ mFeatures = mLocalFeatures = getDefaultFeatures(context);
+ }
+
+ /**
+ * Return the Context this window policy is running in, for retrieving
+ * resources and other information.
+ *
+ * @return Context The Context that was supplied to the constructor.
+ */
+ public final Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Return the {@link android.R.styleable#Window} attributes from this
+ * window's theme.
+ */
+ public final TypedArray getWindowStyle() {
+ synchronized (this) {
+ if (mWindowStyle == null) {
+ mWindowStyle = mContext.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ }
+ return mWindowStyle;
+ }
+ }
+
+ /**
+ * Set the container for this window. If not set, the DecorWindow
+ * operates as a top-level window; otherwise, it negotiates with the
+ * container to display itself appropriately.
+ *
+ * @param container The desired containing Window.
+ */
+ public void setContainer(Window container) {
+ mContainer = container;
+ if (container != null) {
+ // Embedded screens never have a title.
+ mFeatures |= 1<<FEATURE_NO_TITLE;
+ mLocalFeatures |= 1<<FEATURE_NO_TITLE;
+ container.mHasChildren = true;
+ }
+ }
+
+ /**
+ * Return the container for this Window.
+ *
+ * @return Window The containing window, or null if this is a
+ * top-level window.
+ */
+ public final Window getContainer() {
+ return mContainer;
+ }
+
+ public final boolean hasChildren() {
+ return mHasChildren;
+ }
+
+ /** @hide */
+ public final void destroy() {
+ mDestroyed = true;
+ }
+
+ /** @hide */
+ public final boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ /**
+ * Set the window manager for use by this Window to, for example,
+ * display panels. This is <em>not</em> used for displaying the
+ * Window itself -- that must be done by the client.
+ *
+ * @param wm The window manager for adding new windows.
+ */
+ public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
+ setWindowManager(wm, appToken, appName, false);
+ }
+
+ /**
+ * Set the window manager for use by this Window to, for example,
+ * display panels. This is <em>not</em> used for displaying the
+ * Window itself -- that must be done by the client.
+ *
+ * @param wm The window manager for adding new windows.
+ */
+ public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
+ boolean hardwareAccelerated) {
+ mAppToken = appToken;
+ mAppName = appName;
+ mHardwareAccelerated = hardwareAccelerated
+ || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
+ if (wm == null) {
+ wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+ mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
+ }
+
+ void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
+ CharSequence curTitle = wp.getTitle();
+ if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
+ wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ if (wp.token == null) {
+ View decor = peekDecorView();
+ if (decor != null) {
+ wp.token = decor.getWindowToken();
+ }
+ }
+ if (curTitle == null || curTitle.length() == 0) {
+ final StringBuilder title = new StringBuilder(32);
+ if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
+ title.append("Media");
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
+ title.append("MediaOvr");
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+ title.append("Panel");
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
+ title.append("SubPanel");
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) {
+ title.append("AboveSubPanel");
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
+ title.append("AtchDlg");
+ } else {
+ title.append(wp.type);
+ }
+ if (mAppName != null) {
+ title.append(":").append(mAppName);
+ }
+ wp.setTitle(title);
+ }
+ } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
+ wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
+ // We don't set the app token to this system window because the life cycles should be
+ // independent. If an app creates a system window and then the app goes to the stopped
+ // state, the system window should not be affected (can still show and receive input
+ // events).
+ if (curTitle == null || curTitle.length() == 0) {
+ final StringBuilder title = new StringBuilder(32);
+ title.append("Sys").append(wp.type);
+ if (mAppName != null) {
+ title.append(":").append(mAppName);
+ }
+ wp.setTitle(title);
+ }
+ } else {
+ if (wp.token == null) {
+ wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
+ }
+ if ((curTitle == null || curTitle.length() == 0)
+ && mAppName != null) {
+ wp.setTitle(mAppName);
+ }
+ }
+ if (wp.packageName == null) {
+ wp.packageName = mContext.getPackageName();
+ }
+ if (mHardwareAccelerated ||
+ (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
+ wp.flags |= FLAG_HARDWARE_ACCELERATED;
+ }
+ }
+
+ /**
+ * Return the window manager allowing this Window to display its own
+ * windows.
+ *
+ * @return WindowManager The ViewManager.
+ */
+ public WindowManager getWindowManager() {
+ return mWindowManager;
+ }
+
+ /**
+ * Set the Callback interface for this window, used to intercept key
+ * events and other dynamic operations in the window.
+ *
+ * @param callback The desired Callback interface.
+ */
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Return the current Callback interface for this window.
+ */
+ public final Callback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Set an observer to collect frame stats for each frame rendererd in this window.
+ *
+ * Must be in hardware rendering mode.
+ */
+ public final void addOnFrameMetricsAvailableListener(
+ @NonNull OnFrameMetricsAvailableListener listener,
+ Handler handler) {
+ final View decorView = getDecorView();
+ if (decorView == null) {
+ throw new IllegalStateException("can't observe a Window without an attached view");
+ }
+
+ if (listener == null) {
+ throw new NullPointerException("listener cannot be null");
+ }
+
+ decorView.addFrameMetricsListener(this, listener, handler);
+ }
+
+ /**
+ * Remove observer and stop listening to frame stats for this window.
+ */
+ public final void removeOnFrameMetricsAvailableListener(OnFrameMetricsAvailableListener listener) {
+ final View decorView = getDecorView();
+ if (decorView != null) {
+ getDecorView().removeFrameMetricsListener(listener);
+ }
+ }
+
+ /** @hide */
+ public final void setOnWindowDismissedCallback(OnWindowDismissedCallback dcb) {
+ mOnWindowDismissedCallback = dcb;
+ }
+
+ /** @hide */
+ public final void dispatchOnWindowDismissed(
+ boolean finishTask, boolean suppressWindowTransition) {
+ if (mOnWindowDismissedCallback != null) {
+ mOnWindowDismissedCallback.onWindowDismissed(finishTask, suppressWindowTransition);
+ }
+ }
+
+ /** @hide */
+ public final void setOnWindowSwipeDismissedCallback(OnWindowSwipeDismissedCallback sdcb) {
+ mOnWindowSwipeDismissedCallback = sdcb;
+ }
+
+ /** @hide */
+ public final void dispatchOnWindowSwipeDismissed() {
+ if (mOnWindowSwipeDismissedCallback != null) {
+ mOnWindowSwipeDismissedCallback.onWindowSwipeDismissed();
+ }
+ }
+
+ /** @hide */
+ public final void setWindowControllerCallback(WindowControllerCallback wccb) {
+ mWindowControllerCallback = wccb;
+ }
+
+ /** @hide */
+ public final WindowControllerCallback getWindowControllerCallback() {
+ return mWindowControllerCallback;
+ }
+
+ /**
+ * Set a callback for changes of area where caption will draw its content.
+ *
+ * @param listener Callback that will be called when the area changes.
+ */
+ public final void setRestrictedCaptionAreaListener(OnRestrictedCaptionAreaChangedListener listener) {
+ mOnRestrictedCaptionAreaChangedListener = listener;
+ mRestrictedCaptionAreaRect = listener != null ? new Rect() : null;
+ }
+
+ /**
+ * Take ownership of this window's surface. The window's view hierarchy
+ * will no longer draw into the surface, though it will otherwise continue
+ * to operate (such as for receiving input events). The given SurfaceHolder
+ * callback will be used to tell you about state changes to the surface.
+ */
+ public abstract void takeSurface(SurfaceHolder.Callback2 callback);
+
+ /**
+ * Take ownership of this window's InputQueue. The window will no
+ * longer read and dispatch input events from the queue; it is your
+ * responsibility to do so.
+ */
+ public abstract void takeInputQueue(InputQueue.Callback callback);
+
+ /**
+ * Return whether this window is being displayed with a floating style
+ * (based on the {@link android.R.attr#windowIsFloating} attribute in
+ * the style/theme).
+ *
+ * @return Returns true if the window is configured to be displayed floating
+ * on top of whatever is behind it.
+ */
+ public abstract boolean isFloating();
+
+ /**
+ * Set the width and height layout parameters of the window. The default
+ * for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT
+ * or an absolute value to make a window that is not full-screen.
+ *
+ * @param width The desired layout width of the window.
+ * @param height The desired layout height of the window.
+ *
+ * @see ViewGroup.LayoutParams#height
+ * @see ViewGroup.LayoutParams#width
+ */
+ public void setLayout(int width, int height) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.width = width;
+ attrs.height = height;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Set the gravity of the window, as per the Gravity constants. This
+ * controls how the window manager is positioned in the overall window; it
+ * is only useful when using WRAP_CONTENT for the layout width or height.
+ *
+ * @param gravity The desired gravity constant.
+ *
+ * @see Gravity
+ * @see #setLayout
+ */
+ public void setGravity(int gravity)
+ {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.gravity = gravity;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Set the type of the window, as per the WindowManager.LayoutParams
+ * types.
+ *
+ * @param type The new window type (see WindowManager.LayoutParams).
+ */
+ public void setType(int type) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.type = type;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Set the format of window, as per the PixelFormat types. This overrides
+ * the default format that is selected by the Window based on its
+ * window decorations.
+ *
+ * @param format The new window format (see PixelFormat). Use
+ * PixelFormat.UNKNOWN to allow the Window to select
+ * the format.
+ *
+ * @see PixelFormat
+ */
+ public void setFormat(int format) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ if (format != PixelFormat.UNKNOWN) {
+ attrs.format = format;
+ mHaveWindowFormat = true;
+ } else {
+ attrs.format = mDefaultWindowFormat;
+ mHaveWindowFormat = false;
+ }
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Specify custom animations to use for the window, as per
+ * {@link WindowManager.LayoutParams#windowAnimations
+ * WindowManager.LayoutParams.windowAnimations}. Providing anything besides
+ * 0 here will override the animations the window would
+ * normally retrieve from its theme.
+ */
+ public void setWindowAnimations(@StyleRes int resId) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.windowAnimations = resId;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Specify an explicit soft input mode to use for the window, as per
+ * {@link WindowManager.LayoutParams#softInputMode
+ * WindowManager.LayoutParams.softInputMode}. Providing anything besides
+ * "unspecified" here will override the input mode the window would
+ * normally retrieve from its theme.
+ */
+ public void setSoftInputMode(int mode) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ if (mode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+ attrs.softInputMode = mode;
+ mHasSoftInputMode = true;
+ } else {
+ mHasSoftInputMode = false;
+ }
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Convenience function to set the flag bits as specified in flags, as
+ * per {@link #setFlags}.
+ * @param flags The flag bits to be set.
+ * @see #setFlags
+ * @see #clearFlags
+ */
+ public void addFlags(int flags) {
+ setFlags(flags, flags);
+ }
+
+ /** @hide */
+ public void addPrivateFlags(int flags) {
+ setPrivateFlags(flags, flags);
+ }
+
+ /**
+ * Convenience function to clear the flag bits as specified in flags, as
+ * per {@link #setFlags}.
+ * @param flags The flag bits to be cleared.
+ * @see #setFlags
+ * @see #addFlags
+ */
+ public void clearFlags(int flags) {
+ setFlags(0, flags);
+ }
+
+ /**
+ * Set the flags of the window, as per the
+ * {@link WindowManager.LayoutParams WindowManager.LayoutParams}
+ * flags.
+ *
+ * <p>Note that some flags must be set before the window decoration is
+ * created (by the first call to
+ * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} or
+ * {@link #getDecorView()}:
+ * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN} and
+ * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}. These
+ * will be set for you based on the {@link android.R.attr#windowIsFloating}
+ * attribute.
+ *
+ * @param flags The new window flags (see WindowManager.LayoutParams).
+ * @param mask Which of the window flag bits to modify.
+ * @see #addFlags
+ * @see #clearFlags
+ */
+ public void setFlags(int flags, int mask) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.flags = (attrs.flags&~mask) | (flags&mask);
+ mForcedWindowFlags |= mask;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ private void setPrivateFlags(int flags, int mask) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.privateFlags = (attrs.privateFlags & ~mask) | (flags & mask);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * {@hide}
+ */
+ protected void setNeedsMenuKey(int value) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.needsMenuKey = value;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * {@hide}
+ */
+ protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+ if (mCallback != null) {
+ mCallback.onWindowAttributesChanged(attrs);
+ }
+ }
+
+ /**
+ * <p>Sets the requested color mode of the window. The requested the color mode might
+ * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+ *
+ * <p>The requested color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+ * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+ *
+ * <p>The requested color mode is not guaranteed to be honored. Please refer to
+ * {@link #getColorMode()} for more information.</p>
+ *
+ * @see #getColorMode()
+ * @see Display#isWideColorGamut()
+ * @see Configuration#isScreenWideColorGamut()
+ */
+ public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setColorMode(colorMode);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Returns the requested color mode of the window, one of
+ * {@link ActivityInfo#COLOR_MODE_DEFAULT}, {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT}
+ * or {@link ActivityInfo#COLOR_MODE_HDR}. If {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT}
+ * was requested it is possible the window will not be put in wide color gamut mode depending
+ * on device and display support for that mode. Use {@link #isWideColorGamut} to determine
+ * if the window is currently in wide color gamut mode.
+ *
+ * @see #setColorMode(int)
+ * @see Display#isWideColorGamut()
+ * @see Configuration#isScreenWideColorGamut()
+ */
+ @ActivityInfo.ColorMode
+ public int getColorMode() {
+ return getAttributes().getColorMode();
+ }
+
+ /**
+ * Returns true if this window's color mode is {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT},
+ * the display has a wide color gamut and this device supports wide color gamut rendering.
+ *
+ * @see Display#isWideColorGamut()
+ * @see Configuration#isScreenWideColorGamut()
+ */
+ public boolean isWideColorGamut() {
+ return getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
+ && getContext().getResources().getConfiguration().isScreenWideColorGamut();
+ }
+
+ /**
+ * Set the amount of dim behind the window when using
+ * {@link WindowManager.LayoutParams#FLAG_DIM_BEHIND}. This overrides
+ * the default dim amount of that is selected by the Window based on
+ * its theme.
+ *
+ * @param amount The new dim amount, from 0 for no dim to 1 for full dim.
+ */
+ public void setDimAmount(float amount) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.dimAmount = amount;
+ mHaveDimAmount = true;
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * Specify custom window attributes. <strong>PLEASE NOTE:</strong> the
+ * layout params you give here should generally be from values previously
+ * retrieved with {@link #getAttributes()}; you probably do not want to
+ * blindly create and apply your own, since this will blow away any values
+ * set by the framework that you are not interested in.
+ *
+ * @param a The new window attributes, which will completely override any
+ * current values.
+ */
+ public void setAttributes(WindowManager.LayoutParams a) {
+ mWindowAttributes.copyFrom(a);
+ dispatchWindowAttributesChanged(mWindowAttributes);
+ }
+
+ /**
+ * Retrieve the current window attributes associated with this panel.
+ *
+ * @return WindowManager.LayoutParams Either the existing window
+ * attributes object, or a freshly created one if there is none.
+ */
+ public final WindowManager.LayoutParams getAttributes() {
+ return mWindowAttributes;
+ }
+
+ /**
+ * Return the window flags that have been explicitly set by the client,
+ * so will not be modified by {@link #getDecorView}.
+ */
+ protected final int getForcedWindowFlags() {
+ return mForcedWindowFlags;
+ }
+
+ /**
+ * Has the app specified their own soft input mode?
+ */
+ protected final boolean hasSoftInputMode() {
+ return mHasSoftInputMode;
+ }
+
+ /** @hide */
+ public void setCloseOnTouchOutside(boolean close) {
+ mCloseOnTouchOutside = close;
+ mSetCloseOnTouchOutside = true;
+ }
+
+ /** @hide */
+ public void setCloseOnTouchOutsideIfNotSet(boolean close) {
+ if (!mSetCloseOnTouchOutside) {
+ mCloseOnTouchOutside = close;
+ mSetCloseOnTouchOutside = true;
+ }
+ }
+
+ /** @hide */
+ @SystemApi
+ public void setDisableWallpaperTouchEvents(boolean disable) {
+ setPrivateFlags(disable
+ ? WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS : 0,
+ WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS);
+ }
+
+ /** @hide */
+ public abstract void alwaysReadCloseOnTouchAttr();
+
+ /** @hide */
+ public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
+ final boolean isOutside =
+ event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
+ || event.getAction() == MotionEvent.ACTION_OUTSIDE;
+ if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
+ return true;
+ }
+ return false;
+ }
+
+ /* Sets the Sustained Performance requirement for the calling window.
+ * @param enable disables or enables the mode.
+ */
+ public void setSustainedPerformanceMode(boolean enable) {
+ setPrivateFlags(enable
+ ? WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE : 0,
+ WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE);
+ }
+
+ private boolean isOutOfBounds(Context context, MotionEvent event) {
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
+ final View decorView = getDecorView();
+ return (x < -slop) || (y < -slop)
+ || (x > (decorView.getWidth()+slop))
+ || (y > (decorView.getHeight()+slop));
+ }
+
+ /**
+ * Enable extended screen features. This must be called before
+ * setContentView(). May be called as many times as desired as long as it
+ * is before setContentView(). If not called, no extended features
+ * will be available. You can not turn off a feature once it is requested.
+ * You canot use other title features with {@link #FEATURE_CUSTOM_TITLE}.
+ *
+ * @param featureId The desired features, defined as constants by Window.
+ * @return The features that are now set.
+ */
+ public boolean requestFeature(int featureId) {
+ final int flag = 1<<featureId;
+ mFeatures |= flag;
+ mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
+ return (mFeatures&flag) != 0;
+ }
+
+ /**
+ * @hide Used internally to help resolve conflicting features.
+ */
+ protected void removeFeature(int featureId) {
+ final int flag = 1<<featureId;
+ mFeatures &= ~flag;
+ mLocalFeatures &= ~(mContainer != null ? (flag&~mContainer.mFeatures) : flag);
+ }
+
+ public final void makeActive() {
+ if (mContainer != null) {
+ if (mContainer.mActiveChild != null) {
+ mContainer.mActiveChild.mIsActive = false;
+ }
+ mContainer.mActiveChild = this;
+ }
+ mIsActive = true;
+ onActive();
+ }
+
+ public final boolean isActive()
+ {
+ return mIsActive;
+ }
+
+ /**
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link android.app.Activity#onCreate}. This will
+ * implicitly call {@link #getDecorView} with all of the associated
+ * side-effects.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
+ */
+ @Nullable
+ public <T extends View> T findViewById(@IdRes int id) {
+ return getDecorView().findViewById(id);
+ }
+
+ /**
+ * Convenience for
+ * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+ * to set the screen content from a layout resource. The resource will be
+ * inflated, adding all top-level views to the screen.
+ *
+ * @param layoutResID Resource ID to be inflated.
+ * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
+ */
+ public abstract void setContentView(@LayoutRes int layoutResID);
+
+ /**
+ * Convenience for
+ * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+ * set the screen content to an explicit view. This view is placed
+ * directly into the screen's view hierarchy. It can itself be a complex
+ * view hierarhcy.
+ *
+ * @param view The desired content to display.
+ * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
+ */
+ public abstract void setContentView(View view);
+
+ /**
+ * Set the screen content to an explicit view. This view is placed
+ * directly into the screen's view hierarchy. It can itself be a complex
+ * view hierarchy.
+ *
+ * <p>Note that calling this function "locks in" various characteristics
+ * of the window that can not, from this point forward, be changed: the
+ * features that have been requested with {@link #requestFeature(int)},
+ * and certain window flags as described in {@link #setFlags(int, int)}.</p>
+ *
+ * <p>If {@link #FEATURE_CONTENT_TRANSITIONS} is set, the window's
+ * TransitionManager will be used to animate content from the current
+ * content View to view.</p>
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ * @see #getTransitionManager()
+ * @see #setTransitionManager(android.transition.TransitionManager)
+ */
+ public abstract void setContentView(View view, ViewGroup.LayoutParams params);
+
+ /**
+ * Variation on
+ * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
+ * to add an additional content view to the screen. Added after any existing
+ * ones in the screen -- existing views are NOT removed.
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ */
+ public abstract void addContentView(View view, ViewGroup.LayoutParams params);
+
+ /**
+ * Remove the view that was used as the screen content.
+ *
+ * @hide
+ */
+ public abstract void clearContentView();
+
+ /**
+ * Return the view in this Window that currently has focus, or null if
+ * there are none. Note that this does not look in any containing
+ * Window.
+ *
+ * @return View The current View with focus or null.
+ */
+ @Nullable
+ public abstract View getCurrentFocus();
+
+ /**
+ * Quick access to the {@link LayoutInflater} instance that this Window
+ * retrieved from its Context.
+ *
+ * @return LayoutInflater The shared LayoutInflater.
+ */
+ @NonNull
+ public abstract LayoutInflater getLayoutInflater();
+
+ public abstract void setTitle(CharSequence title);
+
+ @Deprecated
+ public abstract void setTitleColor(@ColorInt int textColor);
+
+ public abstract void openPanel(int featureId, KeyEvent event);
+
+ public abstract void closePanel(int featureId);
+
+ public abstract void togglePanel(int featureId, KeyEvent event);
+
+ public abstract void invalidatePanelMenu(int featureId);
+
+ public abstract boolean performPanelShortcut(int featureId,
+ int keyCode,
+ KeyEvent event,
+ int flags);
+ public abstract boolean performPanelIdentifierAction(int featureId,
+ int id,
+ int flags);
+
+ public abstract void closeAllPanels();
+
+ public abstract boolean performContextMenuIdentifierAction(int id, int flags);
+
+ /**
+ * Should be called when the configuration is changed.
+ *
+ * @param newConfig The new configuration.
+ */
+ public abstract void onConfigurationChanged(Configuration newConfig);
+
+ /**
+ * Sets the window elevation.
+ * <p>
+ * Changes to this property take effect immediately and will cause the
+ * window surface to be recreated. This is an expensive operation and as a
+ * result, this property should not be animated.
+ *
+ * @param elevation The window elevation.
+ * @see View#setElevation(float)
+ * @see android.R.styleable#Window_windowElevation
+ */
+ public void setElevation(float elevation) {}
+
+ /**
+ * Gets the window elevation.
+ *
+ * @hide
+ */
+ public float getElevation() {
+ return 0.0f;
+ }
+
+ /**
+ * Sets whether window content should be clipped to the outline of the
+ * window background.
+ *
+ * @param clipToOutline Whether window content should be clipped to the
+ * outline of the window background.
+ * @see View#setClipToOutline(boolean)
+ * @see android.R.styleable#Window_windowClipToOutline
+ */
+ public void setClipToOutline(boolean clipToOutline) {}
+
+ /**
+ * Change the background of this window to a Drawable resource. Setting the
+ * background to null will make the window be opaque. To make the window
+ * transparent, you can use an empty drawable (for instance a ColorDrawable
+ * with the color 0 or the system drawable android:drawable/empty.)
+ *
+ * @param resId The resource identifier of a drawable resource which will
+ * be installed as the new background.
+ */
+ public void setBackgroundDrawableResource(@DrawableRes int resId) {
+ setBackgroundDrawable(mContext.getDrawable(resId));
+ }
+
+ /**
+ * Change the background of this window to a custom Drawable. Setting the
+ * background to null will make the window be opaque. To make the window
+ * transparent, you can use an empty drawable (for instance a ColorDrawable
+ * with the color 0 or the system drawable android:drawable/empty.)
+ *
+ * @param drawable The new Drawable to use for this window's background.
+ */
+ public abstract void setBackgroundDrawable(Drawable drawable);
+
+ /**
+ * Set the value for a drawable feature of this window, from a resource
+ * identifier. You must have called requestFeature(featureId) before
+ * calling this function.
+ *
+ * @see android.content.res.Resources#getDrawable(int)
+ *
+ * @param featureId The desired drawable feature to change, defined as a
+ * constant by Window.
+ * @param resId Resource identifier of the desired image.
+ */
+ public abstract void setFeatureDrawableResource(int featureId, @DrawableRes int resId);
+
+ /**
+ * Set the value for a drawable feature of this window, from a URI. You
+ * must have called requestFeature(featureId) before calling this
+ * function.
+ *
+ * <p>The only URI currently supported is "content:", specifying an image
+ * in a content provider.
+ *
+ * @see android.widget.ImageView#setImageURI
+ *
+ * @param featureId The desired drawable feature to change. Features are
+ * constants defined by Window.
+ * @param uri The desired URI.
+ */
+ public abstract void setFeatureDrawableUri(int featureId, Uri uri);
+
+ /**
+ * Set an explicit Drawable value for feature of this window. You must
+ * have called requestFeature(featureId) before calling this function.
+ *
+ * @param featureId The desired drawable feature to change. Features are
+ * constants defined by Window.
+ * @param drawable A Drawable object to display.
+ */
+ public abstract void setFeatureDrawable(int featureId, Drawable drawable);
+
+ /**
+ * Set a custom alpha value for the given drawable feature, controlling how
+ * much the background is visible through it.
+ *
+ * @param featureId The desired drawable feature to change. Features are
+ * constants defined by Window.
+ * @param alpha The alpha amount, 0 is completely transparent and 255 is
+ * completely opaque.
+ */
+ public abstract void setFeatureDrawableAlpha(int featureId, int alpha);
+
+ /**
+ * Set the integer value for a feature. The range of the value depends on
+ * the feature being set. For {@link #FEATURE_PROGRESS}, it should go from
+ * 0 to 10000. At 10000 the progress is complete and the indicator hidden.
+ *
+ * @param featureId The desired feature to change. Features are constants
+ * defined by Window.
+ * @param value The value for the feature. The interpretation of this
+ * value is feature-specific.
+ */
+ public abstract void setFeatureInt(int featureId, int value);
+
+ /**
+ * Request that key events come to this activity. Use this if your
+ * activity has no views with focus, but the activity still wants
+ * a chance to process key events.
+ */
+ public abstract void takeKeyEvents(boolean get);
+
+ /**
+ * Used by custom windows, such as Dialog, to pass the key press event
+ * further down the view hierarchy. Application developers should
+ * not need to implement or call this.
+ *
+ */
+ public abstract boolean superDispatchKeyEvent(KeyEvent event);
+
+ /**
+ * Used by custom windows, such as Dialog, to pass the key shortcut press event
+ * further down the view hierarchy. Application developers should
+ * not need to implement or call this.
+ *
+ */
+ public abstract boolean superDispatchKeyShortcutEvent(KeyEvent event);
+
+ /**
+ * Used by custom windows, such as Dialog, to pass the touch screen event
+ * further down the view hierarchy. Application developers should
+ * not need to implement or call this.
+ *
+ */
+ public abstract boolean superDispatchTouchEvent(MotionEvent event);
+
+ /**
+ * Used by custom windows, such as Dialog, to pass the trackball event
+ * further down the view hierarchy. Application developers should
+ * not need to implement or call this.
+ *
+ */
+ public abstract boolean superDispatchTrackballEvent(MotionEvent event);
+
+ /**
+ * Used by custom windows, such as Dialog, to pass the generic motion event
+ * further down the view hierarchy. Application developers should
+ * not need to implement or call this.
+ *
+ */
+ public abstract boolean superDispatchGenericMotionEvent(MotionEvent event);
+
+ /**
+ * Retrieve the top-level window decor view (containing the standard
+ * window frame/decorations and the client's content inside of that), which
+ * can be added as a window to the window manager.
+ *
+ * <p><em>Note that calling this function for the first time "locks in"
+ * various window characteristics as described in
+ * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
+ *
+ * @return Returns the top-level window decor view.
+ */
+ public abstract View getDecorView();
+
+ /**
+ * Retrieve the current decor view, but only if it has already been created;
+ * otherwise returns null.
+ *
+ * @return Returns the top-level window decor or null.
+ * @see #getDecorView
+ */
+ public abstract View peekDecorView();
+
+ public abstract Bundle saveHierarchyState();
+
+ public abstract void restoreHierarchyState(Bundle savedInstanceState);
+
+ protected abstract void onActive();
+
+ /**
+ * Return the feature bits that are enabled. This is the set of features
+ * that were given to requestFeature(), and are being handled by this
+ * Window itself or its container. That is, it is the set of
+ * requested features that you can actually use.
+ *
+ * <p>To do: add a public version of this API that allows you to check for
+ * features by their feature ID.
+ *
+ * @return int The feature bits.
+ */
+ protected final int getFeatures()
+ {
+ return mFeatures;
+ }
+
+ /**
+ * Return the feature bits set by default on a window.
+ * @param context The context used to access resources
+ */
+ public static int getDefaultFeatures(Context context) {
+ int features = 0;
+
+ final Resources res = context.getResources();
+ if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureOptionsPanel)) {
+ features |= 1 << FEATURE_OPTIONS_PANEL;
+ }
+
+ if (res.getBoolean(com.android.internal.R.bool.config_defaultWindowFeatureContextMenu)) {
+ features |= 1 << FEATURE_CONTEXT_MENU;
+ }
+
+ return features;
+ }
+
+ /**
+ * Query for the availability of a certain feature.
+ *
+ * @param feature The feature ID to check
+ * @return true if the feature is enabled, false otherwise.
+ */
+ public boolean hasFeature(int feature) {
+ return (getFeatures() & (1 << feature)) != 0;
+ }
+
+ /**
+ * Return the feature bits that are being implemented by this Window.
+ * This is the set of features that were given to requestFeature(), and are
+ * being handled by only this Window itself, not by its containers.
+ *
+ * @return int The feature bits.
+ */
+ protected final int getLocalFeatures()
+ {
+ return mLocalFeatures;
+ }
+
+ /**
+ * Set the default format of window, as per the PixelFormat types. This
+ * is the format that will be used unless the client specifies in explicit
+ * format with setFormat();
+ *
+ * @param format The new window format (see PixelFormat).
+ *
+ * @see #setFormat
+ * @see PixelFormat
+ */
+ protected void setDefaultWindowFormat(int format) {
+ mDefaultWindowFormat = format;
+ if (!mHaveWindowFormat) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.format = format;
+ dispatchWindowAttributesChanged(attrs);
+ }
+ }
+
+ /** @hide */
+ protected boolean haveDimAmount() {
+ return mHaveDimAmount;
+ }
+
+ public abstract void setChildDrawable(int featureId, Drawable drawable);
+
+ public abstract void setChildInt(int featureId, int value);
+
+ /**
+ * Is a keypress one of the defined shortcut keys for this window.
+ * @param keyCode the key code from {@link android.view.KeyEvent} to check.
+ * @param event the {@link android.view.KeyEvent} to use to help check.
+ */
+ public abstract boolean isShortcutKey(int keyCode, KeyEvent event);
+
+ /**
+ * @see android.app.Activity#setVolumeControlStream(int)
+ */
+ public abstract void setVolumeControlStream(int streamType);
+
+ /**
+ * @see android.app.Activity#getVolumeControlStream()
+ */
+ public abstract int getVolumeControlStream();
+
+ /**
+ * Sets a {@link MediaController} to send media keys and volume changes to.
+ * If set, this should be preferred for all media keys and volume requests
+ * sent to this window.
+ *
+ * @param controller The controller for the session which should receive
+ * media keys and volume changes.
+ * @see android.app.Activity#setMediaController(android.media.session.MediaController)
+ */
+ public void setMediaController(MediaController controller) {
+ }
+
+ /**
+ * Gets the {@link MediaController} that was previously set.
+ *
+ * @return The controller which should receive events.
+ * @see #setMediaController(android.media.session.MediaController)
+ * @see android.app.Activity#getMediaController()
+ */
+ public MediaController getMediaController() {
+ return null;
+ }
+
+ /**
+ * Set extra options that will influence the UI for this window.
+ * @param uiOptions Flags specifying extra options for this window.
+ */
+ public void setUiOptions(int uiOptions) { }
+
+ /**
+ * Set extra options that will influence the UI for this window.
+ * Only the bits filtered by mask will be modified.
+ * @param uiOptions Flags specifying extra options for this window.
+ * @param mask Flags specifying which options should be modified. Others will remain unchanged.
+ */
+ public void setUiOptions(int uiOptions, int mask) { }
+
+ /**
+ * Set the primary icon for this window.
+ *
+ * @param resId resource ID of a drawable to set
+ */
+ public void setIcon(@DrawableRes int resId) { }
+
+ /**
+ * Set the default icon for this window.
+ * This will be overridden by any other icon set operation which could come from the
+ * theme or another explicit set.
+ *
+ * @hide
+ */
+ public void setDefaultIcon(@DrawableRes int resId) { }
+
+ /**
+ * Set the logo for this window. A logo is often shown in place of an
+ * {@link #setIcon(int) icon} but is generally wider and communicates window title information
+ * as well.
+ *
+ * @param resId resource ID of a drawable to set
+ */
+ public void setLogo(@DrawableRes int resId) { }
+
+ /**
+ * Set the default logo for this window.
+ * This will be overridden by any other logo set operation which could come from the
+ * theme or another explicit set.
+ *
+ * @hide
+ */
+ public void setDefaultLogo(@DrawableRes int resId) { }
+
+ /**
+ * Set focus locally. The window should have the
+ * {@link WindowManager.LayoutParams#FLAG_LOCAL_FOCUS_MODE} flag set already.
+ * @param hasFocus Whether this window has focus or not.
+ * @param inTouchMode Whether this window is in touch mode or not.
+ */
+ public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { }
+
+ /**
+ * Inject an event to window locally.
+ * @param event A key or touch event to inject to this window.
+ */
+ public void injectInputEvent(InputEvent event) { }
+
+ /**
+ * Retrieve the {@link TransitionManager} responsible for for default transitions
+ * in this window. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return non-null after content has been initialized (e.g. by using
+ * {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+ *
+ * @return This window's content TransitionManager or null if none is set.
+ * @attr ref android.R.styleable#Window_windowContentTransitionManager
+ */
+ public TransitionManager getTransitionManager() {
+ return null;
+ }
+
+ /**
+ * Set the {@link TransitionManager} to use for default transitions in this window.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param tm The TransitionManager to use for scene changes.
+ * @attr ref android.R.styleable#Window_windowContentTransitionManager
+ */
+ public void setTransitionManager(TransitionManager tm) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Retrieve the {@link Scene} representing this window's current content.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return null if the current content is not represented by a Scene.</p>
+ *
+ * @return Current Scene being shown or null
+ */
+ public Scene getContentScene() {
+ return null;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected.
+ *
+ * @param transition The Transition to use to move Views into the initial Scene.
+ * @attr ref android.R.styleable#Window_windowEnterTransition
+ */
+ public void setEnterTransition(Transition transition) {}
+
+ /**
+ * Sets the Transition that will be used to move Views out of the scene when the Window is
+ * preparing to close, for example after a call to
+ * {@link android.app.Activity#finishAfterTransition()}. The exiting
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#VISIBLE} to {@link View#INVISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected. If nothing is set, the default will be to
+ * use the same value as set in {@link #setEnterTransition(android.transition.Transition)}.
+ *
+ * @param transition The Transition to use to move Views out of the Scene when the Window
+ * is preparing to close.
+ * @attr ref android.R.styleable#Window_windowReturnTransition
+ */
+ public void setReturnTransition(Transition transition) {}
+
+ /**
+ * Sets the Transition that will be used to move Views out of the scene when starting a
+ * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param transition The Transition to use to move Views out of the scene when calling a
+ * new Activity.
+ * @attr ref android.R.styleable#Window_windowExitTransition
+ */
+ public void setExitTransition(Transition transition) {}
+
+ /**
+ * Sets the Transition that will be used to move Views in to the scene when returning from
+ * a previously-started Activity. The entering Views will be those that are regular Views
+ * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+ * will extend {@link android.transition.Visibility} as exiting is governed by changing
+ * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null,
+ * the views will remain unaffected. If nothing is set, the default will be to use the same
+ * transition as {@link #setExitTransition(android.transition.Transition)}.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param transition The Transition to use to move Views into the scene when reentering from a
+ * previously-started Activity.
+ * @attr ref android.R.styleable#Window_windowReenterTransition
+ */
+ public void setReenterTransition(Transition transition) {}
+
+ /**
+ * Returns the transition used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If <code>transition</code> is null,
+ * entering Views will remain unaffected. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @return the Transition to use to move Views into the initial Scene.
+ * @attr ref android.R.styleable#Window_windowEnterTransition
+ */
+ public Transition getEnterTransition() { return null; }
+
+ /**
+ * Returns the Transition that will be used to move Views out of the scene when the Window is
+ * preparing to close, for example after a call to
+ * {@link android.app.Activity#finishAfterTransition()}. The exiting
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#VISIBLE} to {@link View#INVISIBLE}.
+ *
+ * @return The Transition to use to move Views out of the Scene when the Window
+ * is preparing to close.
+ * @attr ref android.R.styleable#Window_windowReturnTransition
+ */
+ public Transition getReturnTransition() { return null; }
+
+ /**
+ * Returns the Transition that will be used to move Views out of the scene when starting a
+ * new Activity. The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @return the Transition to use to move Views out of the scene when calling a
+ * new Activity.
+ * @attr ref android.R.styleable#Window_windowExitTransition
+ */
+ public Transition getExitTransition() { return null; }
+
+ /**
+ * Returns the Transition that will be used to move Views in to the scene when returning from
+ * a previously-started Activity. The entering Views will be those that are regular Views
+ * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+ * will extend {@link android.transition.Visibility} as exiting is governed by changing
+ * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @return The Transition to use to move Views into the scene when reentering from a
+ * previously-started Activity.
+ * @attr ref android.R.styleable#Window_windowReenterTransition
+ */
+ public Transition getReenterTransition() { return null; }
+
+ /**
+ * Sets the Transition that will be used for shared elements transferred into the content
+ * Scene. Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param transition The Transition to use for shared elements transferred into the content
+ * Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementEnterTransition
+ */
+ public void setSharedElementEnterTransition(Transition transition) {}
+
+ /**
+ * Sets the Transition that will be used for shared elements transferred back to a
+ * calling Activity. Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * If no value is set, the default will be to use the same value as
+ * {@link #setSharedElementEnterTransition(android.transition.Transition)}.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param transition The Transition to use for shared elements transferred out of the content
+ * Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementReturnTransition
+ */
+ public void setSharedElementReturnTransition(Transition transition) {}
+
+ /**
+ * Returns the Transition that will be used for shared elements transferred into the content
+ * Scene. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @return Transition to use for sharend elements transferred into the content Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementEnterTransition
+ */
+ public Transition getSharedElementEnterTransition() { return null; }
+
+ /**
+ * Returns the Transition that will be used for shared elements transferred back to a
+ * calling Activity. Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @return Transition to use for sharend elements transferred into the content Scene.
+ * @attr ref android.R.styleable#Window_windowSharedElementReturnTransition
+ */
+ public Transition getSharedElementReturnTransition() { return null; }
+
+ /**
+ * Sets the Transition that will be used for shared elements after starting a new Activity
+ * before the shared elements are transferred to the called Activity. If the shared elements
+ * must animate during the exit transition, this Transition should be used. Upon completion,
+ * the shared elements may be transferred to the started Activity.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param transition The Transition to use for shared elements in the launching Window
+ * prior to transferring to the launched Activity's Window.
+ * @attr ref android.R.styleable#Window_windowSharedElementExitTransition
+ */
+ public void setSharedElementExitTransition(Transition transition) {}
+
+ /**
+ * Sets the Transition that will be used for shared elements reentering from a started
+ * Activity after it has returned the shared element to it start location. If no value
+ * is set, this will default to
+ * {@link #setSharedElementExitTransition(android.transition.Transition)}.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param transition The Transition to use for shared elements in the launching Window
+ * after the shared element has returned to the Window.
+ * @attr ref android.R.styleable#Window_windowSharedElementReenterTransition
+ */
+ public void setSharedElementReenterTransition(Transition transition) {}
+
+ /**
+ * Returns the Transition to use for shared elements in the launching Window prior
+ * to transferring to the launched Activity's Window.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @return the Transition to use for shared elements in the launching Window prior
+ * to transferring to the launched Activity's Window.
+ * @attr ref android.R.styleable#Window_windowSharedElementExitTransition
+ */
+ public Transition getSharedElementExitTransition() { return null; }
+
+ /**
+ * Returns the Transition that will be used for shared elements reentering from a started
+ * Activity after it has returned the shared element to it start location.
+ * Requires {@link #FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @return the Transition that will be used for shared elements reentering from a started
+ * Activity after it has returned the shared element to it start location.
+ * @attr ref android.R.styleable#Window_windowSharedElementReenterTransition
+ */
+ public Transition getSharedElementReenterTransition() { return null; }
+
+ /**
+ * Controls how the transition set in
+ * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the calling Activity. When true, the transition will start as soon as possible.
+ * When false, the transition will wait until the remote exiting transition completes before
+ * starting. The default value is true.
+ *
+ * @param allow true to start the enter transition when possible or false to
+ * wait until the exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowEnterTransitionOverlap
+ */
+ public void setAllowEnterTransitionOverlap(boolean allow) {}
+
+ /**
+ * Returns how the transition set in
+ * {@link #setEnterTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the calling Activity. When true, the transition will start as soon as possible.
+ * When false, the transition will wait until the remote exiting transition completes before
+ * starting. The default value is true.
+ *
+ * @return true when the enter transition should start as soon as possible or false to
+ * when it should wait until the exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowEnterTransitionOverlap
+ */
+ public boolean getAllowEnterTransitionOverlap() { return true; }
+
+ /**
+ * Controls how the transition set in
+ * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the called Activity when reentering after if finishes. When true,
+ * the transition will start as soon as possible. When false, the transition will wait
+ * until the called Activity's exiting transition completes before starting.
+ * The default value is true.
+ *
+ * @param allow true to start the transition when possible or false to wait until the
+ * called Activity's exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowReturnTransitionOverlap
+ */
+ public void setAllowReturnTransitionOverlap(boolean allow) {}
+
+ /**
+ * Returns how the transition set in
+ * {@link #setExitTransition(android.transition.Transition)} overlaps with the exit
+ * transition of the called Activity when reentering after if finishes. When true,
+ * the transition will start as soon as possible. When false, the transition will wait
+ * until the called Activity's exiting transition completes before starting.
+ * The default value is true.
+ *
+ * @return true when the transition should start when possible or false when it should wait
+ * until the called Activity's exiting transition completes.
+ * @attr ref android.R.styleable#Window_windowAllowReturnTransitionOverlap
+ */
+ public boolean getAllowReturnTransitionOverlap() { return true; }
+
+ /**
+ * Returns the duration, in milliseconds, of the window background fade
+ * when transitioning into or away from an Activity when called with an Activity Transition.
+ * <p>When executing the enter transition, the background starts transparent
+ * and fades in. This requires {@link #FEATURE_ACTIVITY_TRANSITIONS}. The default is
+ * 300 milliseconds.</p>
+ *
+ * @return The duration of the window background fade to opaque during enter transition.
+ * @see #getEnterTransition()
+ * @attr ref android.R.styleable#Window_windowTransitionBackgroundFadeDuration
+ */
+ public long getTransitionBackgroundFadeDuration() { return 0; }
+
+ /**
+ * Sets the duration, in milliseconds, of the window background fade
+ * when transitioning into or away from an Activity when called with an Activity Transition.
+ * <p>When executing the enter transition, the background starts transparent
+ * and fades in. This requires {@link #FEATURE_ACTIVITY_TRANSITIONS}. The default is
+ * 300 milliseconds.</p>
+ *
+ * @param fadeDurationMillis The duration of the window background fade to or from opaque
+ * during enter transition.
+ * @see #setEnterTransition(android.transition.Transition)
+ * @attr ref android.R.styleable#Window_windowTransitionBackgroundFadeDuration
+ */
+ public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) { }
+
+ /**
+ * Returns <code>true</code> when shared elements should use an Overlay during
+ * shared element transitions or <code>false</code> when they should animate as
+ * part of the normal View hierarchy. The default value is true.
+ *
+ * @return <code>true</code> when shared elements should use an Overlay during
+ * shared element transitions or <code>false</code> when they should animate as
+ * part of the normal View hierarchy.
+ * @attr ref android.R.styleable#Window_windowSharedElementsUseOverlay
+ */
+ public boolean getSharedElementsUseOverlay() { return true; }
+
+ /**
+ * Sets whether or not shared elements should use an Overlay during shared element transitions.
+ * The default value is true.
+ *
+ * @param sharedElementsUseOverlay <code>true</code> indicates that shared elements should
+ * be transitioned with an Overlay or <code>false</code>
+ * to transition within the normal View hierarchy.
+ * @attr ref android.R.styleable#Window_windowSharedElementsUseOverlay
+ */
+ public void setSharedElementsUseOverlay(boolean sharedElementsUseOverlay) { }
+
+ /**
+ * @return the color of the status bar.
+ */
+ @ColorInt
+ public abstract int getStatusBarColor();
+
+ /**
+ * Sets the color of the status bar to {@code color}.
+ *
+ * For this to take effect,
+ * the window must be drawing the system bar backgrounds with
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
+ *
+ * If {@code color} is not opaque, consider setting
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
+ * <p>
+ * The transitionName for the view background will be "android:status:background".
+ * </p>
+ */
+ public abstract void setStatusBarColor(@ColorInt int color);
+
+ /**
+ * @return the color of the navigation bar.
+ */
+ @ColorInt
+ public abstract int getNavigationBarColor();
+
+ /**
+ * Sets the color of the navigation bar to {@param color}.
+ *
+ * For this to take effect,
+ * the window must be drawing the system bar backgrounds with
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
+ *
+ * If {@param color} is not opaque, consider setting
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+ * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+ * <p>
+ * The transitionName for the view background will be "android:navigation:background".
+ * </p>
+ */
+ public abstract void setNavigationBarColor(@ColorInt int color);
+
+ /** @hide */
+ public void setTheme(int resId) {
+ }
+
+ /**
+ * Whether the caption should be displayed directly on the content rather than push the content
+ * down. This affects only freeform windows since they display the caption.
+ * @hide
+ */
+ public void setOverlayWithDecorCaptionEnabled(boolean enabled) {
+ mOverlayWithDecorCaptionEnabled = enabled;
+ }
+
+ /** @hide */
+ public boolean isOverlayWithDecorCaptionEnabled() {
+ return mOverlayWithDecorCaptionEnabled;
+ }
+
+ /** @hide */
+ public void notifyRestrictedCaptionAreaCallback(int left, int top, int right, int bottom) {
+ if (mOnRestrictedCaptionAreaChangedListener != null) {
+ mRestrictedCaptionAreaRect.set(left, top, right, bottom);
+ mOnRestrictedCaptionAreaChangedListener.onRestrictedCaptionAreaChanged(
+ mRestrictedCaptionAreaRect);
+ }
+ }
+
+ /**
+ * Set what color should the caption controls be. By default the system will try to determine
+ * the color from the theme. You can overwrite this by using {@link #DECOR_CAPTION_SHADE_DARK},
+ * {@link #DECOR_CAPTION_SHADE_LIGHT}, or {@link #DECOR_CAPTION_SHADE_AUTO}.
+ * @see #DECOR_CAPTION_SHADE_DARK
+ * @see #DECOR_CAPTION_SHADE_LIGHT
+ * @see #DECOR_CAPTION_SHADE_AUTO
+ */
+ public abstract void setDecorCaptionShade(int decorCaptionShade);
+
+ /**
+ * Set the drawable that is drawn underneath the caption during the resizing.
+ *
+ * During the resizing the caption might not be drawn fast enough to match the new dimensions.
+ * There is a second caption drawn underneath it that will be fast enough. By default the
+ * caption is constructed from the theme. You can provide a drawable, that will be drawn instead
+ * to better match your application.
+ */
+ public abstract void setResizingCaptionDrawable(Drawable drawable);
+
+ /**
+ * Called when the activity changes from fullscreen mode to multi-window mode and visa-versa.
+ * @hide
+ */
+ public abstract void onMultiWindowModeChanged();
+
+ /**
+ * Called when the activity changes to/from picture-in-picture mode.
+ * @hide
+ */
+ public abstract void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+
+ /**
+ * Called when the activity just relaunched.
+ * @hide
+ */
+ public abstract void reportActivityRelaunched();
+
+ /**
+ * Called to set flag to check if the close on swipe is enabled. This will only function if
+ * FEATURE_SWIPE_TO_DISMISS has been set.
+ * @hide
+ */
+ public void setCloseOnSwipeEnabled(boolean closeOnSwipeEnabled) {
+ mCloseOnSwipeEnabled = closeOnSwipeEnabled;
+ }
+
+ /**
+ * @return {@code true} if the close on swipe is enabled.
+ * @hide
+ */
+ public boolean isCloseOnSwipeEnabled() {
+ return mCloseOnSwipeEnabled;
+ }
+}
diff --git a/android/view/WindowAnimationFrameStats.java b/android/view/WindowAnimationFrameStats.java
new file mode 100644
index 00000000..c60b96ca
--- /dev/null
+++ b/android/view/WindowAnimationFrameStats.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window animation frame statistics. For example, a window
+ * animation is usually performed when the application is transitioning from one
+ * activity to another. The frame statistics are a snapshot for the time interval
+ * from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience the system should
+ * run window animations at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the system does not render a frame every refresh
+ * period the user will see irregular window transitions. The time when the frame was
+ * actually presented on the display by calling {@link #getFramePresentedTimeNano(int)}.
+ */
+public final class WindowAnimationFrameStats extends FrameStats implements Parcelable {
+ /**
+ * @hide
+ */
+ public WindowAnimationFrameStats() {
+ /* do nothing */
+ }
+
+ /**
+ * Initializes this isntance.
+ *
+ * @param refreshPeriodNano The display refresh period.
+ * @param framesPresentedTimeNano The presented frame times.
+ *
+ * @hide
+ */
+ public void init(long refreshPeriodNano, long[] framesPresentedTimeNano) {
+ mRefreshPeriodNano = refreshPeriodNano;
+ mFramesPresentedTimeNano = framesPresentedTimeNano;
+ }
+
+ private WindowAnimationFrameStats(Parcel parcel) {
+ mRefreshPeriodNano = parcel.readLong();
+ mFramesPresentedTimeNano = parcel.createLongArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mRefreshPeriodNano);
+ parcel.writeLongArray(mFramesPresentedTimeNano);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowAnimationFrameStats[");
+ builder.append("frameCount:" + getFrameCount());
+ builder.append(", fromTimeNano:" + getStartTimeNano());
+ builder.append(", toTimeNano:" + getEndTimeNano());
+ builder.append(']');
+ return builder.toString();
+ }
+
+ public static final Creator<WindowAnimationFrameStats> CREATOR =
+ new Creator<WindowAnimationFrameStats>() {
+ @Override
+ public WindowAnimationFrameStats createFromParcel(Parcel parcel) {
+ return new WindowAnimationFrameStats(parcel);
+ }
+
+ @Override
+ public WindowAnimationFrameStats[] newArray(int size) {
+ return new WindowAnimationFrameStats[size];
+ }
+ };
+}
diff --git a/android/view/WindowCallback.java b/android/view/WindowCallback.java
new file mode 100644
index 00000000..1ea8a9f2
--- /dev/null
+++ b/android/view/WindowCallback.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2014 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.Nullable;
+import android.view.ActionMode.Callback;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.List;
+
+/**
+ * An empty implementation of {@link Window.Callback} that always returns null/false.
+ */
+public class WindowCallback implements Window.Callback {
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ @Override
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ @Override
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onWindowAttributesChanged(LayoutParams attrs) {
+
+ }
+
+ @Override
+ public void onContentChanged() {
+
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+
+ }
+
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {
+
+ }
+
+ @Override
+ public boolean onSearchRequested(SearchEvent searchEvent) {
+ return onSearchRequested();
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ return false;
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(Callback callback) {
+ return null;
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(Callback callback, int type) {
+ return null;
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+
+ }
+}
diff --git a/android/view/WindowCallbackWrapper.java b/android/view/WindowCallbackWrapper.java
new file mode 100644
index 00000000..02c8945d
--- /dev/null
+++ b/android/view/WindowCallbackWrapper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 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.view.accessibility.AccessibilityEvent;
+
+import java.util.List;
+
+/**
+ * A simple decorator stub for Window.Callback that passes through any calls
+ * to the wrapped instance as a base implementation. Call super.foo() to call into
+ * the wrapped callback for any subclasses.
+ *
+ * @hide for internal use
+ */
+public class WindowCallbackWrapper implements Window.Callback {
+ private Window.Callback mWrapped;
+
+ public WindowCallbackWrapper(Window.Callback wrapped) {
+ if (wrapped == null) {
+ throw new IllegalArgumentException("Window callback may not be null");
+ }
+ mWrapped = wrapped;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mWrapped.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return mWrapped.dispatchKeyShortcutEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return mWrapped.dispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ return mWrapped.dispatchTrackballEvent(event);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ return mWrapped.dispatchGenericMotionEvent(event);
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return mWrapped.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ public View onCreatePanelView(int featureId) {
+ return mWrapped.onCreatePanelView(featureId);
+ }
+
+ @Override
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return mWrapped.onCreatePanelMenu(featureId, menu);
+ }
+
+ @Override
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return mWrapped.onPreparePanel(featureId, view, menu);
+ }
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return mWrapped.onMenuOpened(featureId, menu);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return mWrapped.onMenuItemSelected(featureId, item);
+ }
+
+ @Override
+ public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+ mWrapped.onWindowAttributesChanged(attrs);
+ }
+
+ @Override
+ public void onContentChanged() {
+ mWrapped.onContentChanged();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ mWrapped.onWindowFocusChanged(hasFocus);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ mWrapped.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mWrapped.onDetachedFromWindow();
+ }
+
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {
+ mWrapped.onPanelClosed(featureId, menu);
+ }
+
+ @Override
+ public boolean onSearchRequested(SearchEvent searchEvent) {
+ return mWrapped.onSearchRequested(searchEvent);
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ return mWrapped.onSearchRequested();
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+ return mWrapped.onWindowStartingActionMode(callback);
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+ return mWrapped.onWindowStartingActionMode(callback, type);
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ mWrapped.onActionModeStarted(mode);
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ mWrapped.onActionModeFinished(mode);
+ }
+
+ @Override
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+ mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId);
+ }
+
+ @Override
+ public void onPointerCaptureChanged(boolean hasCapture) {
+ mWrapped.onPointerCaptureChanged(hasCapture);
+ }
+}
+
diff --git a/android/view/WindowCallbacks.java b/android/view/WindowCallbacks.java
new file mode 100644
index 00000000..b2dc1e91
--- /dev/null
+++ b/android/view/WindowCallbacks.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * These callbacks are used to communicate window configuration changes while the user is performing
+ * window changes.
+ * Note: Note that at the time of onWindowDragResizeStart the content size isn't known. A consumer
+ * should therfore not draw anything before the additional onContentDraw call has arrived.
+ * @hide
+ */
+public interface WindowCallbacks {
+
+ public static final int RESIZE_MODE_INVALID = -1;
+ public static final int RESIZE_MODE_FREEFORM = 0;
+ public static final int RESIZE_MODE_DOCKED_DIVIDER = 1;
+
+ /**
+ * Called by the system when the window got changed by the user, before the layouter got called.
+ * It also gets called when the insets changed, or when the window switched between a fullscreen
+ * layout or a non-fullscreen layout. It can be used to perform a "quick and dirty" resize which
+ * should never take more then 4ms to complete.
+ *
+ * <p>At the time the layouting has not happened yet.
+ *
+ * @param newBounds The new window frame bounds.
+ * @param fullscreen Whether the window is currently drawing in fullscreen.
+ * @param systemInsets The current visible system insets for the window.
+ * @param stableInsets The stable insets for the window.
+ */
+ void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets,
+ Rect stableInsets);
+
+ /**
+ * Called when a drag resize starts.
+ *
+ * @param initialBounds The initial bounds where the window will be.
+ * @param fullscreen Whether the window is currently drawing in fullscreen.
+ * @param systemInsets The current visible system insets for the window.
+ * @param stableInsets The stable insets for the window.
+ */
+ void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
+ Rect stableInsets, int resizeMode);
+
+ /**
+ * Called when a drag resize ends.
+ */
+ void onWindowDragResizeEnd();
+
+ /**
+ * The content will now be drawn to these bounds. Returns true if
+ * a draw should be requested after the next content draw.
+ */
+ boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY);
+
+ /**
+ * Called to request the window to draw one frame.
+ * @param reportNextDraw Whether it should report when the requested draw finishes.
+ */
+ void onRequestDraw(boolean reportNextDraw);
+
+ /**
+ * Called after all the content has drawn and the callback now has the ability to draw something
+ * on top of everything. Call {@link ViewRootImpl#requestInvalidateRootRenderNode} when this
+ * content needs to be redrawn.
+ *
+ * @param canvas The canvas to draw on.
+ */
+ void onPostDraw(DisplayListCanvas canvas);
+}
diff --git a/android/view/WindowContentFrameStats.java b/android/view/WindowContentFrameStats.java
new file mode 100644
index 00000000..c6da2fb2
--- /dev/null
+++ b/android/view/WindowContentFrameStats.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window content frame statistics. For example, a window content
+ * is rendred in frames when a view is scrolled. The frame statistics are a snapshot
+ * for the time interval from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience an application
+ * has to draw a frame at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the application does not render a frame every refresh
+ * period the user will see irregular UI transitions.
+ * </p>
+ * <p>
+ * An application posts a frame for presentation by synchronously rendering its contents
+ * in a buffer which is then posted or posting a buffer to which the application is
+ * asychronously rendering the content via GL. After the frame is posted and rendered
+ * (potentially asynchronosly) it is presented to the user. The time a frame was posted
+ * can be obtained via {@link #getFramePostedTimeNano(int)}, the time a frame content
+ * was rendered and ready for dsiplay (GL case) via {@link #getFrameReadyTimeNano(int)},
+ * and the time a frame was presented on the screen via {@link #getFramePresentedTimeNano(int)}.
+ * </p>
+ */
+public final class WindowContentFrameStats extends FrameStats implements Parcelable {
+ private long[] mFramesPostedTimeNano;
+ private long[] mFramesReadyTimeNano;
+
+ /**
+ * @hide
+ */
+ public WindowContentFrameStats() {
+ /* do nothing */
+ }
+
+ /**
+ * Initializes this isntance.
+ *
+ * @param refreshPeriodNano The display refresh period.
+ * @param framesPostedTimeNano The times in milliseconds for when the frame contents were posted.
+ * @param framesPresentedTimeNano The times in milliseconds for when the frame contents were presented.
+ * @param framesReadyTimeNano The times in milliseconds for when the frame contents were ready to be presented.
+ *
+ * @hide
+ */
+ public void init(long refreshPeriodNano, long[] framesPostedTimeNano,
+ long[] framesPresentedTimeNano, long[] framesReadyTimeNano) {
+ mRefreshPeriodNano = refreshPeriodNano;
+ mFramesPostedTimeNano = framesPostedTimeNano;
+ mFramesPresentedTimeNano = framesPresentedTimeNano;
+ mFramesReadyTimeNano = framesReadyTimeNano;
+ }
+
+ private WindowContentFrameStats(Parcel parcel) {
+ mRefreshPeriodNano = parcel.readLong();
+ mFramesPostedTimeNano = parcel.createLongArray();
+ mFramesPresentedTimeNano = parcel.createLongArray();
+ mFramesReadyTimeNano = parcel.createLongArray();
+ }
+
+ /**
+ * Get the time a frame at a given index was posted by the producer (e.g. the application).
+ * It is either explicitly set or defaulted to the time when the render buffer was posted.
+ * <p>
+ * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+ * asynchronously in GL. To get the time the frame content was completely rendered and
+ * ready to display call {@link #getFrameReadyTimeNano(int)}.
+ * </p>
+ *
+ * @param index The frame index.
+ * @return The posted time in nanoseconds.
+ */
+ public long getFramePostedTimeNano(int index) {
+ if (mFramesPostedTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesPostedTimeNano[index];
+ }
+
+ /**
+ * Get the time a frame at a given index was ready for presentation.
+ * <p>
+ * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+ * asynchronously in GL. In such a case this is the time when the frame contents were
+ * completely rendered.
+ * </p>
+ *
+ * @param index The frame index.
+ * @return The ready time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if the frame is not ready yet.
+ */
+ public long getFrameReadyTimeNano(int index) {
+ if (mFramesReadyTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesReadyTimeNano[index];
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mRefreshPeriodNano);
+ parcel.writeLongArray(mFramesPostedTimeNano);
+ parcel.writeLongArray(mFramesPresentedTimeNano);
+ parcel.writeLongArray(mFramesReadyTimeNano);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowContentFrameStats[");
+ builder.append("frameCount:" + getFrameCount());
+ builder.append(", fromTimeNano:" + getStartTimeNano());
+ builder.append(", toTimeNano:" + getEndTimeNano());
+ builder.append(']');
+ return builder.toString();
+ }
+
+ public static final Parcelable.Creator<WindowContentFrameStats> CREATOR =
+ new Creator<WindowContentFrameStats>() {
+ @Override
+ public WindowContentFrameStats createFromParcel(Parcel parcel) {
+ return new WindowContentFrameStats(parcel);
+ }
+
+ @Override
+ public WindowContentFrameStats[] newArray(int size) {
+ return new WindowContentFrameStats[size];
+ }
+ };
+}
diff --git a/android/view/WindowId.java b/android/view/WindowId.java
new file mode 100644
index 00000000..c4cda2c7
--- /dev/null
+++ b/android/view/WindowId.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2006 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.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.HashMap;
+
+/**
+ * Safe identifier for a window. This currently allows you to retrieve and observe
+ * the input focus state of the window. Most applications will
+ * not use this, instead relying on the simpler (and more efficient) methods available
+ * on {@link View}. This classes is useful when window input interactions need to be
+ * done across processes: the class itself is a Parcelable that can be passed to other
+ * processes for them to interact with your window, and it provides a limited safe API
+ * that doesn't allow the other process to negatively harm your window.
+ */
+public class WindowId implements Parcelable {
+ private final IWindowId mToken;
+
+ /**
+ * Subclass for observing changes to the focus state of an {@link WindowId}.
+ * You should use the same instance of this class for observing multiple
+ * {@link WindowId} objects, since this class is fairly heavy-weight -- the
+ * base class includes all of the mechanisms for connecting to and receiving updates
+ * from the window.
+ */
+ public static abstract class FocusObserver {
+ final IWindowFocusObserver.Stub mIObserver = new IWindowFocusObserver.Stub() {
+
+ @Override
+ public void focusGained(IBinder inputToken) {
+ WindowId token;
+ synchronized (mRegistrations) {
+ token = mRegistrations.get(inputToken);
+ }
+ if (mHandler != null) {
+ mHandler.sendMessage(mHandler.obtainMessage(1, token));
+ } else {
+ onFocusGained(token);
+ }
+ }
+
+ @Override
+ public void focusLost(IBinder inputToken) {
+ WindowId token;
+ synchronized (mRegistrations) {
+ token = mRegistrations.get(inputToken);
+ }
+ if (mHandler != null) {
+ mHandler.sendMessage(mHandler.obtainMessage(2, token));
+ } else {
+ onFocusLost(token);
+ }
+ }
+ };
+
+ final HashMap<IBinder, WindowId> mRegistrations
+ = new HashMap<IBinder, WindowId>();
+
+ class H extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case 1:
+ onFocusGained((WindowId)msg.obj);
+ break;
+ case 2:
+ onFocusLost((WindowId)msg.obj);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ final Handler mHandler;
+
+ /**
+ * Construct a new observer. This observer will be configured so that all
+ * of its callbacks are dispatched on the current calling thread.
+ */
+ public FocusObserver() {
+ mHandler = new H();
+ }
+
+ /**
+ * Called when one of the monitored windows gains input focus.
+ */
+ public abstract void onFocusGained(WindowId token);
+
+ /**
+ * Called when one of the monitored windows loses input focus.
+ */
+ public abstract void onFocusLost(WindowId token);
+ }
+
+ /**
+ * Retrieve the current focus state of the associated window.
+ */
+ public boolean isFocused() {
+ try {
+ return mToken.isFocused();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Start monitoring for changes in the focus state of the window.
+ */
+ public void registerFocusObserver(FocusObserver observer) {
+ synchronized (observer.mRegistrations) {
+ if (observer.mRegistrations.containsKey(mToken.asBinder())) {
+ throw new IllegalStateException(
+ "Focus observer already registered with input token");
+ }
+ observer.mRegistrations.put(mToken.asBinder(), this);
+ try {
+ mToken.registerFocusObserver(observer.mIObserver);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Stop monitoring changes in the focus state of the window.
+ */
+ public void unregisterFocusObserver(FocusObserver observer) {
+ synchronized (observer.mRegistrations) {
+ if (observer.mRegistrations.remove(mToken.asBinder()) == null) {
+ throw new IllegalStateException("Focus observer not registered with input token");
+ }
+ try {
+ mToken.unregisterFocusObserver(observer.mIObserver);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Comparison operator on two IntentSender objects, such that true
+ * is returned then they both represent the same operation from the
+ * same package.
+ */
+ @Override
+ public boolean equals(Object otherObj) {
+ if (otherObj instanceof WindowId) {
+ return mToken.asBinder().equals(((WindowId) otherObj)
+ .mToken.asBinder());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mToken.asBinder().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("IntentSender{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(": ");
+ sb.append(mToken != null ? mToken.asBinder() : null);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mToken.asBinder());
+ }
+
+ public static final Parcelable.Creator<WindowId> CREATOR
+ = new Parcelable.Creator<WindowId>() {
+ public WindowId createFromParcel(Parcel in) {
+ IBinder target = in.readStrongBinder();
+ return target != null ? new WindowId(target) : null;
+ }
+
+ public WindowId[] newArray(int size) {
+ return new WindowId[size];
+ }
+ };
+
+ /** @hide */
+ public IWindowId getTarget() {
+ return mToken;
+ }
+
+ /** @hide */
+ public WindowId(IWindowId target) {
+ mToken = target;
+ }
+
+ /** @hide */
+ public WindowId(IBinder target) {
+ mToken = IWindowId.Stub.asInterface(target);
+ }
+}
diff --git a/android/view/WindowInfo.java b/android/view/WindowInfo.java
new file mode 100644
index 00000000..bb9e391d
--- /dev/null
+++ b/android/view/WindowInfo.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 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.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents information about a window from the
+ * window manager to another part of the system.
+ *
+ * @hide
+ */
+public class WindowInfo implements Parcelable {
+ private static final int MAX_POOL_SIZE = 10;
+
+ private static final Pools.SynchronizedPool<WindowInfo> sPool =
+ new Pools.SynchronizedPool<WindowInfo>(MAX_POOL_SIZE);
+
+ public int type;
+ public int layer;
+ public IBinder token;
+ public IBinder parentToken;
+ public IBinder activityToken;
+ public boolean focused;
+ public final Rect boundsInScreen = new Rect();
+ public List<IBinder> childTokens;
+ public CharSequence title;
+ public int accessibilityIdOfAnchor = View.NO_ID;
+ public boolean inPictureInPicture;
+
+ private WindowInfo() {
+ /* do nothing - hide constructor */
+ }
+
+ public static WindowInfo obtain() {
+ WindowInfo window = sPool.acquire();
+ if (window == null) {
+ window = new WindowInfo();
+ }
+ return window;
+ }
+
+ public static WindowInfo obtain(WindowInfo other) {
+ WindowInfo window = obtain();
+ window.type = other.type;
+ window.layer = other.layer;
+ window.token = other.token;
+ window.parentToken = other.parentToken;
+ window.activityToken = other.activityToken;
+ window.focused = other.focused;
+ window.boundsInScreen.set(other.boundsInScreen);
+ window.title = other.title;
+ window.accessibilityIdOfAnchor = other.accessibilityIdOfAnchor;
+ window.inPictureInPicture = other.inPictureInPicture;
+
+ if (other.childTokens != null && !other.childTokens.isEmpty()) {
+ if (window.childTokens == null) {
+ window.childTokens = new ArrayList<IBinder>(other.childTokens);
+ } else {
+ window.childTokens.addAll(other.childTokens);
+ }
+ }
+
+ return window;
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(type);
+ parcel.writeInt(layer);
+ parcel.writeStrongBinder(token);
+ parcel.writeStrongBinder(parentToken);
+ parcel.writeStrongBinder(activityToken);
+ parcel.writeInt(focused ? 1 : 0);
+ boundsInScreen.writeToParcel(parcel, flags);
+ parcel.writeCharSequence(title);
+ parcel.writeInt(accessibilityIdOfAnchor);
+ parcel.writeInt(inPictureInPicture ? 1 : 0);
+
+ if (childTokens != null && !childTokens.isEmpty()) {
+ parcel.writeInt(1);
+ parcel.writeBinderList(childTokens);
+ } else {
+ parcel.writeInt(0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowInfo[");
+ builder.append("title=").append(title);
+ builder.append(", type=").append(type);
+ builder.append(", layer=").append(layer);
+ builder.append(", token=").append(token);
+ builder.append(", bounds=").append(boundsInScreen);
+ builder.append(", parent=").append(parentToken);
+ builder.append(", focused=").append(focused);
+ builder.append(", children=").append(childTokens);
+ builder.append(", accessibility anchor=").append(accessibilityIdOfAnchor);
+ builder.append(']');
+ return builder.toString();
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ type = parcel.readInt();
+ layer = parcel.readInt();
+ token = parcel.readStrongBinder();
+ parentToken = parcel.readStrongBinder();
+ activityToken = parcel.readStrongBinder();
+ focused = (parcel.readInt() == 1);
+ boundsInScreen.readFromParcel(parcel);
+ title = parcel.readCharSequence();
+ accessibilityIdOfAnchor = parcel.readInt();
+ inPictureInPicture = (parcel.readInt() == 1);
+
+ final boolean hasChildren = (parcel.readInt() == 1);
+ if (hasChildren) {
+ if (childTokens == null) {
+ childTokens = new ArrayList<IBinder>();
+ }
+ parcel.readBinderList(childTokens);
+ }
+ }
+
+ private void clear() {
+ type = 0;
+ layer = 0;
+ token = null;
+ parentToken = null;
+ activityToken = null;
+ focused = false;
+ boundsInScreen.setEmpty();
+ if (childTokens != null) {
+ childTokens.clear();
+ }
+ inPictureInPicture = false;
+ }
+
+ public static final Parcelable.Creator<WindowInfo> CREATOR =
+ new Creator<WindowInfo>() {
+ @Override
+ public WindowInfo createFromParcel(Parcel parcel) {
+ WindowInfo window = obtain();
+ window.initFromParcel(parcel);
+ return window;
+ }
+
+ @Override
+ public WindowInfo[] newArray(int size) {
+ return new WindowInfo[size];
+ }
+ };
+}
diff --git a/android/view/WindowInsets.java b/android/view/WindowInsets.java
new file mode 100644
index 00000000..750931ab
--- /dev/null
+++ b/android/view/WindowInsets.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * Describes a set of insets for window content.
+ *
+ * <p>WindowInsets are immutable and may be expanded to include more inset types in the future.
+ * To adjust insets, use one of the supplied clone methods to obtain a new WindowInsets instance
+ * with the adjusted properties.</p>
+ *
+ * @see View.OnApplyWindowInsetsListener
+ * @see View#onApplyWindowInsets(WindowInsets)
+ */
+public final class WindowInsets {
+
+ private Rect mSystemWindowInsets;
+ private Rect mWindowDecorInsets;
+ private Rect mStableInsets;
+ private Rect mTempRect;
+ private boolean mIsRound;
+
+ /**
+ * In multi-window we force show the navigation bar. Because we don't want that the surface size
+ * changes in this mode, we instead have a flag whether the navigation bar size should always
+ * be consumed, so the app is treated like there is no virtual navigation bar at all.
+ */
+ private boolean mAlwaysConsumeNavBar;
+
+ private boolean mSystemWindowInsetsConsumed = false;
+ private boolean mWindowDecorInsetsConsumed = false;
+ private boolean mStableInsetsConsumed = false;
+
+ private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
+
+ /**
+ * Since new insets may be added in the future that existing apps couldn't
+ * know about, this fully empty constant shouldn't be made available to apps
+ * since it would allow them to inadvertently consume unknown insets by returning it.
+ * @hide
+ */
+ public static final WindowInsets CONSUMED;
+
+ static {
+ CONSUMED = new WindowInsets(null, null, null, false, false);
+ }
+
+ /** @hide */
+ public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
+ boolean isRound, boolean alwaysConsumeNavBar) {
+ mSystemWindowInsetsConsumed = systemWindowInsets == null;
+ mSystemWindowInsets = mSystemWindowInsetsConsumed ? EMPTY_RECT : systemWindowInsets;
+
+ mWindowDecorInsetsConsumed = windowDecorInsets == null;
+ mWindowDecorInsets = mWindowDecorInsetsConsumed ? EMPTY_RECT : windowDecorInsets;
+
+ mStableInsetsConsumed = stableInsets == null;
+ mStableInsets = mStableInsetsConsumed ? EMPTY_RECT : stableInsets;
+
+ mIsRound = isRound;
+ mAlwaysConsumeNavBar = alwaysConsumeNavBar;
+ }
+
+ /**
+ * Construct a new WindowInsets, copying all values from a source WindowInsets.
+ *
+ * @param src Source to copy insets from
+ */
+ public WindowInsets(WindowInsets src) {
+ mSystemWindowInsets = src.mSystemWindowInsets;
+ mWindowDecorInsets = src.mWindowDecorInsets;
+ mStableInsets = src.mStableInsets;
+ mSystemWindowInsetsConsumed = src.mSystemWindowInsetsConsumed;
+ mWindowDecorInsetsConsumed = src.mWindowDecorInsetsConsumed;
+ mStableInsetsConsumed = src.mStableInsetsConsumed;
+ mIsRound = src.mIsRound;
+ mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
+ }
+
+ /** @hide */
+ public WindowInsets(Rect systemWindowInsets) {
+ this(systemWindowInsets, null, null, false, false);
+ }
+
+ /**
+ * Used to provide a safe copy of the system window insets to pass through
+ * to the existing fitSystemWindows method and other similar internals.
+ * @hide
+ */
+ public Rect getSystemWindowInsets() {
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ if (mSystemWindowInsets != null) {
+ mTempRect.set(mSystemWindowInsets);
+ } else {
+ // If there were no system window insets, this is just empty.
+ mTempRect.setEmpty();
+ }
+ return mTempRect;
+ }
+
+ /**
+ * Returns the left system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The left system window inset
+ */
+ public int getSystemWindowInsetLeft() {
+ return mSystemWindowInsets.left;
+ }
+
+ /**
+ * Returns the top system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The top system window inset
+ */
+ public int getSystemWindowInsetTop() {
+ return mSystemWindowInsets.top;
+ }
+
+ /**
+ * Returns the right system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The right system window inset
+ */
+ public int getSystemWindowInsetRight() {
+ return mSystemWindowInsets.right;
+ }
+
+ /**
+ * Returns the bottom system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The bottom system window inset
+ */
+ public int getSystemWindowInsetBottom() {
+ return mSystemWindowInsets.bottom;
+ }
+
+ /**
+ * Returns the left window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The left window decor inset
+ * @hide pending API
+ */
+ public int getWindowDecorInsetLeft() {
+ return mWindowDecorInsets.left;
+ }
+
+ /**
+ * Returns the top window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The top window decor inset
+ * @hide pending API
+ */
+ public int getWindowDecorInsetTop() {
+ return mWindowDecorInsets.top;
+ }
+
+ /**
+ * Returns the right window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The right window decor inset
+ * @hide pending API
+ */
+ public int getWindowDecorInsetRight() {
+ return mWindowDecorInsets.right;
+ }
+
+ /**
+ * Returns the bottom window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The bottom window decor inset
+ * @hide pending API
+ */
+ public int getWindowDecorInsetBottom() {
+ return mWindowDecorInsets.bottom;
+ }
+
+ /**
+ * Returns true if this WindowInsets has nonzero system window insets.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return true if any of the system window inset values are nonzero
+ */
+ public boolean hasSystemWindowInsets() {
+ return mSystemWindowInsets.left != 0 || mSystemWindowInsets.top != 0 ||
+ mSystemWindowInsets.right != 0 || mSystemWindowInsets.bottom != 0;
+ }
+
+ /**
+ * Returns true if this WindowInsets has nonzero window decor insets.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return true if any of the window decor inset values are nonzero
+ * @hide pending API
+ */
+ public boolean hasWindowDecorInsets() {
+ return mWindowDecorInsets.left != 0 || mWindowDecorInsets.top != 0 ||
+ mWindowDecorInsets.right != 0 || mWindowDecorInsets.bottom != 0;
+ }
+
+ /**
+ * Returns true if this WindowInsets has any nonzero insets.
+ *
+ * @return true if any inset values are nonzero
+ */
+ public boolean hasInsets() {
+ return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets();
+ }
+
+ /**
+ * Check if these insets have been fully consumed.
+ *
+ * <p>Insets are considered "consumed" if the applicable <code>consume*</code> methods
+ * have been called such that all insets have been set to zero. This affects propagation of
+ * insets through the view hierarchy; insets that have not been fully consumed will continue
+ * to propagate down to child views.</p>
+ *
+ * <p>The result of this method is equivalent to the return value of
+ * {@link View#fitSystemWindows(android.graphics.Rect)}.</p>
+ *
+ * @return true if the insets have been fully consumed.
+ */
+ public boolean isConsumed() {
+ return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed;
+ }
+
+ /**
+ * Returns true if the associated window has a round shape.
+ *
+ * <p>A round window's left, top, right and bottom edges reach all the way to the
+ * associated edges of the window but the corners may not be visible. Views responding
+ * to round insets should take care to not lay out critical elements within the corners
+ * where they may not be accessible.</p>
+ *
+ * @return True if the window is round
+ */
+ public boolean isRound() {
+ return mIsRound;
+ }
+
+ /**
+ * Returns a copy of this WindowInsets with the system window insets fully consumed.
+ *
+ * @return A modified copy of this WindowInsets
+ */
+ public WindowInsets consumeSystemWindowInsets() {
+ final WindowInsets result = new WindowInsets(this);
+ result.mSystemWindowInsets = EMPTY_RECT;
+ result.mSystemWindowInsetsConsumed = true;
+ return result;
+ }
+
+ /**
+ * Returns a copy of this WindowInsets with selected system window insets fully consumed.
+ *
+ * @param left true to consume the left system window inset
+ * @param top true to consume the top system window inset
+ * @param right true to consume the right system window inset
+ * @param bottom true to consume the bottom system window inset
+ * @return A modified copy of this WindowInsets
+ * @hide pending API
+ */
+ public WindowInsets consumeSystemWindowInsets(boolean left, boolean top,
+ boolean right, boolean bottom) {
+ if (left || top || right || bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mSystemWindowInsets = new Rect(
+ left ? 0 : mSystemWindowInsets.left,
+ top ? 0 : mSystemWindowInsets.top,
+ right ? 0 : mSystemWindowInsets.right,
+ bottom ? 0 : mSystemWindowInsets.bottom);
+ return result;
+ }
+ return this;
+ }
+
+ /**
+ * Returns a copy of this WindowInsets with selected system window insets replaced
+ * with new values.
+ *
+ * @param left New left inset in pixels
+ * @param top New top inset in pixels
+ * @param right New right inset in pixels
+ * @param bottom New bottom inset in pixels
+ * @return A modified copy of this WindowInsets
+ */
+ public WindowInsets replaceSystemWindowInsets(int left, int top,
+ int right, int bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mSystemWindowInsets = new Rect(left, top, right, bottom);
+ return result;
+ }
+
+ /**
+ * Returns a copy of this WindowInsets with selected system window insets replaced
+ * with new values.
+ *
+ * @param systemWindowInsets New system window insets. Each field is the inset in pixels
+ * for that edge
+ * @return A modified copy of this WindowInsets
+ */
+ public WindowInsets replaceSystemWindowInsets(Rect systemWindowInsets) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mSystemWindowInsets = new Rect(systemWindowInsets);
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public WindowInsets consumeWindowDecorInsets() {
+ final WindowInsets result = new WindowInsets(this);
+ result.mWindowDecorInsets.set(0, 0, 0, 0);
+ result.mWindowDecorInsetsConsumed = true;
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public WindowInsets consumeWindowDecorInsets(boolean left, boolean top,
+ boolean right, boolean bottom) {
+ if (left || top || right || bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mWindowDecorInsets = new Rect(left ? 0 : mWindowDecorInsets.left,
+ top ? 0 : mWindowDecorInsets.top,
+ right ? 0 : mWindowDecorInsets.right,
+ bottom ? 0 : mWindowDecorInsets.bottom);
+ return result;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public WindowInsets replaceWindowDecorInsets(int left, int top, int right, int bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mWindowDecorInsets = new Rect(left, top, right, bottom);
+ return result;
+ }
+
+ /**
+ * Returns the top stable inset in pixels.
+ *
+ * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+ * partially or fully obscured by the system UI elements. This value does not change
+ * based on the visibility state of those elements; for example, if the status bar is
+ * normally shown, but temporarily hidden, the stable inset will still provide the inset
+ * associated with the status bar being shown.</p>
+ *
+ * @return The top stable inset
+ */
+ public int getStableInsetTop() {
+ return mStableInsets.top;
+ }
+
+ /**
+ * Returns the left stable inset in pixels.
+ *
+ * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+ * partially or fully obscured by the system UI elements. This value does not change
+ * based on the visibility state of those elements; for example, if the status bar is
+ * normally shown, but temporarily hidden, the stable inset will still provide the inset
+ * associated with the status bar being shown.</p>
+ *
+ * @return The left stable inset
+ */
+ public int getStableInsetLeft() {
+ return mStableInsets.left;
+ }
+
+ /**
+ * Returns the right stable inset in pixels.
+ *
+ * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+ * partially or fully obscured by the system UI elements. This value does not change
+ * based on the visibility state of those elements; for example, if the status bar is
+ * normally shown, but temporarily hidden, the stable inset will still provide the inset
+ * associated with the status bar being shown.</p>
+ *
+ * @return The right stable inset
+ */
+ public int getStableInsetRight() {
+ return mStableInsets.right;
+ }
+
+ /**
+ * Returns the bottom stable inset in pixels.
+ *
+ * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+ * partially or fully obscured by the system UI elements. This value does not change
+ * based on the visibility state of those elements; for example, if the status bar is
+ * normally shown, but temporarily hidden, the stable inset will still provide the inset
+ * associated with the status bar being shown.</p>
+ *
+ * @return The bottom stable inset
+ */
+ public int getStableInsetBottom() {
+ return mStableInsets.bottom;
+ }
+
+ /**
+ * Returns true if this WindowInsets has nonzero stable insets.
+ *
+ * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+ * partially or fully obscured by the system UI elements. This value does not change
+ * based on the visibility state of those elements; for example, if the status bar is
+ * normally shown, but temporarily hidden, the stable inset will still provide the inset
+ * associated with the status bar being shown.</p>
+ *
+ * @return true if any of the stable inset values are nonzero
+ */
+ public boolean hasStableInsets() {
+ return mStableInsets.top != 0 || mStableInsets.left != 0 || mStableInsets.right != 0
+ || mStableInsets.bottom != 0;
+ }
+
+ /**
+ * Returns a copy of this WindowInsets with the stable insets fully consumed.
+ *
+ * @return A modified copy of this WindowInsets
+ */
+ public WindowInsets consumeStableInsets() {
+ final WindowInsets result = new WindowInsets(this);
+ result.mStableInsets = EMPTY_RECT;
+ result.mStableInsetsConsumed = true;
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean shouldAlwaysConsumeNavBar() {
+ return mAlwaysConsumeNavBar;
+ }
+
+ @Override
+ public String toString() {
+ return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
+ + " windowDecorInsets=" + mWindowDecorInsets
+ + " stableInsets=" + mStableInsets +
+ (isRound() ? " round}" : "}");
+ }
+}
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
new file mode 100644
index 00000000..e56a82ff
--- /dev/null
+++ b/android/view/WindowManager.java
@@ -0,0 +1,2638 @@
+/*
+ * Copyright (C) 2006 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.Manifest.permission;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.app.KeyguardManager;
+import android.app.Presentation;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The interface that apps use to talk to the window manager.
+ * </p><p>
+ * Each window manager instance is bound to a particular {@link Display}.
+ * To obtain a {@link WindowManager} for a different display, use
+ * {@link Context#createDisplayContext} to obtain a {@link Context} for that
+ * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
+ * to get the WindowManager.
+ * </p><p>
+ * The simplest way to show a window on another display is to create a
+ * {@link Presentation}. The presentation will automatically obtain a
+ * {@link WindowManager} and {@link Context} for that display.
+ * </p>
+ */
+@SystemService(Context.WINDOW_SERVICE)
+public interface WindowManager extends ViewManager {
+
+ /** @hide */
+ int DOCKED_INVALID = -1;
+ /** @hide */
+ int DOCKED_LEFT = 1;
+ /** @hide */
+ int DOCKED_TOP = 2;
+ /** @hide */
+ int DOCKED_RIGHT = 3;
+ /** @hide */
+ int DOCKED_BOTTOM = 4;
+
+ /** @hide */
+ final static String INPUT_CONSUMER_PIP = "pip_input_consumer";
+ /** @hide */
+ final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer";
+ /** @hide */
+ final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer";
+
+ /**
+ * Exception that is thrown when trying to add view whose
+ * {@link LayoutParams} {@link LayoutParams#token}
+ * is invalid.
+ */
+ public static class BadTokenException extends RuntimeException {
+ public BadTokenException() {
+ }
+
+ public BadTokenException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Exception that is thrown when calling {@link #addView} to a secondary display that cannot
+ * be found. See {@link android.app.Presentation} for more information on secondary displays.
+ */
+ public static class InvalidDisplayException extends RuntimeException {
+ public InvalidDisplayException() {
+ }
+
+ public InvalidDisplayException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns the {@link Display} upon which this {@link WindowManager} instance
+ * will create new windows.
+ * <p>
+ * Despite the name of this method, the display that is returned is not
+ * necessarily the primary display of the system (see {@link Display#DEFAULT_DISPLAY}).
+ * The returned display could instead be a secondary display that this
+ * window manager instance is managing. Think of it as the display that
+ * this {@link WindowManager} instance uses by default.
+ * </p><p>
+ * To create windows on a different display, you need to obtain a
+ * {@link WindowManager} for that {@link Display}. (See the {@link WindowManager}
+ * class documentation for more information.)
+ * </p>
+ *
+ * @return The display that this window manager is managing.
+ */
+ public Display getDefaultDisplay();
+
+ /**
+ * Special variation of {@link #removeView} that immediately invokes
+ * the given view hierarchy's {@link View#onDetachedFromWindow()
+ * View.onDetachedFromWindow()} methods before returning. This is not
+ * for normal applications; using it correctly requires great care.
+ *
+ * @param view The view to be removed.
+ */
+ public void removeViewImmediate(View view);
+
+ /**
+ * Used to asynchronously request Keyboard Shortcuts from the focused window.
+ *
+ * @hide
+ */
+ public interface KeyboardShortcutsReceiver {
+ /**
+ * Callback used when the focused window keyboard shortcuts are ready to be displayed.
+ *
+ * @param result The keyboard shortcuts to be displayed.
+ */
+ void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
+ }
+
+ /**
+ * Message for taking fullscreen screenshot
+ * @hide
+ */
+ final int TAKE_SCREENSHOT_FULLSCREEN = 1;
+
+ /**
+ * Message for taking screenshot of selected region.
+ * @hide
+ */
+ final int TAKE_SCREENSHOT_SELECTED_REGION = 2;
+
+ /**
+ * @hide
+ */
+ public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
+
+ /**
+ * Request for keyboard shortcuts to be retrieved asynchronously.
+ *
+ * @param receiver The callback to be triggered when the result is ready.
+ *
+ * @hide
+ */
+ public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
+
+ /**
+ * Return the touch region for the current IME window, or an empty region if there is none.
+ *
+ * @return The region of the IME that is accepting touch inputs, or null if there is no IME, no
+ * region or there was an error.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ public Region getCurrentImeTouchRegion();
+
+ public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
+ /**
+ * X position for this window. With the default gravity it is ignored.
+ * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
+ * {@link Gravity#END} it provides an offset from the given edge.
+ */
+ @ViewDebug.ExportedProperty
+ public int x;
+
+ /**
+ * Y position for this window. With the default gravity it is ignored.
+ * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides
+ * an offset from the given edge.
+ */
+ @ViewDebug.ExportedProperty
+ public int y;
+
+ /**
+ * Indicates how much of the extra space will be allocated horizontally
+ * to the view associated with these LayoutParams. Specify 0 if the view
+ * should not be stretched. Otherwise the extra pixels will be pro-rated
+ * among all views whose weight is greater than 0.
+ */
+ @ViewDebug.ExportedProperty
+ public float horizontalWeight;
+
+ /**
+ * Indicates how much of the extra space will be allocated vertically
+ * to the view associated with these LayoutParams. Specify 0 if the view
+ * should not be stretched. Otherwise the extra pixels will be pro-rated
+ * among all views whose weight is greater than 0.
+ */
+ @ViewDebug.ExportedProperty
+ public float verticalWeight;
+
+ /**
+ * The general type of window. There are three main classes of
+ * window types:
+ * <ul>
+ * <li> <strong>Application windows</strong> (ranging from
+ * {@link #FIRST_APPLICATION_WINDOW} to
+ * {@link #LAST_APPLICATION_WINDOW}) are normal top-level application
+ * windows. For these types of windows, the {@link #token} must be
+ * set to the token of the activity they are a part of (this will
+ * normally be done for you if {@link #token} is null).
+ * <li> <strong>Sub-windows</strong> (ranging from
+ * {@link #FIRST_SUB_WINDOW} to
+ * {@link #LAST_SUB_WINDOW}) are associated with another top-level
+ * window. For these types of windows, the {@link #token} must be
+ * the token of the window it is attached to.
+ * <li> <strong>System windows</strong> (ranging from
+ * {@link #FIRST_SYSTEM_WINDOW} to
+ * {@link #LAST_SYSTEM_WINDOW}) are special types of windows for
+ * use by the system for specific purposes. They should not normally
+ * be used by applications, and a special permission is required
+ * to use them.
+ * </ul>
+ *
+ * @see #TYPE_BASE_APPLICATION
+ * @see #TYPE_APPLICATION
+ * @see #TYPE_APPLICATION_STARTING
+ * @see #TYPE_DRAWN_APPLICATION
+ * @see #TYPE_APPLICATION_PANEL
+ * @see #TYPE_APPLICATION_MEDIA
+ * @see #TYPE_APPLICATION_SUB_PANEL
+ * @see #TYPE_APPLICATION_ABOVE_SUB_PANEL
+ * @see #TYPE_APPLICATION_ATTACHED_DIALOG
+ * @see #TYPE_STATUS_BAR
+ * @see #TYPE_SEARCH_BAR
+ * @see #TYPE_PHONE
+ * @see #TYPE_SYSTEM_ALERT
+ * @see #TYPE_TOAST
+ * @see #TYPE_SYSTEM_OVERLAY
+ * @see #TYPE_PRIORITY_PHONE
+ * @see #TYPE_STATUS_BAR_PANEL
+ * @see #TYPE_SYSTEM_DIALOG
+ * @see #TYPE_KEYGUARD_DIALOG
+ * @see #TYPE_SYSTEM_ERROR
+ * @see #TYPE_INPUT_METHOD
+ * @see #TYPE_INPUT_METHOD_DIALOG
+ */
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = TYPE_BASE_APPLICATION,
+ to = "TYPE_BASE_APPLICATION"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION,
+ to = "TYPE_APPLICATION"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_STARTING,
+ to = "TYPE_APPLICATION_STARTING"),
+ @ViewDebug.IntToString(from = TYPE_DRAWN_APPLICATION,
+ to = "TYPE_DRAWN_APPLICATION"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL,
+ to = "TYPE_APPLICATION_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA,
+ to = "TYPE_APPLICATION_MEDIA"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL,
+ to = "TYPE_APPLICATION_SUB_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL,
+ to = "TYPE_APPLICATION_ABOVE_SUB_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG,
+ to = "TYPE_APPLICATION_ATTACHED_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY,
+ to = "TYPE_APPLICATION_MEDIA_OVERLAY"),
+ @ViewDebug.IntToString(from = TYPE_STATUS_BAR,
+ to = "TYPE_STATUS_BAR"),
+ @ViewDebug.IntToString(from = TYPE_SEARCH_BAR,
+ to = "TYPE_SEARCH_BAR"),
+ @ViewDebug.IntToString(from = TYPE_PHONE,
+ to = "TYPE_PHONE"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT,
+ to = "TYPE_SYSTEM_ALERT"),
+ @ViewDebug.IntToString(from = TYPE_TOAST,
+ to = "TYPE_TOAST"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY,
+ to = "TYPE_SYSTEM_OVERLAY"),
+ @ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE,
+ to = "TYPE_PRIORITY_PHONE"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG,
+ to = "TYPE_SYSTEM_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG,
+ to = "TYPE_KEYGUARD_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR,
+ to = "TYPE_SYSTEM_ERROR"),
+ @ViewDebug.IntToString(from = TYPE_INPUT_METHOD,
+ to = "TYPE_INPUT_METHOD"),
+ @ViewDebug.IntToString(from = TYPE_INPUT_METHOD_DIALOG,
+ to = "TYPE_INPUT_METHOD_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_WALLPAPER,
+ to = "TYPE_WALLPAPER"),
+ @ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL,
+ to = "TYPE_STATUS_BAR_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_SECURE_SYSTEM_OVERLAY,
+ to = "TYPE_SECURE_SYSTEM_OVERLAY"),
+ @ViewDebug.IntToString(from = TYPE_DRAG,
+ to = "TYPE_DRAG"),
+ @ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL,
+ to = "TYPE_STATUS_BAR_SUB_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_POINTER,
+ to = "TYPE_POINTER"),
+ @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR,
+ to = "TYPE_NAVIGATION_BAR"),
+ @ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY,
+ to = "TYPE_VOLUME_OVERLAY"),
+ @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS,
+ to = "TYPE_BOOT_PROGRESS"),
+ @ViewDebug.IntToString(from = TYPE_INPUT_CONSUMER,
+ to = "TYPE_INPUT_CONSUMER"),
+ @ViewDebug.IntToString(from = TYPE_DREAM,
+ to = "TYPE_DREAM"),
+ @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL,
+ to = "TYPE_NAVIGATION_BAR_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY,
+ to = "TYPE_DISPLAY_OVERLAY"),
+ @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY,
+ to = "TYPE_MAGNIFICATION_OVERLAY"),
+ @ViewDebug.IntToString(from = TYPE_PRESENTATION,
+ to = "TYPE_PRESENTATION"),
+ @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION,
+ to = "TYPE_PRIVATE_PRESENTATION"),
+ @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION,
+ to = "TYPE_VOICE_INTERACTION"),
+ @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING,
+ to = "TYPE_VOICE_INTERACTION_STARTING"),
+ @ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER,
+ to = "TYPE_DOCK_DIVIDER"),
+ @ViewDebug.IntToString(from = TYPE_QS_DIALOG,
+ to = "TYPE_QS_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_SCREENSHOT,
+ to = "TYPE_SCREENSHOT"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_OVERLAY,
+ to = "TYPE_APPLICATION_OVERLAY")
+ })
+ public int type;
+
+ /**
+ * Start of window types that represent normal application windows.
+ */
+ public static final int FIRST_APPLICATION_WINDOW = 1;
+
+ /**
+ * Window type: an application window that serves as the "base" window
+ * of the overall application; all other application windows will
+ * appear on top of it.
+ * In multiuser systems shows only on the owning user's window.
+ */
+ public static final int TYPE_BASE_APPLICATION = 1;
+
+ /**
+ * Window type: a normal application window. The {@link #token} must be
+ * an Activity token identifying who the window belongs to.
+ * In multiuser systems shows only on the owning user's window.
+ */
+ public static final int TYPE_APPLICATION = 2;
+
+ /**
+ * Window type: special application window that is displayed while the
+ * application is starting. Not for use by applications themselves;
+ * this is used by the system to display something until the
+ * application can show its own windows.
+ * In multiuser systems shows on all users' windows.
+ */
+ public static final int TYPE_APPLICATION_STARTING = 3;
+
+ /**
+ * Window type: a variation on TYPE_APPLICATION that ensures the window
+ * manager will wait for this window to be drawn before the app is shown.
+ * In multiuser systems shows only on the owning user's window.
+ */
+ public static final int TYPE_DRAWN_APPLICATION = 4;
+
+ /**
+ * End of types of application windows.
+ */
+ public static final int LAST_APPLICATION_WINDOW = 99;
+
+ /**
+ * Start of types of sub-windows. The {@link #token} of these windows
+ * must be set to the window they are attached to. These types of
+ * windows are kept next to their attached window in Z-order, and their
+ * coordinate space is relative to their attached window.
+ */
+ public static final int FIRST_SUB_WINDOW = 1000;
+
+ /**
+ * Window type: a panel on top of an application window. These windows
+ * appear on top of their attached window.
+ */
+ public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
+
+ /**
+ * Window type: window for showing media (such as video). These windows
+ * are displayed behind their attached window.
+ */
+ public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
+
+ /**
+ * Window type: a sub-panel on top of an application window. These
+ * windows are displayed on top their attached window and any
+ * {@link #TYPE_APPLICATION_PANEL} panels.
+ */
+ public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
+
+ /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
+ * of the window happens as that of a top-level window, <em>not</em>
+ * as a child of its container.
+ */
+ public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
+
+ /**
+ * Window type: window for showing overlays on top of media windows.
+ * These windows are displayed between TYPE_APPLICATION_MEDIA and the
+ * application window. They should be translucent to be useful. This
+ * is a big ugly hack so:
+ * @hide
+ */
+ public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
+
+ /**
+ * Window type: a above sub-panel on top of an application window and it's
+ * sub-panel windows. These windows are displayed on top of their attached window
+ * and any {@link #TYPE_APPLICATION_SUB_PANEL} panels.
+ * @hide
+ */
+ public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
+
+ /**
+ * End of types of sub-windows.
+ */
+ public static final int LAST_SUB_WINDOW = 1999;
+
+ /**
+ * Start of system-specific window types. These are not normally
+ * created by applications.
+ */
+ public static final int FIRST_SYSTEM_WINDOW = 2000;
+
+ /**
+ * Window type: the status bar. There can be only one status bar
+ * window; it is placed at the top of the screen, and all other
+ * windows are shifted down so they are below it.
+ * In multiuser systems shows on all users' windows.
+ */
+ public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
+
+ /**
+ * Window type: the search bar. There can be only one search bar
+ * window; it is placed at the top of the screen.
+ * In multiuser systems shows on all users' windows.
+ */
+ public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
+
+ /**
+ * Window type: phone. These are non-application windows providing
+ * user interaction with the phone (in particular incoming calls).
+ * These windows are normally placed above all applications, but behind
+ * the status bar.
+ * In multiuser systems shows on all users' windows.
+ * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+ */
+ @Deprecated
+ public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
+
+ /**
+ * Window type: system window, such as low power alert. These windows
+ * are always on top of application windows.
+ * In multiuser systems shows only on the owning user's window.
+ * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+ */
+ @Deprecated
+ public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
+
+ /**
+ * Window type: keyguard window.
+ * In multiuser systems shows on all users' windows.
+ * @removed
+ */
+ public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
+
+ /**
+ * Window type: transient notifications.
+ * In multiuser systems shows only on the owning user's window.
+ * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+ */
+ @Deprecated
+ public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
+
+ /**
+ * Window type: system overlay windows, which need to be displayed
+ * on top of everything else. These windows must not take input
+ * focus, or they will interfere with the keyguard.
+ * In multiuser systems shows only on the owning user's window.
+ * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+ */
+ @Deprecated
+ public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
+
+ /**
+ * Window type: priority phone UI, which needs to be displayed even if
+ * the keyguard is active. These windows must not take input
+ * focus, or they will interfere with the keyguard.
+ * In multiuser systems shows on all users' windows.
+ * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+ */
+ @Deprecated
+ public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
+
+ /**
+ * Window type: panel that slides out from the status bar
+ * In multiuser systems shows on all users' windows.
+ */
+ public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
+
+ /**
+ * Window type: dialogs that the keyguard shows
+ * In multiuser systems shows on all users' windows.
+ */
+ public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
+
+ /**
+ * Window type: internal system error windows, appear on top of
+ * everything they can.
+ * In multiuser systems shows only on the owning user's window.
+ * @deprecated for non-system apps. Use {@link #TYPE_APPLICATION_OVERLAY} instead.
+ */
+ @Deprecated
+ public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
+
+ /**
+ * Window type: internal input methods windows, which appear above
+ * the normal UI. Application windows may be resized or panned to keep
+ * the input focus visible while this window is displayed.
+ * In multiuser systems shows only on the owning user's window.
+ */
+ public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
+
+ /**
+ * Window type: internal input methods dialog windows, which appear above
+ * the current input method window.
+ * In multiuser systems shows only on the owning user's window.
+ */
+ public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
+
+ /**
+ * Window type: wallpaper window, placed behind any window that wants
+ * to sit on top of the wallpaper.
+ * In multiuser systems shows only on the owning user's window.
+ */
+ public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
+
+ /**
+ * Window type: panel that slides out from over the status bar
+ * In multiuser systems shows on all users' windows.
+ */
+ public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
+
+ /**
+ * Window type: secure system overlay windows, which need to be displayed
+ * on top of everything else. These windows must not take input
+ * focus, or they will interfere with the keyguard.
+ *
+ * This is exactly like {@link #TYPE_SYSTEM_OVERLAY} except that only the
+ * system itself is allowed to create these overlays. Applications cannot
+ * obtain permission to create secure system overlays.
+ *
+ * In multiuser systems shows only on the owning user's window.
+ * @hide
+ */
+ public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
+
+ /**
+ * Window type: the drag-and-drop pseudowindow. There is only one
+ * drag layer (at most), and it is placed on top of all other windows.
+ * In multiuser systems shows only on the owning user's window.
+ * @hide
+ */
+ public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;
+
+ /**
+ * Window type: panel that slides out from under the status bar
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
+
+ /**
+ * Window type: (mouse) pointer
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
+
+ /**
+ * Window type: Navigation bar (when distinct from status bar)
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
+
+ /**
+ * Window type: The volume level overlay/dialog shown when the user
+ * changes the system volume.
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
+
+ /**
+ * Window type: The boot progress dialog, goes on top of everything
+ * in the world.
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
+
+ /**
+ * Window type to consume input events when the systemUI bars are hidden.
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;
+
+ /**
+ * Window type: Dreams (screen saver) window, just above keyguard.
+ * In multiuser systems shows only on the owning user's window.
+ * @hide
+ */
+ public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
+
+ /**
+ * Window type: Navigation bar panel (when navigation bar is distinct from status bar)
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
+
+ /**
+ * Window type: Display overlay window. Used to simulate secondary display devices.
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
+
+ /**
+ * Window type: Magnification overlay window. Used to highlight the magnified
+ * portion of a display when accessibility magnification is enabled.
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
+
+ /**
+ * Window type: Window for Presentation on top of private
+ * virtual display.
+ */
+ public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
+
+ /**
+ * Window type: Windows in the voice interaction layer.
+ * @hide
+ */
+ public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
+
+ /**
+ * Window type: Windows that are overlaid <em>only</em> by a connected {@link
+ * android.accessibilityservice.AccessibilityService} for interception of
+ * user interactions without changing the windows an accessibility service
+ * can introspect. In particular, an accessibility service can introspect
+ * only windows that a sighted user can interact with which is they can touch
+ * these windows or can type into these windows. For example, if there
+ * is a full screen accessibility overlay that is touchable, the windows
+ * below it will be introspectable by an accessibility service even though
+ * they are covered by a touchable window.
+ */
+ public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
+
+ /**
+ * Window type: Starting window for voice interaction layer.
+ * @hide
+ */
+ public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
+
+ /**
+ * Window for displaying a handle used for resizing docked stacks. This window is owned
+ * by the system process.
+ * @hide
+ */
+ public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
+
+ /**
+ * Window type: like {@link #TYPE_APPLICATION_ATTACHED_DIALOG}, but used
+ * by Quick Settings Tiles.
+ * @hide
+ */
+ public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
+
+ /**
+ * Window type: shares similar characteristics with {@link #TYPE_DREAM}. The layer is
+ * reserved for screenshot region selection. These windows must not take input focus.
+ * @hide
+ */
+ public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;
+
+ /**
+ * Window type: Window for Presentation on an external display.
+ * @see android.app.Presentation
+ * @hide
+ */
+ public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
+
+ /**
+ * Window type: Application overlay windows are displayed above all activity windows
+ * (types between {@link #FIRST_APPLICATION_WINDOW} and {@link #LAST_APPLICATION_WINDOW})
+ * but below critical system windows like the status bar or IME.
+ * <p>
+ * The system may change the position, size, or visibility of these windows at anytime
+ * to reduce visual clutter to the user and also manage resources.
+ * <p>
+ * Requires {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission.
+ * <p>
+ * The system will adjust the importance of processes with this window type to reduce the
+ * chance of the low-memory-killer killing them.
+ * <p>
+ * In multi-user systems shows only on the owning user's screen.
+ */
+ public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
+
+ /**
+ * End of types of system windows.
+ */
+ public static final int LAST_SYSTEM_WINDOW = 2999;
+
+ /**
+ * @hide
+ * Used internally when there is no suitable type available.
+ */
+ public static final int INVALID_WINDOW_TYPE = -1;
+
+ /**
+ * Return true if the window type is an alert window.
+ *
+ * @param type The window type.
+ * @return If the window type is an alert window.
+ * @hide
+ */
+ public static boolean isSystemAlertWindowType(int type) {
+ switch (type) {
+ case TYPE_PHONE:
+ case TYPE_PRIORITY_PHONE:
+ case TYPE_SYSTEM_ALERT:
+ case TYPE_SYSTEM_ERROR:
+ case TYPE_SYSTEM_OVERLAY:
+ case TYPE_APPLICATION_OVERLAY:
+ return true;
+ }
+ return false;
+ }
+
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int MEMORY_TYPE_NORMAL = 0;
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int MEMORY_TYPE_HARDWARE = 1;
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int MEMORY_TYPE_GPU = 2;
+ /** @deprecated this is ignored, this value is set automatically when needed. */
+ @Deprecated
+ public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;
+
+ /**
+ * @deprecated this is ignored
+ */
+ @Deprecated
+ public int memoryType;
+
+ /** Window flag: as long as this window is visible to the user, allow
+ * the lock screen to activate while the screen is on.
+ * This can be used independently, or in combination with
+ * {@link #FLAG_KEEP_SCREEN_ON} and/or {@link #FLAG_SHOW_WHEN_LOCKED} */
+ public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
+
+ /** Window flag: everything behind this window will be dimmed.
+ * Use {@link #dimAmount} to control the amount of dim. */
+ public static final int FLAG_DIM_BEHIND = 0x00000002;
+
+ /** Window flag: blur everything behind this window.
+ * @deprecated Blurring is no longer supported. */
+ @Deprecated
+ public static final int FLAG_BLUR_BEHIND = 0x00000004;
+
+ /** Window flag: this window won't ever get key input focus, so the
+ * user can not send key or other button events to it. Those will
+ * instead go to whatever focusable window is behind it. This flag
+ * will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that
+ * is explicitly set.
+ *
+ * <p>Setting this flag also implies that the window will not need to
+ * interact with
+ * a soft input method, so it will be Z-ordered and positioned
+ * independently of any active input method (typically this means it
+ * gets Z-ordered on top of the input method, so it can use the full
+ * screen for its content and cover the input method if needed. You
+ * can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
+ public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
+
+ /** Window flag: this window can never receive touch events. */
+ public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
+
+ /** Window flag: even when this window is focusable (its
+ * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
+ * outside of the window to be sent to the windows behind it. Otherwise
+ * it will consume all pointer events itself, regardless of whether they
+ * are inside of the window. */
+ public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
+
+ /** Window flag: when set, if the device is asleep when the touch
+ * screen is pressed, you will receive this first touch event. Usually
+ * the first touch event is consumed by the system since the user can
+ * not see what they are pressing on.
+ *
+ * @deprecated This flag has no effect.
+ */
+ @Deprecated
+ public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
+
+ /** Window flag: as long as this window is visible to the user, keep
+ * the device's screen turned on and bright. */
+ public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
+
+ /** Window flag: place the window within the entire screen, ignoring
+ * decorations around the border (such as the status bar). The
+ * window must correctly position its contents to take the screen
+ * decoration into account. This flag is normally set for you
+ * by Window as described in {@link Window#setFlags}. */
+ public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
+
+ /** Window flag: allow window to extend outside of the screen. */
+ public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
+
+ /**
+ * Window flag: hide all screen decorations (such as the status bar) while
+ * this window is displayed. This allows the window to use the entire
+ * display space for itself -- the status bar will be hidden when
+ * an app window with this flag set is on the top layer. A fullscreen window
+ * will ignore a value of {@link #SOFT_INPUT_ADJUST_RESIZE} for the window's
+ * {@link #softInputMode} field; the window will stay fullscreen
+ * and will not resize.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowFullscreen} attribute; this attribute
+ * is automatically set for you in the standard fullscreen themes
+ * such as {@link android.R.style#Theme_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Black_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Light_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_NoActionBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_Fullscreen},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Fullscreen}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p>
+ */
+ public static final int FLAG_FULLSCREEN = 0x00000400;
+
+ /** Window flag: override {@link #FLAG_FULLSCREEN} and force the
+ * screen decorations (such as the status bar) to be shown. */
+ public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
+
+ /** Window flag: turn on dithering when compositing this window to
+ * the screen.
+ * @deprecated This flag is no longer used. */
+ @Deprecated
+ public static final int FLAG_DITHER = 0x00001000;
+
+ /** Window flag: treat the content of the window as secure, preventing
+ * it from appearing in screenshots or from being viewed on non-secure
+ * displays.
+ *
+ * <p>See {@link android.view.Display#FLAG_SECURE} for more details about
+ * secure surfaces and secure displays.
+ */
+ public static final int FLAG_SECURE = 0x00002000;
+
+ /** Window flag: a special mode where the layout parameters are used
+ * to perform scaling of the surface when it is composited to the
+ * screen. */
+ public static final int FLAG_SCALED = 0x00004000;
+
+ /** Window flag: intended for windows that will often be used when the user is
+ * holding the screen against their face, it will aggressively filter the event
+ * stream to prevent unintended presses in this situation that may not be
+ * desired for a particular window, when such an event stream is detected, the
+ * application will receive a CANCEL motion event to indicate this so applications
+ * can handle this accordingly by taking no action on the event
+ * until the finger is released. */
+ public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
+
+ /** Window flag: a special option only for use in combination with
+ * {@link #FLAG_LAYOUT_IN_SCREEN}. When requesting layout in the
+ * screen your window may appear on top of or behind screen decorations
+ * such as the status bar. By also including this flag, the window
+ * manager will report the inset rectangle needed to ensure your
+ * content is not covered by screen decorations. This flag is normally
+ * set for you by Window as described in {@link Window#setFlags}.*/
+ public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
+
+ /** Window flag: invert the state of {@link #FLAG_NOT_FOCUSABLE} with
+ * respect to how this window interacts with the current method. That
+ * is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the
+ * window will behave as if it needs to interact with the input method
+ * and thus be placed behind/away from it; if FLAG_NOT_FOCUSABLE is
+ * not set and this flag is set, then the window will behave as if it
+ * doesn't need to interact with the input method and can be placed
+ * to use more space and cover the input method.
+ */
+ public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
+
+ /** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you
+ * can set this flag to receive a single special MotionEvent with
+ * the action
+ * {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE} for
+ * touches that occur outside of your window. Note that you will not
+ * receive the full down/move/up gesture, only the location of the
+ * first down as an ACTION_OUTSIDE.
+ */
+ public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
+
+ /** Window flag: special flag to let windows be shown when the screen
+ * is locked. This will let application windows take precedence over
+ * key guard or any other lock screens. Can be used with
+ * {@link #FLAG_KEEP_SCREEN_ON} to turn screen on and display windows
+ * directly before showing the key guard window. Can be used with
+ * {@link #FLAG_DISMISS_KEYGUARD} to automatically fully dismisss
+ * non-secure keyguards. This flag only applies to the top-most
+ * full-screen window.
+ * @deprecated Use {@link android.R.attr#showWhenLocked} or
+ * {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an
+ * unintentional double life-cycle event.
+ */
+ @Deprecated
+ public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
+
+ /** Window flag: ask that the system wallpaper be shown behind
+ * your window. The window surface must be translucent to be able
+ * to actually see the wallpaper behind it; this flag just ensures
+ * that the wallpaper surface will be there if this window actually
+ * has translucent regions.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowShowWallpaper} attribute; this attribute
+ * is automatically set for you in the standard wallpaper themes
+ * such as {@link android.R.style#Theme_Wallpaper},
+ * {@link android.R.style#Theme_Wallpaper_NoTitleBar},
+ * {@link android.R.style#Theme_Wallpaper_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_Wallpaper},
+ * {@link android.R.style#Theme_Holo_Wallpaper_NoTitleBar},
+ * {@link android.R.style#Theme_DeviceDefault_Wallpaper}, and
+ * {@link android.R.style#Theme_DeviceDefault_Wallpaper_NoTitleBar}.</p>
+ */
+ public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
+
+ /** Window flag: when set as a window is being added or made
+ * visible, once the window has been shown then the system will
+ * poke the power manager's user activity (as if the user had woken
+ * up the device) to turn the screen on.
+ * @deprecated Use {@link android.R.attr#turnScreenOn} or
+ * {@link android.app.Activity#setTurnScreenOn(boolean)} instead to prevent an
+ * unintentional double life-cycle event.
+ */
+ @Deprecated
+ public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
+
+ /**
+ * Window flag: when set the window will cause the keyguard to be
+ * dismissed, only if it is not a secure lock keyguard. Because such a
+ * keyguard is not needed for security, it will never re-appear if the
+ * user navigates to another window (in contrast to
+ * {@link #FLAG_SHOW_WHEN_LOCKED}, which will only temporarily hide both
+ * secure and non-secure keyguards but ensure they reappear when the
+ * user moves to another UI that doesn't hide them). If the keyguard is
+ * currently active and is secure (requires an unlock credential) than
+ * the user will still need to confirm it before seeing this window,
+ * unless {@link #FLAG_SHOW_WHEN_LOCKED} has also been set.
+ *
+ * @deprecated Use {@link #FLAG_SHOW_WHEN_LOCKED} or
+ * {@link KeyguardManager#requestDismissKeyguard} instead.
+ * Since keyguard was dismissed all the time as long as an
+ * activity with this flag on its window was focused,
+ * keyguard couldn't guard against unintentional touches on
+ * the screen, which isn't desired.
+ */
+ @Deprecated
+ public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
+
+ /** Window flag: when set the window will accept for touch events
+ * outside of its bounds to be sent to other windows that also
+ * support split touch. When this flag is not set, the first pointer
+ * that goes down determines the window to which all subsequent touches
+ * go until all pointers go up. When this flag is set, each pointer
+ * (not necessarily the first) that goes down determines the window
+ * to which all subsequent touches of that pointer will go until that
+ * pointer goes up thereby enabling touches with multiple pointers
+ * to be split across multiple windows.
+ */
+ public static final int FLAG_SPLIT_TOUCH = 0x00800000;
+
+ /**
+ * <p>Indicates whether this window should be hardware accelerated.
+ * Requesting hardware acceleration does not guarantee it will happen.</p>
+ *
+ * <p>This flag can be controlled programmatically <em>only</em> to enable
+ * hardware acceleration. To enable hardware acceleration for a given
+ * window programmatically, do the following:</p>
+ *
+ * <pre>
+ * Window w = activity.getWindow(); // in Activity's onCreate() for instance
+ * w.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ * WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ * </pre>
+ *
+ * <p>It is important to remember that this flag <strong>must</strong>
+ * be set before setting the content view of your activity or dialog.</p>
+ *
+ * <p>This flag cannot be used to disable hardware acceleration after it
+ * was enabled in your manifest using
+ * {@link android.R.attr#hardwareAccelerated}. If you need to selectively
+ * and programmatically disable hardware acceleration (for automated testing
+ * for instance), make sure it is turned off in your manifest and enable it
+ * on your activity or dialog when you need it instead, using the method
+ * described above.</p>
+ *
+ * <p>This flag is automatically set by the system if the
+ * {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated}
+ * XML attribute is set to true on an activity or on the application.</p>
+ */
+ public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
+
+ /**
+ * Window flag: allow window contents to extend in to the screen's
+ * overscan area, if there is one. The window should still correctly
+ * position its contents to take the overscan area into account.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowOverscan} attribute; this attribute
+ * is automatically set for you in the standard overscan themes
+ * such as
+ * {@link android.R.style#Theme_Holo_NoActionBar_Overscan},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_Overscan},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Overscan}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Overscan}.</p>
+ *
+ * <p>When this flag is enabled for a window, its normal content may be obscured
+ * to some degree by the overscan region of the display. To ensure key parts of
+ * that content are visible to the user, you can use
+ * {@link View#setFitsSystemWindows(boolean) View.setFitsSystemWindows(boolean)}
+ * to set the point in the view hierarchy where the appropriate offsets should
+ * be applied. (This can be done either by directly calling this function, using
+ * the {@link android.R.attr#fitsSystemWindows} attribute in your view hierarchy,
+ * or implementing you own {@link View#fitSystemWindows(android.graphics.Rect)
+ * View.fitSystemWindows(Rect)} method).</p>
+ *
+ * <p>This mechanism for positioning content elements is identical to its equivalent
+ * use with layout and {@link View#setSystemUiVisibility(int)
+ * View.setSystemUiVisibility(int)}; here is an example layout that will correctly
+ * position its UI elements with this overscan flag is set:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout/overscan_activity.xml complete}
+ */
+ public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
+
+ /**
+ * Window flag: request a translucent status bar with minimal system-provided
+ * background protection.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowTranslucentStatus} attribute; this attribute
+ * is automatically set for you in the standard translucent decor themes
+ * such as
+ * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.</p>
+ *
+ * <p>When this flag is enabled for a window, it automatically sets
+ * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+ * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.</p>
+ */
+ public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
+
+ /**
+ * Window flag: request a translucent navigation bar with minimal system-provided
+ * background protection.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowTranslucentNavigation} attribute; this attribute
+ * is automatically set for you in the standard translucent decor themes
+ * such as
+ * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.</p>
+ *
+ * <p>When this flag is enabled for a window, it automatically sets
+ * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
+ * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.</p>
+ */
+ public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
+
+ /**
+ * Flag for a window in local focus mode.
+ * Window in local focus mode can control focus independent of window manager using
+ * {@link Window#setLocalFocus(boolean, boolean)}.
+ * Usually window in this mode will not get touch/key events from window manager, but will
+ * get events only via local injection using {@link Window#injectInputEvent(InputEvent)}.
+ */
+ public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
+
+ /** Window flag: Enable touches to slide out of a window into neighboring
+ * windows in mid-gesture instead of being captured for the duration of
+ * the gesture.
+ *
+ * This flag changes the behavior of touch focus for this window only.
+ * Touches can slide out of the window but they cannot necessarily slide
+ * back in (unless the other window with touch focus permits it).
+ *
+ * {@hide}
+ */
+ public static final int FLAG_SLIPPERY = 0x20000000;
+
+ /**
+ * Window flag: When requesting layout with an attached window, the attached window may
+ * overlap with the screen decorations of the parent window such as the navigation bar. By
+ * including this flag, the window manager will layout the attached window within the decor
+ * frame of the parent window such that it doesn't overlap with screen decorations.
+ */
+ public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
+
+ /**
+ * Flag indicating that this Window is responsible for drawing the background for the
+ * system bars. If set, the system bars are drawn with a transparent background and the
+ * corresponding areas in this window are filled with the colors specified in
+ * {@link Window#getStatusBarColor()} and {@link Window#getNavigationBarColor()}.
+ */
+ public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
+
+ /**
+ * Various behavioral options/flags. Default is none.
+ *
+ * @see #FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
+ * @see #FLAG_DIM_BEHIND
+ * @see #FLAG_NOT_FOCUSABLE
+ * @see #FLAG_NOT_TOUCHABLE
+ * @see #FLAG_NOT_TOUCH_MODAL
+ * @see #FLAG_TOUCHABLE_WHEN_WAKING
+ * @see #FLAG_KEEP_SCREEN_ON
+ * @see #FLAG_LAYOUT_IN_SCREEN
+ * @see #FLAG_LAYOUT_NO_LIMITS
+ * @see #FLAG_FULLSCREEN
+ * @see #FLAG_FORCE_NOT_FULLSCREEN
+ * @see #FLAG_SECURE
+ * @see #FLAG_SCALED
+ * @see #FLAG_IGNORE_CHEEK_PRESSES
+ * @see #FLAG_LAYOUT_INSET_DECOR
+ * @see #FLAG_ALT_FOCUSABLE_IM
+ * @see #FLAG_WATCH_OUTSIDE_TOUCH
+ * @see #FLAG_SHOW_WHEN_LOCKED
+ * @see #FLAG_SHOW_WALLPAPER
+ * @see #FLAG_TURN_SCREEN_ON
+ * @see #FLAG_DISMISS_KEYGUARD
+ * @see #FLAG_SPLIT_TOUCH
+ * @see #FLAG_HARDWARE_ACCELERATED
+ * @see #FLAG_LOCAL_FOCUS_MODE
+ * @see #FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+ */
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
+ name = "FLAG_ALLOW_LOCK_WHILE_SCREEN_ON"),
+ @ViewDebug.FlagToString(mask = FLAG_DIM_BEHIND, equals = FLAG_DIM_BEHIND,
+ name = "FLAG_DIM_BEHIND"),
+ @ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
+ name = "FLAG_BLUR_BEHIND"),
+ @ViewDebug.FlagToString(mask = FLAG_NOT_FOCUSABLE, equals = FLAG_NOT_FOCUSABLE,
+ name = "FLAG_NOT_FOCUSABLE"),
+ @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCHABLE, equals = FLAG_NOT_TOUCHABLE,
+ name = "FLAG_NOT_TOUCHABLE"),
+ @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCH_MODAL, equals = FLAG_NOT_TOUCH_MODAL,
+ name = "FLAG_NOT_TOUCH_MODAL"),
+ @ViewDebug.FlagToString(mask = FLAG_TOUCHABLE_WHEN_WAKING, equals = FLAG_TOUCHABLE_WHEN_WAKING,
+ name = "FLAG_TOUCHABLE_WHEN_WAKING"),
+ @ViewDebug.FlagToString(mask = FLAG_KEEP_SCREEN_ON, equals = FLAG_KEEP_SCREEN_ON,
+ name = "FLAG_KEEP_SCREEN_ON"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_SCREEN, equals = FLAG_LAYOUT_IN_SCREEN,
+ name = "FLAG_LAYOUT_IN_SCREEN"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_NO_LIMITS, equals = FLAG_LAYOUT_NO_LIMITS,
+ name = "FLAG_LAYOUT_NO_LIMITS"),
+ @ViewDebug.FlagToString(mask = FLAG_FULLSCREEN, equals = FLAG_FULLSCREEN,
+ name = "FLAG_FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN, equals = FLAG_FORCE_NOT_FULLSCREEN,
+ name = "FLAG_FORCE_NOT_FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = FLAG_DITHER, equals = FLAG_DITHER,
+ name = "FLAG_DITHER"),
+ @ViewDebug.FlagToString(mask = FLAG_SECURE, equals = FLAG_SECURE,
+ name = "FLAG_SECURE"),
+ @ViewDebug.FlagToString(mask = FLAG_SCALED, equals = FLAG_SCALED,
+ name = "FLAG_SCALED"),
+ @ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES, equals = FLAG_IGNORE_CHEEK_PRESSES,
+ name = "FLAG_IGNORE_CHEEK_PRESSES"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_INSET_DECOR, equals = FLAG_LAYOUT_INSET_DECOR,
+ name = "FLAG_LAYOUT_INSET_DECOR"),
+ @ViewDebug.FlagToString(mask = FLAG_ALT_FOCUSABLE_IM, equals = FLAG_ALT_FOCUSABLE_IM,
+ name = "FLAG_ALT_FOCUSABLE_IM"),
+ @ViewDebug.FlagToString(mask = FLAG_WATCH_OUTSIDE_TOUCH, equals = FLAG_WATCH_OUTSIDE_TOUCH,
+ name = "FLAG_WATCH_OUTSIDE_TOUCH"),
+ @ViewDebug.FlagToString(mask = FLAG_SHOW_WHEN_LOCKED, equals = FLAG_SHOW_WHEN_LOCKED,
+ name = "FLAG_SHOW_WHEN_LOCKED"),
+ @ViewDebug.FlagToString(mask = FLAG_SHOW_WALLPAPER, equals = FLAG_SHOW_WALLPAPER,
+ name = "FLAG_SHOW_WALLPAPER"),
+ @ViewDebug.FlagToString(mask = FLAG_TURN_SCREEN_ON, equals = FLAG_TURN_SCREEN_ON,
+ name = "FLAG_TURN_SCREEN_ON"),
+ @ViewDebug.FlagToString(mask = FLAG_DISMISS_KEYGUARD, equals = FLAG_DISMISS_KEYGUARD,
+ name = "FLAG_DISMISS_KEYGUARD"),
+ @ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH,
+ name = "FLAG_SPLIT_TOUCH"),
+ @ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED,
+ name = "FLAG_HARDWARE_ACCELERATED"),
+ @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
+ name = "FLAG_LOCAL_FOCUS_MODE"),
+ @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS,
+ name = "FLAG_TRANSLUCENT_STATUS"),
+ @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION,
+ name = "FLAG_TRANSLUCENT_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, equals = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ name = "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS")
+ }, formatToHexString = true)
+ public int flags;
+
+ /**
+ * If the window has requested hardware acceleration, but this is not
+ * allowed in the process it is in, then still render it as if it is
+ * hardware accelerated. This is used for the starting preview windows
+ * in the system process, which don't need to have the overhead of
+ * hardware acceleration (they are just a static rendering), but should
+ * be rendered as such to match the actual window of the app even if it
+ * is hardware accelerated.
+ * Even if the window isn't hardware accelerated, still do its rendering
+ * as if it was.
+ * Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows
+ * that need hardware acceleration (e.g. LockScreen), where hardware acceleration
+ * is generally disabled. This flag must be specified in addition to
+ * {@link #FLAG_HARDWARE_ACCELERATED} to enable hardware acceleration for system
+ * windows.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
+
+ /**
+ * In the system process, we globally do not use hardware acceleration
+ * because there are many threads doing UI there and they conflict.
+ * If certain parts of the UI that really do want to use hardware
+ * acceleration, this flag can be set to force it. This is basically
+ * for the lock screen. Anyone else using it, you are probably wrong.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
+
+ /**
+ * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers
+ * may elect to skip these notifications if they are not doing anything productive with
+ * them (they do not affect the wallpaper scrolling operation) by calling
+ * {@link
+ * android.service.wallpaper.WallpaperService.Engine#setOffsetNotificationsEnabled(boolean)}.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
+
+ /** In a multiuser system if this flag is set and the owner is a system process then this
+ * window will appear on all user screens. This overrides the default behavior of window
+ * types that normally only appear on the owning user's screen. Refer to each window type
+ * to determine its default behavior.
+ *
+ * {@hide} */
+ public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+
+ /**
+ * Never animate position changes of the window.
+ *
+ * {@hide}
+ */
+ @TestApi
+ public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
+
+ /** Window flag: special flag to limit the size of the window to be
+ * original size ([320x480] x density). Used to create window for applications
+ * running under compatibility mode.
+ *
+ * {@hide} */
+ public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;
+
+ /** Window flag: a special option intended for system dialogs. When
+ * this flag is set, the window will demand focus unconditionally when
+ * it is created.
+ * {@hide} */
+ public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
+
+ /** Window flag: maintain the previous translucent decor state when this window
+ * becomes top-most.
+ * {@hide} */
+ public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;
+
+ /**
+ * Flag whether the current window is a keyguard window, meaning that it will hide all other
+ * windows behind it except for windows with flag {@link #FLAG_SHOW_WHEN_LOCKED} set.
+ * Further, this can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
+
+ /**
+ * Flag that prevents the wallpaper behind the current window from receiving touch events.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+
+ /**
+ * Flag to force the status bar window to be visible all the time. If the bar is hidden when
+ * this flag is set it will be shown again and the bar will have a transparent background.
+ * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT = 0x00001000;
+
+ /**
+ * Flag indicating that the x, y, width, and height members should be
+ * ignored (and thus their previous value preserved). For example
+ * because they are being managed externally through repositionChild.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_PRESERVE_GEOMETRY = 0x00002000;
+
+ /**
+ * Flag that will make window ignore app visibility and instead depend purely on the decor
+ * view visibility for determining window visibility. This is used by recents to keep
+ * drawing after it launches an app.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
+
+ /**
+ * Flag to indicate that this window is not expected to be replaced across
+ * configuration change triggered activity relaunches. In general the WindowManager
+ * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
+ * until the replacement is ready to show in order to prevent visual glitch. However
+ * some windows, such as PopupWindows expect to be cleared across configuration change,
+ * and thus should hint to the WindowManager that it should not wait for a replacement.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
+
+ /**
+ * Flag to indicate that this child window should always be laid-out in the parent
+ * frame regardless of the current windowing mode configuration.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000;
+
+ /**
+ * Flag to indicate that this window is always drawing the status bar background, no matter
+ * what the other flags are.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND = 0x00020000;
+
+ /**
+ * Flag to indicate that this window needs Sustained Performance Mode if
+ * the device supports it.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 0x00040000;
+
+ /**
+ * Flag to indicate that any window added by an application process that is of type
+ * {@link #TYPE_TOAST} or that requires
+ * {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when
+ * this window is visible.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
+
+ /**
+ * Indicates that this window is the rounded corners overlay present on some
+ * devices this means that it will be excluded from: screenshots,
+ * screen magnification, and mirroring.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
+
+ /**
+ * If this flag is set on the window, window manager will acquire a sleep token that puts
+ * all activities to sleep as long as this window is visible. When this flag is set, the
+ * window needs to occlude all activity windows.
+ * @hide
+ */
+ @RequiresPermission(permission.DEVICE_POWER)
+ public static final int PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN = 0x00200000;
+
+ /**
+ * Control flags that are private to the platform.
+ * @hide
+ */
+ @TestApi
+ public int privateFlags;
+
+ /**
+ * Value for {@link #needsMenuKey} for a window that has not explicitly specified if it
+ * needs {@link #NEEDS_MENU_SET_TRUE} or doesn't need {@link #NEEDS_MENU_SET_FALSE} a menu
+ * key. For this case, we should look at windows behind it to determine the appropriate
+ * value.
+ *
+ * @hide
+ */
+ public static final int NEEDS_MENU_UNSET = 0;
+
+ /**
+ * Value for {@link #needsMenuKey} for a window that has explicitly specified it needs a
+ * menu key.
+ *
+ * @hide
+ */
+ public static final int NEEDS_MENU_SET_TRUE = 1;
+
+ /**
+ * Value for {@link #needsMenuKey} for a window that has explicitly specified it doesn't
+ * needs a menu key.
+ *
+ * @hide
+ */
+ public static final int NEEDS_MENU_SET_FALSE = 2;
+
+ /**
+ * State variable for a window belonging to an activity that responds to
+ * {@link KeyEvent#KEYCODE_MENU} and therefore needs a Menu key. For devices where Menu is a
+ * physical button this variable is ignored, but on devices where the Menu key is drawn in
+ * software it may be hidden unless this variable is set to {@link #NEEDS_MENU_SET_TRUE}.
+ *
+ * (Note that Action Bars, when available, are the preferred way to offer additional
+ * functions otherwise accessed via an options menu.)
+ *
+ * {@hide}
+ */
+ public int needsMenuKey = NEEDS_MENU_UNSET;
+
+ /**
+ * Given a particular set of window manager flags, determine whether
+ * such a window may be a target for an input method when it has
+ * focus. In particular, this checks the
+ * {@link #FLAG_NOT_FOCUSABLE} and {@link #FLAG_ALT_FOCUSABLE_IM}
+ * flags and returns true if the combination of the two corresponds
+ * to a window that needs to be behind the input method so that the
+ * user can type into it.
+ *
+ * @param flags The current window manager flags.
+ *
+ * @return Returns true if such a window should be behind/interact
+ * with an input method, false if not.
+ */
+ public static boolean mayUseInputMethod(int flags) {
+ switch (flags&(FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)) {
+ case 0:
+ case FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Mask for {@link #softInputMode} of the bits that determine the
+ * desired visibility state of the soft input area for this window.
+ */
+ public static final int SOFT_INPUT_MASK_STATE = 0x0f;
+
+ /**
+ * Visibility state for {@link #softInputMode}: no state has been specified.
+ */
+ public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please don't change the state of
+ * the soft input area.
+ */
+ public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please hide any soft input
+ * area when normally appropriate (when the user is navigating
+ * forward to your window).
+ */
+ public static final int SOFT_INPUT_STATE_HIDDEN = 2;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please always hide any
+ * soft input area when this window receives focus.
+ */
+ public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please show the soft
+ * input area when normally appropriate (when the user is navigating
+ * forward to your window).
+ */
+ public static final int SOFT_INPUT_STATE_VISIBLE = 4;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please always make the
+ * soft input area visible when this window receives input focus.
+ */
+ public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
+
+ /**
+ * Mask for {@link #softInputMode} of the bits that determine the
+ * way that the window should be adjusted to accommodate the soft
+ * input window.
+ */
+ public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
+
+ /** Adjustment option for {@link #softInputMode}: nothing specified.
+ * The system will try to pick one or
+ * the other depending on the contents of the window.
+ */
+ public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
+
+ /** Adjustment option for {@link #softInputMode}: set to allow the
+ * window to be resized when an input
+ * method is shown, so that its contents are not covered by the input
+ * method. This can <em>not</em> be combined with
+ * {@link #SOFT_INPUT_ADJUST_PAN}; if
+ * neither of these are set, then the system will try to pick one or
+ * the other depending on the contents of the window. If the window's
+ * layout parameter flags include {@link #FLAG_FULLSCREEN}, this
+ * value for {@link #softInputMode} will be ignored; the window will
+ * not resize, but will stay fullscreen.
+ */
+ public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
+
+ /** Adjustment option for {@link #softInputMode}: set to have a window
+ * pan when an input method is
+ * shown, so it doesn't need to deal with resizing but just panned
+ * by the framework to ensure the current input focus is visible. This
+ * can <em>not</em> be combined with {@link #SOFT_INPUT_ADJUST_RESIZE}; if
+ * neither of these are set, then the system will try to pick one or
+ * the other depending on the contents of the window.
+ */
+ public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
+
+ /** Adjustment option for {@link #softInputMode}: set to have a window
+ * not adjust for a shown input method. The window will not be resized,
+ * and it will not be panned to make its focus visible.
+ */
+ public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
+
+ /**
+ * Bit for {@link #softInputMode}: set when the user has navigated
+ * forward to the window. This is normally set automatically for
+ * you by the system, though you may want to set it in certain cases
+ * when you are displaying a window yourself. This flag will always
+ * be cleared automatically after the window is displayed.
+ */
+ public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
+
+ /**
+ * An internal annotation for flags that can be specified to {@link #softInputMode}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ SOFT_INPUT_STATE_UNSPECIFIED,
+ SOFT_INPUT_STATE_UNCHANGED,
+ SOFT_INPUT_STATE_HIDDEN,
+ SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+ SOFT_INPUT_STATE_VISIBLE,
+ SOFT_INPUT_STATE_ALWAYS_VISIBLE,
+ SOFT_INPUT_ADJUST_UNSPECIFIED,
+ SOFT_INPUT_ADJUST_RESIZE,
+ SOFT_INPUT_ADJUST_PAN,
+ SOFT_INPUT_ADJUST_NOTHING,
+ SOFT_INPUT_IS_FORWARD_NAVIGATION,
+ })
+ public @interface SoftInputModeFlags {}
+
+ /**
+ * Desired operating mode for any soft input area. May be any combination
+ * of:
+ *
+ * <ul>
+ * <li> One of the visibility states
+ * {@link #SOFT_INPUT_STATE_UNSPECIFIED}, {@link #SOFT_INPUT_STATE_UNCHANGED},
+ * {@link #SOFT_INPUT_STATE_HIDDEN}, {@link #SOFT_INPUT_STATE_ALWAYS_HIDDEN},
+ * {@link #SOFT_INPUT_STATE_VISIBLE}, or {@link #SOFT_INPUT_STATE_ALWAYS_VISIBLE}.
+ * <li> One of the adjustment options
+ * {@link #SOFT_INPUT_ADJUST_UNSPECIFIED}, {@link #SOFT_INPUT_ADJUST_RESIZE},
+ * {@link #SOFT_INPUT_ADJUST_PAN}, or {@link #SOFT_INPUT_ADJUST_NOTHING}.
+ * </ul>
+ *
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowSoftInputMode} attribute.</p>
+ */
+ @SoftInputModeFlags
+ public int softInputMode;
+
+ /**
+ * Placement of window within the screen as per {@link Gravity}. Both
+ * {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int,
+ * android.graphics.Rect) Gravity.apply} and
+ * {@link Gravity#applyDisplay(int, android.graphics.Rect, android.graphics.Rect)
+ * Gravity.applyDisplay} are used during window layout, with this value
+ * given as the desired gravity. For example you can specify
+ * {@link Gravity#DISPLAY_CLIP_HORIZONTAL Gravity.DISPLAY_CLIP_HORIZONTAL} and
+ * {@link Gravity#DISPLAY_CLIP_VERTICAL Gravity.DISPLAY_CLIP_VERTICAL} here
+ * to control the behavior of
+ * {@link Gravity#applyDisplay(int, android.graphics.Rect, android.graphics.Rect)
+ * Gravity.applyDisplay}.
+ *
+ * @see Gravity
+ */
+ public int gravity;
+
+ /**
+ * The horizontal margin, as a percentage of the container's width,
+ * between the container and the widget. See
+ * {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int,
+ * android.graphics.Rect) Gravity.apply} for how this is used. This
+ * field is added with {@link #x} to supply the <var>xAdj</var> parameter.
+ */
+ public float horizontalMargin;
+
+ /**
+ * The vertical margin, as a percentage of the container's height,
+ * between the container and the widget. See
+ * {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int,
+ * android.graphics.Rect) Gravity.apply} for how this is used. This
+ * field is added with {@link #y} to supply the <var>yAdj</var> parameter.
+ */
+ public float verticalMargin;
+
+ /**
+ * Positive insets between the drawing surface and window content.
+ *
+ * @hide
+ */
+ public final Rect surfaceInsets = new Rect();
+
+ /**
+ * Whether the surface insets have been manually set. When set to
+ * {@code false}, the view root will automatically determine the
+ * appropriate surface insets.
+ *
+ * @see #surfaceInsets
+ * @hide
+ */
+ public boolean hasManualSurfaceInsets;
+
+ /**
+ * Whether the previous surface insets should be used vs. what is currently set. When set
+ * to {@code true}, the view root will ignore surfaces insets in this object and use what
+ * it currently has.
+ *
+ * @see #surfaceInsets
+ * @hide
+ */
+ public boolean preservePreviousSurfaceInsets = true;
+
+ /**
+ * The desired bitmap format. May be one of the constants in
+ * {@link android.graphics.PixelFormat}. The choice of format
+ * might be overridden by {@link #setColorMode(int)}. Default is OPAQUE.
+ */
+ public int format;
+
+ /**
+ * A style resource defining the animations to use for this window.
+ * This must be a system resource; it can not be an application resource
+ * because the window manager does not have access to applications.
+ */
+ public int windowAnimations;
+
+ /**
+ * An alpha value to apply to this entire window.
+ * An alpha of 1.0 means fully opaque and 0.0 means fully transparent
+ */
+ public float alpha = 1.0f;
+
+ /**
+ * When {@link #FLAG_DIM_BEHIND} is set, this is the amount of dimming
+ * to apply. Range is from 1.0 for completely opaque to 0.0 for no
+ * dim.
+ */
+ public float dimAmount = 1.0f;
+
+ /**
+ * Default value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the brightness value is not overridden for this window
+ * and normal brightness policy should be used.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
+
+ /**
+ * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the screen or button backlight brightness should be set
+ * to the lowest value when this window is in front.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
+
+ /**
+ * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the screen or button backlight brightness should be set
+ * to the hightest value when this window is in front.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
+
+ /**
+ * This can be used to override the user's preferred brightness of
+ * the screen. A value of less than 0, the default, means to use the
+ * preferred screen brightness. 0 to 1 adjusts the brightness from
+ * dark to full bright.
+ */
+ public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
+
+ /**
+ * This can be used to override the standard behavior of the button and
+ * keyboard backlights. A value of less than 0, the default, means to
+ * use the standard backlight behavior. 0 to 1 adjusts the brightness
+ * from dark to full bright.
+ */
+ public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
+
+ /**
+ * Unspecified value for {@link #rotationAnimation} indicating
+ * a lack of preference.
+ * @hide
+ */
+ public static final int ROTATION_ANIMATION_UNSPECIFIED = -1;
+
+ /**
+ * Value for {@link #rotationAnimation} which specifies that this
+ * window will visually rotate in or out following a rotation.
+ */
+ public static final int ROTATION_ANIMATION_ROTATE = 0;
+
+ /**
+ * Value for {@link #rotationAnimation} which specifies that this
+ * window will fade in or out following a rotation.
+ */
+ public static final int ROTATION_ANIMATION_CROSSFADE = 1;
+
+ /**
+ * Value for {@link #rotationAnimation} which specifies that this window
+ * will immediately disappear or appear following a rotation.
+ */
+ public static final int ROTATION_ANIMATION_JUMPCUT = 2;
+
+ /**
+ * Value for {@link #rotationAnimation} to specify seamless rotation mode.
+ * This works like JUMPCUT but will fall back to CROSSFADE if rotation
+ * can't be applied without pausing the screen. For example, this is ideal
+ * for Camera apps which don't want the viewfinder contents to ever rotate
+ * or fade (and rather to be seamless) but also don't want ROTATION_ANIMATION_JUMPCUT
+ * during app transition scenarios where seamless rotation can't be applied.
+ */
+ public static final int ROTATION_ANIMATION_SEAMLESS = 3;
+
+ /**
+ * Define the exit and entry animations used on this window when the device is rotated.
+ * This only has an affect if the incoming and outgoing topmost
+ * opaque windows have the #FLAG_FULLSCREEN bit set and are not covered
+ * by other windows. All other situations default to the
+ * {@link #ROTATION_ANIMATION_ROTATE} behavior.
+ *
+ * @see #ROTATION_ANIMATION_ROTATE
+ * @see #ROTATION_ANIMATION_CROSSFADE
+ * @see #ROTATION_ANIMATION_JUMPCUT
+ */
+ public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
+
+ /**
+ * Identifier for this window. This will usually be filled in for
+ * you.
+ */
+ public IBinder token = null;
+
+ /**
+ * Name of the package owning this window.
+ */
+ public String packageName = null;
+
+ /**
+ * Specific orientation value for a window.
+ * May be any of the same values allowed
+ * for {@link android.content.pm.ActivityInfo#screenOrientation}.
+ * If not set, a default value of
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}
+ * will be used.
+ */
+ public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+ /**
+ * The preferred refresh rate for the window.
+ *
+ * This must be one of the supported refresh rates obtained for the display(s) the window
+ * is on. The selected refresh rate will be applied to the display's default mode.
+ *
+ * This value is ignored if {@link #preferredDisplayModeId} is set.
+ *
+ * @see Display#getSupportedRefreshRates()
+ * @deprecated use {@link #preferredDisplayModeId} instead
+ */
+ @Deprecated
+ public float preferredRefreshRate;
+
+ /**
+ * Id of the preferred display mode for the window.
+ * <p>
+ * This must be one of the supported modes obtained for the display(s) the window is on.
+ * A value of {@code 0} means no preference.
+ *
+ * @see Display#getSupportedModes()
+ * @see Display.Mode#getModeId()
+ */
+ public int preferredDisplayModeId;
+
+ /**
+ * Control the visibility of the status bar.
+ *
+ * @see View#STATUS_BAR_VISIBLE
+ * @see View#STATUS_BAR_HIDDEN
+ */
+ public int systemUiVisibility;
+
+ /**
+ * @hide
+ * The ui visibility as requested by the views in this hierarchy.
+ * the combined value should be systemUiVisibility | subtreeSystemUiVisibility.
+ */
+ public int subtreeSystemUiVisibility;
+
+ /**
+ * Get callbacks about the system ui visibility changing.
+ *
+ * TODO: Maybe there should be a bitfield of optional callbacks that we need.
+ *
+ * @hide
+ */
+ public boolean hasSystemUiListeners;
+
+ /**
+ * When this window has focus, disable touch pad pointer gesture processing.
+ * The window will receive raw position updates from the touch pad instead
+ * of pointer movements and synthetic touch events.
+ *
+ * @hide
+ */
+ public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;
+
+ /**
+ * Does not construct an input channel for this window. The channel will therefore
+ * be incapable of receiving input.
+ *
+ * @hide
+ */
+ public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;
+
+ /**
+ * When this window has focus, does not call user activity for all input events so
+ * the application will have to do it itself. Should only be used by
+ * the keyguard and phone app.
+ * <p>
+ * Should only be used by the keyguard and phone app.
+ * </p>
+ *
+ * @hide
+ */
+ public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;
+
+ /**
+ * Control special features of the input subsystem.
+ *
+ * @see #INPUT_FEATURE_DISABLE_POINTER_GESTURES
+ * @see #INPUT_FEATURE_NO_INPUT_CHANNEL
+ * @see #INPUT_FEATURE_DISABLE_USER_ACTIVITY
+ * @hide
+ */
+ public int inputFeatures;
+
+ /**
+ * Sets the number of milliseconds before the user activity timeout occurs
+ * when this window has focus. A value of -1 uses the standard timeout.
+ * A value of 0 uses the minimum support display timeout.
+ * <p>
+ * This property can only be used to reduce the user specified display timeout;
+ * it can never make the timeout longer than it normally would be.
+ * </p><p>
+ * Should only be used by the keyguard and phone app.
+ * </p>
+ *
+ * @hide
+ */
+ public long userActivityTimeout = -1;
+
+ /**
+ * For windows with an anchor (e.g. PopupWindow), keeps track of the View that anchors the
+ * window.
+ *
+ * @hide
+ */
+ public int accessibilityIdOfAnchor = -1;
+
+ /**
+ * The window title isn't kept in sync with what is displayed in the title bar, so we
+ * separately track the currently shown title to provide to accessibility.
+ *
+ * @hide
+ */
+ @TestApi
+ public CharSequence accessibilityTitle;
+
+ /**
+ * Sets a timeout in milliseconds before which the window will be hidden
+ * by the window manager. Useful for transient notifications like toasts
+ * so we don't have to rely on client cooperation to ensure the window
+ * is hidden. Must be specified at window creation time. Note that apps
+ * are not prepared to handle their windows being removed without their
+ * explicit request and may try to interact with the removed window
+ * resulting in undefined behavior and crashes. Therefore, we do hide
+ * such windows to prevent them from overlaying other apps.
+ *
+ * @hide
+ */
+ public long hideTimeoutMilliseconds = -1;
+
+ /**
+ * The color mode requested by this window. The target display may
+ * not be able to honor the request. When the color mode is not set
+ * to {@link ActivityInfo#COLOR_MODE_DEFAULT}, it might override the
+ * pixel format specified in {@link #format}.
+ *
+ * @hide
+ */
+ @ActivityInfo.ColorMode
+ private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+
+ public LayoutParams() {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ type = TYPE_APPLICATION;
+ format = PixelFormat.OPAQUE;
+ }
+
+ public LayoutParams(int _type) {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ type = _type;
+ format = PixelFormat.OPAQUE;
+ }
+
+ public LayoutParams(int _type, int _flags) {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ type = _type;
+ flags = _flags;
+ format = PixelFormat.OPAQUE;
+ }
+
+ public LayoutParams(int _type, int _flags, int _format) {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ type = _type;
+ flags = _flags;
+ format = _format;
+ }
+
+ public LayoutParams(int w, int h, int _type, int _flags, int _format) {
+ super(w, h);
+ type = _type;
+ flags = _flags;
+ format = _format;
+ }
+
+ public LayoutParams(int w, int h, int xpos, int ypos, int _type,
+ int _flags, int _format) {
+ super(w, h);
+ x = xpos;
+ y = ypos;
+ type = _type;
+ flags = _flags;
+ format = _format;
+ }
+
+ public final void setTitle(CharSequence title) {
+ if (null == title)
+ title = "";
+
+ mTitle = TextUtils.stringOrSpannedString(title);
+ }
+
+ public final CharSequence getTitle() {
+ return mTitle != null ? mTitle : "";
+ }
+
+ /**
+ * Sets the surface insets based on the elevation (visual z position) of the input view.
+ * @hide
+ */
+ public final void setSurfaceInsets(View view, boolean manual, boolean preservePrevious) {
+ final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
+ // Partial workaround for b/28318973. Every inset change causes a freeform window
+ // to jump a little for a few frames. If we never allow surface insets to decrease,
+ // they will stabilize quickly (often from the very beginning, as most windows start
+ // as focused).
+ // TODO(b/22668382) to fix this properly.
+ if (surfaceInset == 0) {
+ // OK to have 0 (this is the case for non-freeform windows).
+ surfaceInsets.set(0, 0, 0, 0);
+ } else {
+ surfaceInsets.set(
+ Math.max(surfaceInset, surfaceInsets.left),
+ Math.max(surfaceInset, surfaceInsets.top),
+ Math.max(surfaceInset, surfaceInsets.right),
+ Math.max(surfaceInset, surfaceInsets.bottom));
+ }
+ hasManualSurfaceInsets = manual;
+ preservePreviousSurfaceInsets = preservePrevious;
+ }
+
+ /**
+ * <p>Set the color mode of the window. Setting the color mode might
+ * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+ *
+ * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+ * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or
+ * {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+ *
+ * @see #getColorMode()
+ */
+ public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+ mColorMode = colorMode;
+ }
+
+ /**
+ * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+ * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.
+ *
+ * @see #setColorMode(int)
+ */
+ @ActivityInfo.ColorMode
+ public int getColorMode() {
+ return mColorMode;
+ }
+
+ /** @hide */
+ @SystemApi
+ public final void setUserActivityTimeout(long timeout) {
+ userActivityTimeout = timeout;
+ }
+
+ /** @hide */
+ @SystemApi
+ public final long getUserActivityTimeout() {
+ return userActivityTimeout;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int parcelableFlags) {
+ out.writeInt(width);
+ out.writeInt(height);
+ out.writeInt(x);
+ out.writeInt(y);
+ out.writeInt(type);
+ out.writeInt(flags);
+ out.writeInt(privateFlags);
+ out.writeInt(softInputMode);
+ out.writeInt(gravity);
+ out.writeFloat(horizontalMargin);
+ out.writeFloat(verticalMargin);
+ out.writeInt(format);
+ out.writeInt(windowAnimations);
+ out.writeFloat(alpha);
+ out.writeFloat(dimAmount);
+ out.writeFloat(screenBrightness);
+ out.writeFloat(buttonBrightness);
+ out.writeInt(rotationAnimation);
+ out.writeStrongBinder(token);
+ out.writeString(packageName);
+ TextUtils.writeToParcel(mTitle, out, parcelableFlags);
+ out.writeInt(screenOrientation);
+ out.writeFloat(preferredRefreshRate);
+ out.writeInt(preferredDisplayModeId);
+ out.writeInt(systemUiVisibility);
+ out.writeInt(subtreeSystemUiVisibility);
+ out.writeInt(hasSystemUiListeners ? 1 : 0);
+ out.writeInt(inputFeatures);
+ out.writeLong(userActivityTimeout);
+ out.writeInt(surfaceInsets.left);
+ out.writeInt(surfaceInsets.top);
+ out.writeInt(surfaceInsets.right);
+ out.writeInt(surfaceInsets.bottom);
+ out.writeInt(hasManualSurfaceInsets ? 1 : 0);
+ out.writeInt(preservePreviousSurfaceInsets ? 1 : 0);
+ out.writeInt(needsMenuKey);
+ out.writeInt(accessibilityIdOfAnchor);
+ TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
+ out.writeInt(mColorMode);
+ out.writeLong(hideTimeoutMilliseconds);
+ }
+
+ public static final Parcelable.Creator<LayoutParams> CREATOR
+ = new Parcelable.Creator<LayoutParams>() {
+ public LayoutParams createFromParcel(Parcel in) {
+ return new LayoutParams(in);
+ }
+
+ public LayoutParams[] newArray(int size) {
+ return new LayoutParams[size];
+ }
+ };
+
+
+ public LayoutParams(Parcel in) {
+ width = in.readInt();
+ height = in.readInt();
+ x = in.readInt();
+ y = in.readInt();
+ type = in.readInt();
+ flags = in.readInt();
+ privateFlags = in.readInt();
+ softInputMode = in.readInt();
+ gravity = in.readInt();
+ horizontalMargin = in.readFloat();
+ verticalMargin = in.readFloat();
+ format = in.readInt();
+ windowAnimations = in.readInt();
+ alpha = in.readFloat();
+ dimAmount = in.readFloat();
+ screenBrightness = in.readFloat();
+ buttonBrightness = in.readFloat();
+ rotationAnimation = in.readInt();
+ token = in.readStrongBinder();
+ packageName = in.readString();
+ mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ screenOrientation = in.readInt();
+ preferredRefreshRate = in.readFloat();
+ preferredDisplayModeId = in.readInt();
+ systemUiVisibility = in.readInt();
+ subtreeSystemUiVisibility = in.readInt();
+ hasSystemUiListeners = in.readInt() != 0;
+ inputFeatures = in.readInt();
+ userActivityTimeout = in.readLong();
+ surfaceInsets.left = in.readInt();
+ surfaceInsets.top = in.readInt();
+ surfaceInsets.right = in.readInt();
+ surfaceInsets.bottom = in.readInt();
+ hasManualSurfaceInsets = in.readInt() != 0;
+ preservePreviousSurfaceInsets = in.readInt() != 0;
+ needsMenuKey = in.readInt();
+ accessibilityIdOfAnchor = in.readInt();
+ accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mColorMode = in.readInt();
+ hideTimeoutMilliseconds = in.readLong();
+ }
+
+ @SuppressWarnings({"PointlessBitwiseExpression"})
+ public static final int LAYOUT_CHANGED = 1<<0;
+ public static final int TYPE_CHANGED = 1<<1;
+ public static final int FLAGS_CHANGED = 1<<2;
+ public static final int FORMAT_CHANGED = 1<<3;
+ public static final int ANIMATION_CHANGED = 1<<4;
+ public static final int DIM_AMOUNT_CHANGED = 1<<5;
+ public static final int TITLE_CHANGED = 1<<6;
+ public static final int ALPHA_CHANGED = 1<<7;
+ public static final int MEMORY_TYPE_CHANGED = 1<<8;
+ public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
+ public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
+ public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+ public static final int ROTATION_ANIMATION_CHANGED = 1<<12;
+ /** {@hide} */
+ public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<13;
+ /** {@hide} */
+ public static final int SYSTEM_UI_VISIBILITY_CHANGED = 1<<14;
+ /** {@hide} */
+ public static final int SYSTEM_UI_LISTENER_CHANGED = 1<<15;
+ /** {@hide} */
+ public static final int INPUT_FEATURES_CHANGED = 1<<16;
+ /** {@hide} */
+ public static final int PRIVATE_FLAGS_CHANGED = 1<<17;
+ /** {@hide} */
+ public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<18;
+ /** {@hide} */
+ public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19;
+ /** {@hide} */
+ public static final int SURFACE_INSETS_CHANGED = 1<<20;
+ /** {@hide} */
+ public static final int PREFERRED_REFRESH_RATE_CHANGED = 1 << 21;
+ /** {@hide} */
+ public static final int NEEDS_MENU_KEY_CHANGED = 1 << 22;
+ /** {@hide} */
+ public static final int PREFERRED_DISPLAY_MODE_ID = 1 << 23;
+ /** {@hide} */
+ public static final int ACCESSIBILITY_ANCHOR_CHANGED = 1 << 24;
+ /** {@hide} */
+ @TestApi
+ public static final int ACCESSIBILITY_TITLE_CHANGED = 1 << 25;
+ /** {@hide} */
+ public static final int COLOR_MODE_CHANGED = 1 << 26;
+ /** {@hide} */
+ public static final int EVERYTHING_CHANGED = 0xffffffff;
+
+ // internal buffer to backup/restore parameters under compatibility mode.
+ private int[] mCompatibilityParamsBackup = null;
+
+ public final int copyFrom(LayoutParams o) {
+ int changes = 0;
+
+ if (width != o.width) {
+ width = o.width;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (height != o.height) {
+ height = o.height;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (x != o.x) {
+ x = o.x;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (y != o.y) {
+ y = o.y;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (horizontalWeight != o.horizontalWeight) {
+ horizontalWeight = o.horizontalWeight;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (verticalWeight != o.verticalWeight) {
+ verticalWeight = o.verticalWeight;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (horizontalMargin != o.horizontalMargin) {
+ horizontalMargin = o.horizontalMargin;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (verticalMargin != o.verticalMargin) {
+ verticalMargin = o.verticalMargin;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (type != o.type) {
+ type = o.type;
+ changes |= TYPE_CHANGED;
+ }
+ if (flags != o.flags) {
+ final int diff = flags ^ o.flags;
+ if ((diff & (FLAG_TRANSLUCENT_STATUS | FLAG_TRANSLUCENT_NAVIGATION)) != 0) {
+ changes |= TRANSLUCENT_FLAGS_CHANGED;
+ }
+ flags = o.flags;
+ changes |= FLAGS_CHANGED;
+ }
+ if (privateFlags != o.privateFlags) {
+ privateFlags = o.privateFlags;
+ changes |= PRIVATE_FLAGS_CHANGED;
+ }
+ if (softInputMode != o.softInputMode) {
+ softInputMode = o.softInputMode;
+ changes |= SOFT_INPUT_MODE_CHANGED;
+ }
+ if (gravity != o.gravity) {
+ gravity = o.gravity;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (format != o.format) {
+ format = o.format;
+ changes |= FORMAT_CHANGED;
+ }
+ if (windowAnimations != o.windowAnimations) {
+ windowAnimations = o.windowAnimations;
+ changes |= ANIMATION_CHANGED;
+ }
+ if (token == null) {
+ // NOTE: token only copied if the recipient doesn't
+ // already have one.
+ token = o.token;
+ }
+ if (packageName == null) {
+ // NOTE: packageName only copied if the recipient doesn't
+ // already have one.
+ packageName = o.packageName;
+ }
+ if (!Objects.equals(mTitle, o.mTitle) && o.mTitle != null) {
+ // NOTE: mTitle only copied if the originator set one.
+ mTitle = o.mTitle;
+ changes |= TITLE_CHANGED;
+ }
+ if (alpha != o.alpha) {
+ alpha = o.alpha;
+ changes |= ALPHA_CHANGED;
+ }
+ if (dimAmount != o.dimAmount) {
+ dimAmount = o.dimAmount;
+ changes |= DIM_AMOUNT_CHANGED;
+ }
+ if (screenBrightness != o.screenBrightness) {
+ screenBrightness = o.screenBrightness;
+ changes |= SCREEN_BRIGHTNESS_CHANGED;
+ }
+ if (buttonBrightness != o.buttonBrightness) {
+ buttonBrightness = o.buttonBrightness;
+ changes |= BUTTON_BRIGHTNESS_CHANGED;
+ }
+ if (rotationAnimation != o.rotationAnimation) {
+ rotationAnimation = o.rotationAnimation;
+ changes |= ROTATION_ANIMATION_CHANGED;
+ }
+
+ if (screenOrientation != o.screenOrientation) {
+ screenOrientation = o.screenOrientation;
+ changes |= SCREEN_ORIENTATION_CHANGED;
+ }
+
+ if (preferredRefreshRate != o.preferredRefreshRate) {
+ preferredRefreshRate = o.preferredRefreshRate;
+ changes |= PREFERRED_REFRESH_RATE_CHANGED;
+ }
+
+ if (preferredDisplayModeId != o.preferredDisplayModeId) {
+ preferredDisplayModeId = o.preferredDisplayModeId;
+ changes |= PREFERRED_DISPLAY_MODE_ID;
+ }
+
+ if (systemUiVisibility != o.systemUiVisibility
+ || subtreeSystemUiVisibility != o.subtreeSystemUiVisibility) {
+ systemUiVisibility = o.systemUiVisibility;
+ subtreeSystemUiVisibility = o.subtreeSystemUiVisibility;
+ changes |= SYSTEM_UI_VISIBILITY_CHANGED;
+ }
+
+ if (hasSystemUiListeners != o.hasSystemUiListeners) {
+ hasSystemUiListeners = o.hasSystemUiListeners;
+ changes |= SYSTEM_UI_LISTENER_CHANGED;
+ }
+
+ if (inputFeatures != o.inputFeatures) {
+ inputFeatures = o.inputFeatures;
+ changes |= INPUT_FEATURES_CHANGED;
+ }
+
+ if (userActivityTimeout != o.userActivityTimeout) {
+ userActivityTimeout = o.userActivityTimeout;
+ changes |= USER_ACTIVITY_TIMEOUT_CHANGED;
+ }
+
+ if (!surfaceInsets.equals(o.surfaceInsets)) {
+ surfaceInsets.set(o.surfaceInsets);
+ changes |= SURFACE_INSETS_CHANGED;
+ }
+
+ if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) {
+ hasManualSurfaceInsets = o.hasManualSurfaceInsets;
+ changes |= SURFACE_INSETS_CHANGED;
+ }
+
+ if (preservePreviousSurfaceInsets != o.preservePreviousSurfaceInsets) {
+ preservePreviousSurfaceInsets = o.preservePreviousSurfaceInsets;
+ changes |= SURFACE_INSETS_CHANGED;
+ }
+
+ if (needsMenuKey != o.needsMenuKey) {
+ needsMenuKey = o.needsMenuKey;
+ changes |= NEEDS_MENU_KEY_CHANGED;
+ }
+
+ if (accessibilityIdOfAnchor != o.accessibilityIdOfAnchor) {
+ accessibilityIdOfAnchor = o.accessibilityIdOfAnchor;
+ changes |= ACCESSIBILITY_ANCHOR_CHANGED;
+ }
+
+ if (!Objects.equals(accessibilityTitle, o.accessibilityTitle)
+ && o.accessibilityTitle != null) {
+ // NOTE: accessibilityTitle only copied if the originator set one.
+ accessibilityTitle = o.accessibilityTitle;
+ changes |= ACCESSIBILITY_TITLE_CHANGED;
+ }
+
+ if (mColorMode != o.mColorMode) {
+ mColorMode = o.mColorMode;
+ changes |= COLOR_MODE_CHANGED;
+ }
+
+ // This can't change, it's only set at window creation time.
+ hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
+
+ return changes;
+ }
+
+ @Override
+ public String debug(String output) {
+ output += "Contents of " + this + ":";
+ Log.d("Debug", output);
+ output = super.debug("");
+ Log.d("Debug", output);
+ Log.d("Debug", "");
+ Log.d("Debug", "WindowManager.LayoutParams={title=" + mTitle + "}");
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(256);
+ sb.append("WM.LayoutParams{");
+ sb.append("(");
+ sb.append(x);
+ sb.append(',');
+ sb.append(y);
+ sb.append(")(");
+ sb.append((width == MATCH_PARENT ? "fill" : (width == WRAP_CONTENT
+ ? "wrap" : String.valueOf(width))));
+ sb.append('x');
+ sb.append((height == MATCH_PARENT ? "fill" : (height == WRAP_CONTENT
+ ? "wrap" : String.valueOf(height))));
+ sb.append(")");
+ if (horizontalMargin != 0) {
+ sb.append(" hm=");
+ sb.append(horizontalMargin);
+ }
+ if (verticalMargin != 0) {
+ sb.append(" vm=");
+ sb.append(verticalMargin);
+ }
+ if (gravity != 0) {
+ sb.append(" gr=#");
+ sb.append(Integer.toHexString(gravity));
+ }
+ if (softInputMode != 0) {
+ sb.append(" sim=#");
+ sb.append(Integer.toHexString(softInputMode));
+ }
+ sb.append(" ty=");
+ sb.append(type);
+ sb.append(" fl=#");
+ sb.append(Integer.toHexString(flags));
+ if (privateFlags != 0) {
+ if ((privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
+ sb.append(" compatible=true");
+ }
+ sb.append(" pfl=0x").append(Integer.toHexString(privateFlags));
+ }
+ if (format != PixelFormat.OPAQUE) {
+ sb.append(" fmt=");
+ sb.append(format);
+ }
+ if (windowAnimations != 0) {
+ sb.append(" wanim=0x");
+ sb.append(Integer.toHexString(windowAnimations));
+ }
+ if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+ sb.append(" or=");
+ sb.append(screenOrientation);
+ }
+ if (alpha != 1.0f) {
+ sb.append(" alpha=");
+ sb.append(alpha);
+ }
+ if (screenBrightness != BRIGHTNESS_OVERRIDE_NONE) {
+ sb.append(" sbrt=");
+ sb.append(screenBrightness);
+ }
+ if (buttonBrightness != BRIGHTNESS_OVERRIDE_NONE) {
+ sb.append(" bbrt=");
+ sb.append(buttonBrightness);
+ }
+ if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
+ sb.append(" rotAnim=");
+ sb.append(rotationAnimation);
+ }
+ if (preferredRefreshRate != 0) {
+ sb.append(" preferredRefreshRate=");
+ sb.append(preferredRefreshRate);
+ }
+ if (preferredDisplayModeId != 0) {
+ sb.append(" preferredDisplayMode=");
+ sb.append(preferredDisplayModeId);
+ }
+ if (systemUiVisibility != 0) {
+ sb.append(" sysui=0x");
+ sb.append(Integer.toHexString(systemUiVisibility));
+ }
+ if (subtreeSystemUiVisibility != 0) {
+ sb.append(" vsysui=0x");
+ sb.append(Integer.toHexString(subtreeSystemUiVisibility));
+ }
+ if (hasSystemUiListeners) {
+ sb.append(" sysuil=");
+ sb.append(hasSystemUiListeners);
+ }
+ if (inputFeatures != 0) {
+ sb.append(" if=0x").append(Integer.toHexString(inputFeatures));
+ }
+ if (userActivityTimeout >= 0) {
+ sb.append(" userActivityTimeout=").append(userActivityTimeout);
+ }
+ if (surfaceInsets.left != 0 || surfaceInsets.top != 0 || surfaceInsets.right != 0 ||
+ surfaceInsets.bottom != 0 || hasManualSurfaceInsets
+ || !preservePreviousSurfaceInsets) {
+ sb.append(" surfaceInsets=").append(surfaceInsets);
+ if (hasManualSurfaceInsets) {
+ sb.append(" (manual)");
+ }
+ if (!preservePreviousSurfaceInsets) {
+ sb.append(" (!preservePreviousSurfaceInsets)");
+ }
+ }
+ if (needsMenuKey != NEEDS_MENU_UNSET) {
+ sb.append(" needsMenuKey=");
+ sb.append(needsMenuKey);
+ }
+ sb.append(" colorMode=").append(mColorMode);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(WindowLayoutParamsProto.TYPE, type);
+ proto.end(token);
+ }
+
+ /**
+ * Scale the layout params' coordinates and size.
+ * @hide
+ */
+ public void scale(float scale) {
+ x = (int) (x * scale + 0.5f);
+ y = (int) (y * scale + 0.5f);
+ if (width > 0) {
+ width = (int) (width * scale + 0.5f);
+ }
+ if (height > 0) {
+ height = (int) (height * scale + 0.5f);
+ }
+ }
+
+ /**
+ * Backup the layout parameters used in compatibility mode.
+ * @see LayoutParams#restore()
+ */
+ void backup() {
+ int[] backup = mCompatibilityParamsBackup;
+ if (backup == null) {
+ // we backup 4 elements, x, y, width, height
+ backup = mCompatibilityParamsBackup = new int[4];
+ }
+ backup[0] = x;
+ backup[1] = y;
+ backup[2] = width;
+ backup[3] = height;
+ }
+
+ /**
+ * Restore the layout params' coordinates, size and gravity
+ * @see LayoutParams#backup()
+ */
+ void restore() {
+ int[] backup = mCompatibilityParamsBackup;
+ if (backup != null) {
+ x = backup[0];
+ y = backup[1];
+ width = backup[2];
+ height = backup[3];
+ }
+ }
+
+ private CharSequence mTitle = null;
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("x", x);
+ encoder.addProperty("y", y);
+ encoder.addProperty("horizontalWeight", horizontalWeight);
+ encoder.addProperty("verticalWeight", verticalWeight);
+ encoder.addProperty("type", type);
+ encoder.addProperty("flags", flags);
+ }
+
+ /**
+ * @hide
+ * @return True if the layout parameters will cause the window to cover the full screen;
+ * false otherwise.
+ */
+ public boolean isFullscreen() {
+ return x == 0 && y == 0
+ && width == WindowManager.LayoutParams.MATCH_PARENT
+ && height == WindowManager.LayoutParams.MATCH_PARENT;
+ }
+ }
+}
diff --git a/android/view/WindowManagerGlobal.java b/android/view/WindowManagerGlobal.java
new file mode 100644
index 00000000..c7e8dee3
--- /dev/null
+++ b/android/view/WindowManagerGlobal.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) 2012 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.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.ComponentCallbacks2;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Provides low-level communication with the system window manager for
+ * operations that are not associated with any particular context.
+ *
+ * This class is only used internally to implement global functions where
+ * the caller already knows the display and relevant compatibility information
+ * for the operation. For most purposes, you should use {@link WindowManager} instead
+ * since it is bound to a context.
+ *
+ * @see WindowManagerImpl
+ * @hide
+ */
+public final class WindowManagerGlobal {
+ private static final String TAG = "WindowManager";
+
+ /**
+ * The user is navigating with keys (not the touch screen), so
+ * navigational focus should be shown.
+ */
+ public static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1;
+
+ /**
+ * This is the first time the window is being drawn,
+ * so the client must call drawingFinished() when done
+ */
+ public static final int RELAYOUT_RES_FIRST_TIME = 0x2;
+
+ /**
+ * The window manager has changed the surface from the last call.
+ */
+ public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4;
+
+ /**
+ * The window is being resized by dragging on the docked divider. The client should render
+ * at (0, 0) and extend its background to the background frame passed into
+ * {@link IWindow#resized}.
+ */
+ public static final int RELAYOUT_RES_DRAG_RESIZING_DOCKED = 0x8;
+
+ /**
+ * The window is being resized by dragging one of the window corners,
+ * in this case the surface would be fullscreen-sized. The client should
+ * render to the actual frame location (instead of (0,curScrollY)).
+ */
+ public static final int RELAYOUT_RES_DRAG_RESIZING_FREEFORM = 0x10;
+
+ /**
+ * The window manager has changed the size of the surface from the last call.
+ */
+ public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x20;
+
+ /**
+ * In multi-window we force show the navigation bar. Because we don't want that the surface size
+ * changes in this mode, we instead have a flag whether the navigation bar size should always be
+ * consumed, so the app is treated like there is no virtual navigation bar at all.
+ */
+ public static final int RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR = 0x40;
+
+ /**
+ * Flag for relayout: the client will be later giving
+ * internal insets; as a result, the window will not impact other window
+ * layouts until the insets are given.
+ */
+ public static final int RELAYOUT_INSETS_PENDING = 0x1;
+
+ /**
+ * Flag for relayout: the client may be currently using the current surface,
+ * so if it is to be destroyed as a part of the relayout the destroy must
+ * be deferred until later. The client will call performDeferredDestroy()
+ * when it is okay.
+ */
+ public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2;
+
+ public static final int ADD_FLAG_APP_VISIBLE = 0x2;
+ public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_RES_IN_TOUCH_MODE;
+
+ /**
+ * Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR}, but as a "hint" when adding the window.
+ */
+ public static final int ADD_FLAG_ALWAYS_CONSUME_NAV_BAR = 0x4;
+
+ public static final int ADD_OKAY = 0;
+ public static final int ADD_BAD_APP_TOKEN = -1;
+ public static final int ADD_BAD_SUBWINDOW_TOKEN = -2;
+ public static final int ADD_NOT_APP_TOKEN = -3;
+ public static final int ADD_APP_EXITING = -4;
+ public static final int ADD_DUPLICATE_ADD = -5;
+ public static final int ADD_STARTING_NOT_NEEDED = -6;
+ public static final int ADD_MULTIPLE_SINGLETON = -7;
+ public static final int ADD_PERMISSION_DENIED = -8;
+ public static final int ADD_INVALID_DISPLAY = -9;
+ public static final int ADD_INVALID_TYPE = -10;
+
+ private static WindowManagerGlobal sDefaultWindowManager;
+ private static IWindowManager sWindowManagerService;
+ private static IWindowSession sWindowSession;
+
+ private final Object mLock = new Object();
+
+ private final ArrayList<View> mViews = new ArrayList<View>();
+ private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
+ private final ArrayList<WindowManager.LayoutParams> mParams =
+ new ArrayList<WindowManager.LayoutParams>();
+ private final ArraySet<View> mDyingViews = new ArraySet<View>();
+
+ private Runnable mSystemPropertyUpdater;
+
+ private WindowManagerGlobal() {
+ }
+
+ public static void initialize() {
+ getWindowManagerService();
+ }
+
+ public static WindowManagerGlobal getInstance() {
+ synchronized (WindowManagerGlobal.class) {
+ if (sDefaultWindowManager == null) {
+ sDefaultWindowManager = new WindowManagerGlobal();
+ }
+ return sDefaultWindowManager;
+ }
+ }
+
+ public static IWindowManager getWindowManagerService() {
+ synchronized (WindowManagerGlobal.class) {
+ if (sWindowManagerService == null) {
+ sWindowManagerService = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ try {
+ if (sWindowManagerService != null) {
+ ValueAnimator.setDurationScale(
+ sWindowManagerService.getCurrentAnimatorScale());
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return sWindowManagerService;
+ }
+ }
+
+ public static IWindowSession getWindowSession() {
+ synchronized (WindowManagerGlobal.class) {
+ if (sWindowSession == null) {
+ try {
+ InputMethodManager imm = InputMethodManager.getInstance();
+ IWindowManager windowManager = getWindowManagerService();
+ sWindowSession = windowManager.openSession(
+ new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {
+ ValueAnimator.setDurationScale(scale);
+ }
+ },
+ imm.getClient(), imm.getInputContext());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return sWindowSession;
+ }
+ }
+
+ public static IWindowSession peekWindowSession() {
+ synchronized (WindowManagerGlobal.class) {
+ return sWindowSession;
+ }
+ }
+
+ public String[] getViewRootNames() {
+ synchronized (mLock) {
+ final int numRoots = mRoots.size();
+ String[] mViewRoots = new String[numRoots];
+ for (int i = 0; i < numRoots; ++i) {
+ mViewRoots[i] = getWindowName(mRoots.get(i));
+ }
+ return mViewRoots;
+ }
+ }
+
+ public ArrayList<ViewRootImpl> getRootViews(IBinder token) {
+ ArrayList<ViewRootImpl> views = new ArrayList<>();
+ synchronized (mLock) {
+ final int numRoots = mRoots.size();
+ for (int i = 0; i < numRoots; ++i) {
+ WindowManager.LayoutParams params = mParams.get(i);
+ if (params.token == null) {
+ continue;
+ }
+ if (params.token != token) {
+ boolean isChild = false;
+ if (params.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
+ && params.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ for (int j = 0 ; j < numRoots; ++j) {
+ View viewj = mViews.get(j);
+ WindowManager.LayoutParams paramsj = mParams.get(j);
+ if (params.token == viewj.getWindowToken()
+ && paramsj.token == token) {
+ isChild = true;
+ break;
+ }
+ }
+ }
+ if (!isChild) {
+ continue;
+ }
+ }
+ views.add(mRoots.get(i));
+ }
+ }
+ return views;
+ }
+
+ public View getWindowView(IBinder windowToken) {
+ synchronized (mLock) {
+ final int numViews = mViews.size();
+ for (int i = 0; i < numViews; ++i) {
+ final View view = mViews.get(i);
+ if (view.getWindowToken() == windowToken) {
+ return view;
+ }
+ }
+ }
+ return null;
+ }
+
+ public View getRootView(String name) {
+ synchronized (mLock) {
+ for (int i = mRoots.size() - 1; i >= 0; --i) {
+ final ViewRootImpl root = mRoots.get(i);
+ if (name.equals(getWindowName(root))) return root.getView();
+ }
+ }
+
+ return null;
+ }
+
+ public void addView(View view, ViewGroup.LayoutParams params,
+ Display display, Window parentWindow) {
+ if (view == null) {
+ throw new IllegalArgumentException("view must not be null");
+ }
+ if (display == null) {
+ throw new IllegalArgumentException("display must not be null");
+ }
+ if (!(params instanceof WindowManager.LayoutParams)) {
+ throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+ }
+
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+ if (parentWindow != null) {
+ parentWindow.adjustLayoutParamsForSubWindow(wparams);
+ } else {
+ // If there's no parent, then hardware acceleration for this view is
+ // set from the application's hardware acceleration setting.
+ final Context context = view.getContext();
+ if (context != null
+ && (context.getApplicationInfo().flags
+ & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+ wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ }
+
+ ViewRootImpl root;
+ View panelParentView = null;
+
+ synchronized (mLock) {
+ // Start watching for system property changes.
+ if (mSystemPropertyUpdater == null) {
+ mSystemPropertyUpdater = new Runnable() {
+ @Override public void run() {
+ synchronized (mLock) {
+ for (int i = mRoots.size() - 1; i >= 0; --i) {
+ mRoots.get(i).loadSystemProperties();
+ }
+ }
+ }
+ };
+ SystemProperties.addChangeCallback(mSystemPropertyUpdater);
+ }
+
+ int index = findViewLocked(view, false);
+ if (index >= 0) {
+ if (mDyingViews.contains(view)) {
+ // Don't wait for MSG_DIE to make it's way through root's queue.
+ mRoots.get(index).doDie();
+ } else {
+ throw new IllegalStateException("View " + view
+ + " has already been added to the window manager.");
+ }
+ // The previous removeView() had not completed executing. Now it has.
+ }
+
+ // If this is a panel window, then find the window it is being
+ // attached to for future reference.
+ if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
+ wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ final int count = mViews.size();
+ for (int i = 0; i < count; i++) {
+ if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
+ panelParentView = mViews.get(i);
+ }
+ }
+ }
+
+ root = new ViewRootImpl(view.getContext(), display);
+
+ view.setLayoutParams(wparams);
+
+ mViews.add(view);
+ mRoots.add(root);
+ mParams.add(wparams);
+
+ // do this last because it fires off messages to start doing things
+ try {
+ root.setView(view, wparams, panelParentView);
+ } catch (RuntimeException e) {
+ // BadTokenException or InvalidDisplayException, clean up.
+ if (index >= 0) {
+ removeViewLocked(index, true);
+ }
+ throw e;
+ }
+ }
+ }
+
+ public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+ if (view == null) {
+ throw new IllegalArgumentException("view must not be null");
+ }
+ if (!(params instanceof WindowManager.LayoutParams)) {
+ throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+ }
+
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
+
+ view.setLayoutParams(wparams);
+
+ synchronized (mLock) {
+ int index = findViewLocked(view, true);
+ ViewRootImpl root = mRoots.get(index);
+ mParams.remove(index);
+ mParams.add(index, wparams);
+ root.setLayoutParams(wparams, false);
+ }
+ }
+
+ public void removeView(View view, boolean immediate) {
+ if (view == null) {
+ throw new IllegalArgumentException("view must not be null");
+ }
+
+ synchronized (mLock) {
+ int index = findViewLocked(view, true);
+ View curView = mRoots.get(index).getView();
+ removeViewLocked(index, immediate);
+ if (curView == view) {
+ return;
+ }
+
+ throw new IllegalStateException("Calling with view " + view
+ + " but the ViewAncestor is attached to " + curView);
+ }
+ }
+
+ /**
+ * Remove all roots with specified token.
+ *
+ * @param token app or window token.
+ * @param who name of caller, used in logs.
+ * @param what type of caller, used in logs.
+ */
+ public void closeAll(IBinder token, String who, String what) {
+ closeAllExceptView(token, null /* view */, who, what);
+ }
+
+ /**
+ * Remove all roots with specified token, except maybe one view.
+ *
+ * @param token app or window token.
+ * @param view view that should be should be preserved along with it's root.
+ * Pass null if everything should be removed.
+ * @param who name of caller, used in logs.
+ * @param what type of caller, used in logs.
+ */
+ public void closeAllExceptView(IBinder token, View view, String who, String what) {
+ synchronized (mLock) {
+ int count = mViews.size();
+ for (int i = 0; i < count; i++) {
+ if ((view == null || mViews.get(i) != view)
+ && (token == null || mParams.get(i).token == token)) {
+ ViewRootImpl root = mRoots.get(i);
+
+ if (who != null) {
+ WindowLeaked leak = new WindowLeaked(
+ what + " " + who + " has leaked window "
+ + root.getView() + " that was originally added here");
+ leak.setStackTrace(root.getLocation().getStackTrace());
+ Log.e(TAG, "", leak);
+ }
+
+ removeViewLocked(i, false);
+ }
+ }
+ }
+ }
+
+ private void removeViewLocked(int index, boolean immediate) {
+ ViewRootImpl root = mRoots.get(index);
+ View view = root.getView();
+
+ if (view != null) {
+ InputMethodManager imm = InputMethodManager.getInstance();
+ if (imm != null) {
+ imm.windowDismissed(mViews.get(index).getWindowToken());
+ }
+ }
+ boolean deferred = root.die(immediate);
+ if (view != null) {
+ view.assignParent(null);
+ if (deferred) {
+ mDyingViews.add(view);
+ }
+ }
+ }
+
+ void doRemoveView(ViewRootImpl root) {
+ synchronized (mLock) {
+ final int index = mRoots.indexOf(root);
+ if (index >= 0) {
+ mRoots.remove(index);
+ mParams.remove(index);
+ final View view = mViews.remove(index);
+ mDyingViews.remove(view);
+ }
+ }
+ if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
+ doTrimForeground();
+ }
+ }
+
+ private int findViewLocked(View view, boolean required) {
+ final int index = mViews.indexOf(view);
+ if (required && index < 0) {
+ throw new IllegalArgumentException("View=" + view + " not attached to window manager");
+ }
+ return index;
+ }
+
+ public static boolean shouldDestroyEglContext(int trimLevel) {
+ // On low-end gfx devices we trim when memory is moderate;
+ // on high-end devices we do this when low.
+ if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+ return true;
+ }
+ if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
+ && !ActivityManager.isHighEndGfx()) {
+ return true;
+ }
+ return false;
+ }
+
+ public void trimMemory(int level) {
+ if (ThreadedRenderer.isAvailable()) {
+ if (shouldDestroyEglContext(level)) {
+ // Destroy all hardware surfaces and resources associated to
+ // known windows
+ synchronized (mLock) {
+ for (int i = mRoots.size() - 1; i >= 0; --i) {
+ mRoots.get(i).destroyHardwareResources();
+ }
+ }
+ // Force a full memory flush
+ level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
+ }
+
+ ThreadedRenderer.trimMemory(level);
+
+ if (ThreadedRenderer.sTrimForeground) {
+ doTrimForeground();
+ }
+ }
+ }
+
+ public static void trimForeground() {
+ if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
+ WindowManagerGlobal wm = WindowManagerGlobal.getInstance();
+ wm.doTrimForeground();
+ }
+ }
+
+ private void doTrimForeground() {
+ boolean hasVisibleWindows = false;
+ synchronized (mLock) {
+ for (int i = mRoots.size() - 1; i >= 0; --i) {
+ final ViewRootImpl root = mRoots.get(i);
+ if (root.mView != null && root.getHostVisibility() == View.VISIBLE
+ && root.mAttachInfo.mThreadedRenderer != null) {
+ hasVisibleWindows = true;
+ } else {
+ root.destroyHardwareResources();
+ }
+ }
+ }
+ if (!hasVisibleWindows) {
+ ThreadedRenderer.trimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ }
+ }
+
+ public void dumpGfxInfo(FileDescriptor fd, String[] args) {
+ FileOutputStream fout = new FileOutputStream(fd);
+ PrintWriter pw = new FastPrintWriter(fout);
+ try {
+ synchronized (mLock) {
+ final int count = mViews.size();
+
+ pw.println("Profile data in ms:");
+
+ for (int i = 0; i < count; i++) {
+ ViewRootImpl root = mRoots.get(i);
+ String name = getWindowName(root);
+ pw.printf("\n\t%s (visibility=%d)", name, root.getHostVisibility());
+
+ ThreadedRenderer renderer =
+ root.getView().mAttachInfo.mThreadedRenderer;
+ if (renderer != null) {
+ renderer.dumpGfxInfo(pw, fd, args);
+ }
+ }
+
+ pw.println("\nView hierarchy:\n");
+
+ int viewsCount = 0;
+ int displayListsSize = 0;
+ int[] info = new int[2];
+
+ for (int i = 0; i < count; i++) {
+ ViewRootImpl root = mRoots.get(i);
+ root.dumpGfxInfo(info);
+
+ String name = getWindowName(root);
+ pw.printf(" %s\n %d views, %.2f kB of display lists",
+ name, info[0], info[1] / 1024.0f);
+ pw.printf("\n\n");
+
+ viewsCount += info[0];
+ displayListsSize += info[1];
+ }
+
+ pw.printf("\nTotal ViewRootImpl: %d\n", count);
+ pw.printf("Total Views: %d\n", viewsCount);
+ pw.printf("Total DisplayList: %.2f kB\n\n", displayListsSize / 1024.0f);
+ }
+ } finally {
+ pw.flush();
+ }
+ }
+
+ private static String getWindowName(ViewRootImpl root) {
+ return root.mWindowAttributes.getTitle() + "/" +
+ root.getClass().getName() + '@' + Integer.toHexString(root.hashCode());
+ }
+
+ public void setStoppedState(IBinder token, boolean stopped) {
+ synchronized (mLock) {
+ int count = mViews.size();
+ for (int i = 0; i < count; i++) {
+ if (token == null || mParams.get(i).token == token) {
+ ViewRootImpl root = mRoots.get(i);
+ root.setWindowStopped(stopped);
+ }
+ }
+ }
+ }
+
+ public void reportNewConfiguration(Configuration config) {
+ synchronized (mLock) {
+ int count = mViews.size();
+ config = new Configuration(config);
+ for (int i=0; i < count; i++) {
+ ViewRootImpl root = mRoots.get(i);
+ root.requestUpdateConfiguration(config);
+ }
+ }
+ }
+
+ /** @hide */
+ public void changeCanvasOpacity(IBinder token, boolean opaque) {
+ if (token == null) {
+ return;
+ }
+ synchronized (mLock) {
+ for (int i = mParams.size() - 1; i >= 0; --i) {
+ if (mParams.get(i).token == token) {
+ mRoots.get(i).changeCanvasOpacity(opaque);
+ return;
+ }
+ }
+ }
+ }
+}
+
+final class WindowLeaked extends AndroidRuntimeException {
+ public WindowLeaked(String msg) {
+ super(msg);
+ }
+}
diff --git a/android/view/WindowManagerGlobal_Delegate.java b/android/view/WindowManagerGlobal_Delegate.java
new file mode 100644
index 00000000..2606e55e
--- /dev/null
+++ b/android/view/WindowManagerGlobal_Delegate.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link WindowManagerGlobal}
+ *
+ * Through the layoutlib_create tool, the original methods of WindowManagerGlobal have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class WindowManagerGlobal_Delegate {
+
+ private static IWindowManager sService;
+
+ @LayoutlibDelegate
+ public static IWindowManager getWindowManagerService() {
+ return sService;
+ }
+
+ // ---- internal implementation stuff ----
+
+ public static void setWindowManagerService(IWindowManager service) {
+ sService = service;
+ }
+}
diff --git a/android/view/WindowManagerImpl.java b/android/view/WindowManagerImpl.java
new file mode 100644
index 00000000..a8722f10
--- /dev/null
+++ b/android/view/WindowManagerImpl.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2006 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.content.Context;
+import android.graphics.Region;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.util.List;
+
+/**
+ * Provides low-level communication with the system window manager for
+ * operations that are bound to a particular context, display or parent window.
+ * Instances of this object are sensitive to the compatibility info associated
+ * with the running application.
+ *
+ * This object implements the {@link ViewManager} interface,
+ * allowing you to add any View subclass as a top-level window on the screen.
+ * Additional window manager specific layout parameters are defined for
+ * control over how windows are displayed. It also implements the {@link WindowManager}
+ * interface, allowing you to control the displays attached to the device.
+ *
+ * <p>Applications will not normally use WindowManager directly, instead relying
+ * on the higher-level facilities in {@link android.app.Activity} and
+ * {@link android.app.Dialog}.
+ *
+ * <p>Even for low-level window manager access, it is almost never correct to use
+ * this class. For example, {@link android.app.Activity#getWindowManager}
+ * provides a window manager for adding windows that are associated with that
+ * activity -- the window manager will not normally allow you to add arbitrary
+ * windows that are not associated with an activity.
+ *
+ * @see WindowManager
+ * @see WindowManagerGlobal
+ * @hide
+ */
+public final class WindowManagerImpl implements WindowManager {
+ private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
+ private final Context mContext;
+ private final Window mParentWindow;
+
+ private IBinder mDefaultToken;
+
+ public WindowManagerImpl(Context context) {
+ this(context, null);
+ }
+
+ private WindowManagerImpl(Context context, Window parentWindow) {
+ mContext = context;
+ mParentWindow = parentWindow;
+ }
+
+ public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
+ return new WindowManagerImpl(mContext, parentWindow);
+ }
+
+ public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
+ return new WindowManagerImpl(displayContext, mParentWindow);
+ }
+
+ /**
+ * Sets the window token to assign when none is specified by the client or
+ * available from the parent window.
+ *
+ * @param token The default token to assign.
+ */
+ public void setDefaultToken(IBinder token) {
+ mDefaultToken = token;
+ }
+
+ @Override
+ public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
+ applyDefaultToken(params);
+ mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
+ }
+
+ @Override
+ public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
+ applyDefaultToken(params);
+ mGlobal.updateViewLayout(view, params);
+ }
+
+ private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
+ // Only use the default token if we don't have a parent window.
+ if (mDefaultToken != null && mParentWindow == null) {
+ if (!(params instanceof WindowManager.LayoutParams)) {
+ throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+ }
+
+ // Only use the default token if we don't already have a token.
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+ if (wparams.token == null) {
+ wparams.token = mDefaultToken;
+ }
+ }
+ }
+
+ @Override
+ public void removeView(View view) {
+ mGlobal.removeView(view, false);
+ }
+
+ @Override
+ public void removeViewImmediate(View view) {
+ mGlobal.removeView(view, true);
+ }
+
+ @Override
+ public void requestAppKeyboardShortcuts(
+ final KeyboardShortcutsReceiver receiver, int deviceId) {
+ IResultReceiver resultReceiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) throws RemoteException {
+ List<KeyboardShortcutGroup> result =
+ resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);
+ receiver.onKeyboardShortcutsReceived(result);
+ }
+ };
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .requestAppKeyboardShortcuts(resultReceiver, deviceId);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public Display getDefaultDisplay() {
+ return mContext.getDisplay();
+ }
+
+ @Override
+ public Region getCurrentImeTouchRegion() {
+ try {
+ return WindowManagerGlobal.getWindowManagerService().getCurrentImeTouchRegion();
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+}
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
new file mode 100644
index 00000000..98f8dc8e
--- /dev/null
+++ b/android/view/WindowManagerInternal.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2014 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 android.graphics.Region;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.view.animation.Animation;
+
+import java.util.List;
+
+/**
+ * Window manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class WindowManagerInternal {
+
+ /**
+ * Interface to receive a callback when the windows reported for
+ * accessibility changed.
+ */
+ public interface WindowsForAccessibilityCallback {
+
+ /**
+ * Called when the windows for accessibility changed.
+ *
+ * @param windows The windows for accessibility.
+ */
+ public void onWindowsForAccessibilityChanged(List<WindowInfo> windows);
+ }
+
+ /**
+ * Callbacks for contextual changes that affect the screen magnification
+ * feature.
+ */
+ public interface MagnificationCallbacks {
+
+ /**
+ * Called when the region where magnification operates changes. Note that this isn't the
+ * entire screen. For example, IMEs are not magnified.
+ *
+ * @param magnificationRegion the current magnification region
+ */
+ public void onMagnificationRegionChanged(Region magnificationRegion);
+
+ /**
+ * Called when an application requests a rectangle on the screen to allow
+ * the client to apply the appropriate pan and scale.
+ *
+ * @param left The rectangle left.
+ * @param top The rectangle top.
+ * @param right The rectangle right.
+ * @param bottom The rectangle bottom.
+ */
+ public void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
+
+ /**
+ * Notifies that the rotation changed.
+ *
+ * @param rotation The current rotation.
+ */
+ public void onRotationChanged(int rotation);
+
+ /**
+ * Notifies that the context of the user changed. For example, an application
+ * was started.
+ */
+ public void onUserContextChanged();
+ }
+
+ /**
+ * Abstract class to be notified about {@link com.android.server.wm.AppTransition} events. Held
+ * as an abstract class so a listener only needs to implement the methods of its interest.
+ */
+ public static abstract class AppTransitionListener {
+
+ /**
+ * Called when an app transition is being setup and about to be executed.
+ */
+ public void onAppTransitionPendingLocked() {}
+
+ /**
+ * Called when a pending app transition gets cancelled.
+ *
+ * @param transit transition type indicating what kind of transition got cancelled
+ */
+ public void onAppTransitionCancelledLocked(int transit) {}
+
+ /**
+ * Called when an app transition gets started
+ *
+ * @param transit transition type indicating what kind of transition gets run, must be one
+ * of AppTransition.TRANSIT_* values
+ * @param openToken the token for the opening app
+ * @param closeToken the token for the closing app
+ * @param openAnimation the animation for the opening app
+ * @param closeAnimation the animation for the closing app
+ *
+ * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
+ * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
+ * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
+ * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
+ */
+ public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
+ Animation openAnimation, Animation closeAnimation) {
+ return 0;
+ }
+
+ /**
+ * Called when an app transition is finished running.
+ *
+ * @param token the token for app whose transition has finished
+ */
+ public void onAppTransitionFinishedLocked(IBinder token) {}
+ }
+
+ /**
+ * An interface to be notified about hardware keyboard status.
+ */
+ public interface OnHardKeyboardStatusChangeListener {
+ public void onHardKeyboardStatusChange(boolean available);
+ }
+
+ /**
+ * Request that the window manager call
+ * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
+ * within a surface transaction at a later time.
+ */
+ public abstract void requestTraversalFromDisplayManager();
+
+ /**
+ * Set by the accessibility layer to observe changes in the magnified region,
+ * rotation, and other window transformations related to display magnification
+ * as the window manager is responsible for doing the actual magnification
+ * and has access to the raw window data while the accessibility layer serves
+ * as a controller.
+ *
+ * @param callbacks The callbacks to invoke.
+ */
+ public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
+
+ /**
+ * Set by the accessibility layer to specify the magnification and panning to
+ * be applied to all windows that should be magnified.
+ *
+ * @param spec The MagnficationSpec to set.
+ *
+ * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ */
+ public abstract void setMagnificationSpec(MagnificationSpec spec);
+
+ /**
+ * Set by the accessibility framework to indicate whether the magnifiable regions of the display
+ * should be shown.
+ *
+ * @param show {@code true} to show magnifiable region bounds, {@code false} to hide
+ */
+ public abstract void setForceShowMagnifiableBounds(boolean show);
+
+ /**
+ * Obtains the magnification regions.
+ *
+ * @param magnificationRegion the current magnification region
+ */
+ public abstract void getMagnificationRegion(@NonNull Region magnificationRegion);
+
+ /**
+ * Gets the magnification and translation applied to a window given its token.
+ * Not all windows are magnified and the window manager policy determines which
+ * windows are magnified. The returned result also takes into account the compat
+ * scale if necessary.
+ *
+ * @param windowToken The window's token.
+ *
+ * @return The magnification spec for the window.
+ *
+ * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ */
+ public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
+ IBinder windowToken);
+
+ /**
+ * Sets a callback for observing which windows are touchable for the purposes
+ * of accessibility.
+ *
+ * @param callback The callback.
+ */
+ public abstract void setWindowsForAccessibilityCallback(
+ WindowsForAccessibilityCallback callback);
+
+ /**
+ * Sets a filter for manipulating the input event stream.
+ *
+ * @param filter The filter implementation.
+ */
+ public abstract void setInputFilter(IInputFilter filter);
+
+ /**
+ * Gets the token of the window that has input focus.
+ *
+ * @return The token.
+ */
+ public abstract IBinder getFocusedWindowToken();
+
+ /**
+ * @return Whether the keyguard is engaged.
+ */
+ public abstract boolean isKeyguardLocked();
+
+ /** @return {@code true} if the keyguard is going away. */
+ public abstract boolean isKeyguardGoingAway();
+
+ /**
+ * @return Whether the keyguard is showing and not occluded.
+ */
+ public abstract boolean isKeyguardShowingAndNotOccluded();
+
+ /**
+ * Gets the frame of a window given its token.
+ *
+ * @param token The token.
+ * @param outBounds The frame to populate.
+ */
+ public abstract void getWindowFrame(IBinder token, Rect outBounds);
+
+ /**
+ * Opens the global actions dialog.
+ */
+ public abstract void showGlobalActions();
+
+ /**
+ * Invalidate all visible windows. Then report back on the callback once all windows have
+ * redrawn.
+ */
+ public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout);
+
+ /**
+ * Adds a window token for a given window type.
+ *
+ * @param token The token to add.
+ * @param type The window type.
+ * @param displayId The display to add the token to.
+ */
+ public abstract void addWindowToken(android.os.IBinder token, int type, int displayId);
+
+ /**
+ * Removes a window token.
+ *
+ * @param token The toke to remove.
+ * @param removeWindows Whether to also remove the windows associated with the token.
+ * @param displayId The display to remove the token from.
+ */
+ public abstract void removeWindowToken(android.os.IBinder token, boolean removeWindows,
+ int displayId);
+
+ /**
+ * Registers a listener to be notified about app transition events.
+ *
+ * @param listener The listener to register.
+ */
+ public abstract void registerAppTransitionListener(AppTransitionListener listener);
+
+ /**
+ * Retrieves a height of input method window.
+ */
+ public abstract int getInputMethodWindowVisibleHeight();
+
+ /**
+ * Saves last input method window for transition.
+ *
+ * Note that it is assumed that this method is called only by InputMethodManagerService.
+ */
+ public abstract void saveLastInputMethodWindowForTransition();
+
+ /**
+ * Clears last input method window for transition.
+ *
+ * Note that it is assumed that this method is called only by InputMethodManagerService.
+ */
+ public abstract void clearLastInputMethodWindowForTransition();
+
+ /**
+ * Notifies WindowManagerService that the current IME window status is being changed.
+ *
+ * <p>Only {@link com.android.server.InputMethodManagerService} is the expected and tested
+ * caller of this method.</p>
+ *
+ * @param imeToken token to track the active input method. Corresponding IME windows can be
+ * identified by checking {@link android.view.WindowManager.LayoutParams#token}.
+ * Note that there is no guarantee that the corresponding window is already
+ * created
+ * @param imeWindowVisible whether the active IME thinks that its window should be visible or
+ * hidden, no matter how WindowManagerService will react / has reacted
+ * to corresponding API calls. Note that this state is not guaranteed
+ * to be synchronized with state in WindowManagerService.
+ * @param dismissImeOnBackKeyPressed {@code true} if the software keyboard is shown and the back
+ * key is expected to dismiss the software keyboard.
+ * @param targetWindowToken token to identify the target window that the IME is associated with.
+ * {@code null} when application, system, or the IME itself decided to
+ * change its window visibility before being associated with any target
+ * window.
+ */
+ public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
+ boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed,
+ @Nullable IBinder targetWindowToken);
+
+ /**
+ * Returns true when the hardware keyboard is available.
+ */
+ public abstract boolean isHardKeyboardAvailable();
+
+ /**
+ * Sets the callback listener for hardware keyboard status changes.
+ *
+ * @param listener The listener to set.
+ */
+ public abstract void setOnHardKeyboardStatusChangeListener(
+ OnHardKeyboardStatusChangeListener listener);
+
+ /** Returns true if the stack with the input Id is currently visible. */
+ public abstract boolean isStackVisible(int stackId);
+
+ /**
+ * @return True if and only if the docked divider is currently in resize mode.
+ */
+ public abstract boolean isDockedDividerResizing();
+
+ /**
+ * Requests the window manager to recompute the windows for accessibility.
+ */
+ public abstract void computeWindowsForAccessibility();
+
+ /**
+ * Called after virtual display Id is updated by
+ * {@link com.android.server.vr.Vr2dDisplay} with a specific
+ * {@param vr2dDisplayId}.
+ */
+ public abstract void setVr2dDisplayId(int vr2dDisplayId);
+}
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
new file mode 100644
index 00000000..66506a18
--- /dev/null
+++ b/android/view/WindowManagerPolicy.java
@@ -0,0 +1,1753 @@
+/*
+ * Copyright (C) 2006 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 static android.Manifest.permission;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.ActivityManager.StackId;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.animation.Animation;
+
+import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.IShortcutService;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This interface supplies all UI-specific behavior of the window manager. An
+ * instance of it is created by the window manager when it starts up, and allows
+ * customization of window layering, special window types, key dispatching, and
+ * layout.
+ *
+ * <p>Because this provides deep interaction with the system window manager,
+ * specific methods on this interface can be called from a variety of contexts
+ * with various restrictions on what they can do. These are encoded through
+ * a suffixes at the end of a method encoding the thread the method is called
+ * from and any locks that are held when it is being called; if no suffix
+ * is attached to a method, then it is not called with any locks and may be
+ * called from the main window manager thread or another thread calling into
+ * the window manager.
+ *
+ * <p>The current suffixes are:
+ *
+ * <dl>
+ * <dt> Ti <dd> Called from the input thread. This is the thread that
+ * collects pending input events and dispatches them to the appropriate window.
+ * It may block waiting for events to be processed, so that the input stream is
+ * properly serialized.
+ * <dt> Tq <dd> Called from the low-level input queue thread. This is the
+ * thread that reads events out of the raw input devices and places them
+ * into the global input queue that is read by the <var>Ti</var> thread.
+ * This thread should not block for a long period of time on anything but the
+ * key driver.
+ * <dt> Lw <dd> Called with the main window manager lock held. Because the
+ * window manager is a very low-level system service, there are few other
+ * system services you can call with this lock held. It is explicitly okay to
+ * make calls into the package manager and power manager; it is explicitly not
+ * okay to make calls into the activity manager or most other services. Note that
+ * {@link android.content.Context#checkPermission(String, int, int)} and
+ * variations require calling into the activity manager.
+ * <dt> Li <dd> Called with the input thread lock held. This lock can be
+ * acquired by the window manager while it holds the window lock, so this is
+ * even more restrictive than <var>Lw</var>.
+ * </dl>
+ *
+ * @hide
+ */
+public interface WindowManagerPolicy {
+ // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h.
+ public final static int FLAG_WAKE = 0x00000001;
+ public final static int FLAG_VIRTUAL = 0x00000002;
+
+ public final static int FLAG_INJECTED = 0x01000000;
+ public final static int FLAG_TRUSTED = 0x02000000;
+ public final static int FLAG_FILTERED = 0x04000000;
+ public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000;
+
+ public final static int FLAG_INTERACTIVE = 0x20000000;
+ public final static int FLAG_PASS_TO_USER = 0x40000000;
+
+ // Flags for IActivityManager.keyguardGoingAway()
+ public final static int KEYGUARD_GOING_AWAY_FLAG_TO_SHADE = 1 << 0;
+ public final static int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1;
+ public final static int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2;
+
+ // Flags used for indicating whether the internal and/or external input devices
+ // of some type are available.
+ public final static int PRESENCE_INTERNAL = 1 << 0;
+ public final static int PRESENCE_EXTERNAL = 1 << 1;
+
+ // Navigation bar position values
+ int NAV_BAR_LEFT = 1 << 0;
+ int NAV_BAR_RIGHT = 1 << 1;
+ int NAV_BAR_BOTTOM = 1 << 2;
+
+ public final static boolean WATCH_POINTER = false;
+
+ /**
+ * Sticky broadcast of the current HDMI plugged state.
+ */
+ public final static String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED";
+
+ /**
+ * Extra in {@link #ACTION_HDMI_PLUGGED} indicating the state: true if
+ * plugged in to HDMI, false if not.
+ */
+ public final static String EXTRA_HDMI_PLUGGED_STATE = "state";
+
+ /**
+ * Set to {@code true} when intent was invoked from pressing the home key.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
+
+ /**
+ * Pass this event to the user / app. To be returned from
+ * {@link #interceptKeyBeforeQueueing}.
+ */
+ public final static int ACTION_PASS_TO_USER = 0x00000001;
+
+ /**
+ * Register shortcuts for window manager to dispatch.
+ * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode
+ * @hide
+ */
+ void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException;
+
+ /**
+ * Called when the Keyguard occluded state changed.
+ * @param occluded Whether Keyguard is currently occluded or not.
+ */
+ void onKeyguardOccludedChangedLw(boolean occluded);
+
+ /**
+ * Interface to the Window Manager state associated with a particular
+ * window. You can hold on to an instance of this interface from the call
+ * to prepareAddWindow() until removeWindow().
+ */
+ public interface WindowState {
+ /**
+ * Return the uid of the app that owns this window.
+ */
+ int getOwningUid();
+
+ /**
+ * Return the package name of the app that owns this window.
+ */
+ String getOwningPackage();
+
+ /**
+ * Perform standard frame computation. The result can be obtained with
+ * getFrame() if so desired. Must be called with the window manager
+ * lock held.
+ *
+ * @param parentFrame The frame of the parent container this window
+ * is in, used for computing its basic position.
+ * @param displayFrame The frame of the overall display in which this
+ * window can appear, used for constraining the overall dimensions
+ * of the window.
+ * @param overlayFrame The frame within the display that is inside
+ * of the overlay region.
+ * @param contentFrame The frame within the display in which we would
+ * like active content to appear. This will cause windows behind to
+ * be resized to match the given content frame.
+ * @param visibleFrame The frame within the display that the window
+ * is actually visible, used for computing its visible insets to be
+ * given to windows behind.
+ * This can be used as a hint for scrolling (avoiding resizing)
+ * the window to make certain that parts of its content
+ * are visible.
+ * @param decorFrame The decor frame specified by policy specific to this window,
+ * to use for proper cropping during animation.
+ * @param stableFrame The frame around which stable system decoration is positioned.
+ * @param outsetFrame The frame that includes areas that aren't part of the surface but we
+ * want to treat them as such.
+ */
+ public void computeFrameLw(Rect parentFrame, Rect displayFrame,
+ Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame,
+ Rect stableFrame, Rect outsetFrame);
+
+ /**
+ * Retrieve the current frame of the window that has been assigned by
+ * the window manager. Must be called with the window manager lock held.
+ *
+ * @return Rect The rectangle holding the window frame.
+ */
+ public Rect getFrameLw();
+
+ /**
+ * Retrieve the current position of the window that is actually shown.
+ * Must be called with the window manager lock held.
+ *
+ * @return Point The point holding the shown window position.
+ */
+ public Point getShownPositionLw();
+
+ /**
+ * Retrieve the frame of the display that this window was last
+ * laid out in. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the display frame.
+ */
+ public Rect getDisplayFrameLw();
+
+ /**
+ * Retrieve the frame of the area inside the overscan region of the
+ * display that this window was last laid out in. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the display overscan frame.
+ */
+ public Rect getOverscanFrameLw();
+
+ /**
+ * Retrieve the frame of the content area that this window was last
+ * laid out in. This is the area in which the content of the window
+ * should be placed. It will be smaller than the display frame to
+ * account for screen decorations such as a status bar or soft
+ * keyboard. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the content frame.
+ */
+ public Rect getContentFrameLw();
+
+ /**
+ * Retrieve the frame of the visible area that this window was last
+ * laid out in. This is the area of the screen in which the window
+ * will actually be fully visible. It will be smaller than the
+ * content frame to account for transient UI elements blocking it
+ * such as an input method's candidates UI. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the visible frame.
+ */
+ public Rect getVisibleFrameLw();
+
+ /**
+ * Returns true if this window is waiting to receive its given
+ * internal insets from the client app, and so should not impact the
+ * layout of other windows.
+ */
+ public boolean getGivenInsetsPendingLw();
+
+ /**
+ * Retrieve the insets given by this window's client for the content
+ * area of windows behind it. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The left, top, right, and bottom insets, relative
+ * to the window's frame, of the actual contents.
+ */
+ public Rect getGivenContentInsetsLw();
+
+ /**
+ * Retrieve the insets given by this window's client for the visible
+ * area of windows behind it. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The left, top, right, and bottom insets, relative
+ * to the window's frame, of the actual visible area.
+ */
+ public Rect getGivenVisibleInsetsLw();
+
+ /**
+ * Retrieve the current LayoutParams of the window.
+ *
+ * @return WindowManager.LayoutParams The window's internal LayoutParams
+ * instance.
+ */
+ public WindowManager.LayoutParams getAttrs();
+
+ /**
+ * Return whether this window needs the menu key shown. Must be called
+ * with window lock held, because it may need to traverse down through
+ * window list to determine the result.
+ * @param bottom The bottom-most window to consider when determining this.
+ */
+ public boolean getNeedsMenuLw(WindowState bottom);
+
+ /**
+ * Retrieve the current system UI visibility flags associated with
+ * this window.
+ */
+ public int getSystemUiVisibility();
+
+ /**
+ * Get the layer at which this window's surface will be Z-ordered.
+ */
+ public int getSurfaceLayer();
+
+ /**
+ * Retrieve the type of the top-level window.
+ *
+ * @return the base type of the parent window if attached or its own type otherwise
+ */
+ public int getBaseType();
+
+ /**
+ * Return the token for the application (actually activity) that owns
+ * this window. May return null for system windows.
+ *
+ * @return An IApplicationToken identifying the owning activity.
+ */
+ public IApplicationToken getAppToken();
+
+ /**
+ * Return true if this window is participating in voice interaction.
+ */
+ public boolean isVoiceInteraction();
+
+ /**
+ * Return true if, at any point, the application token associated with
+ * this window has actually displayed any windows. This is most useful
+ * with the "starting up" window to determine if any windows were
+ * displayed when it is closed.
+ *
+ * @return Returns true if one or more windows have been displayed,
+ * else false.
+ */
+ public boolean hasAppShownWindows();
+
+ /**
+ * Is this window visible? It is not visible if there is no
+ * surface, or we are in the process of running an exit animation
+ * that will remove the surface.
+ */
+ boolean isVisibleLw();
+
+ /**
+ * Is this window currently visible to the user on-screen? It is
+ * displayed either if it is visible or it is currently running an
+ * animation before no longer being visible. Must be called with the
+ * window manager lock held.
+ */
+ boolean isDisplayedLw();
+
+ /**
+ * Return true if this window (or a window it is attached to, but not
+ * considering its app token) is currently animating.
+ */
+ boolean isAnimatingLw();
+
+ /**
+ * @return Whether the window can affect SystemUI flags, meaning that SystemUI (system bars,
+ * for example) will be affected by the flags specified in this window. This is the
+ * case when the surface is on screen but not exiting.
+ */
+ boolean canAffectSystemUiFlags();
+
+ /**
+ * Is this window considered to be gone for purposes of layout?
+ */
+ boolean isGoneForLayoutLw();
+
+ /**
+ * Returns true if the window has a surface that it has drawn a
+ * complete UI in to. Note that this is different from {@link #hasDrawnLw()}
+ * in that it also returns true if the window is READY_TO_SHOW, but was not yet
+ * promoted to HAS_DRAWN.
+ */
+ boolean isDrawnLw();
+
+ /**
+ * Returns true if this window has been shown on screen at some time in
+ * the past. Must be called with the window manager lock held.
+ */
+ public boolean hasDrawnLw();
+
+ /**
+ * Can be called by the policy to force a window to be hidden,
+ * regardless of whether the client or window manager would like
+ * it shown. Must be called with the window manager lock held.
+ * Returns true if {@link #showLw} was last called for the window.
+ */
+ public boolean hideLw(boolean doAnimation);
+
+ /**
+ * Can be called to undo the effect of {@link #hideLw}, allowing a
+ * window to be shown as long as the window manager and client would
+ * also like it to be shown. Must be called with the window manager
+ * lock held.
+ * Returns true if {@link #hideLw} was last called for the window.
+ */
+ public boolean showLw(boolean doAnimation);
+
+ /**
+ * Check whether the process hosting this window is currently alive.
+ */
+ public boolean isAlive();
+
+ /**
+ * Check if window is on {@link Display#DEFAULT_DISPLAY}.
+ * @return true if window is on default display.
+ */
+ public boolean isDefaultDisplay();
+
+ /**
+ * Check whether the window is currently dimming.
+ */
+ public boolean isDimming();
+
+ /**
+ * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if
+ * not attached to any stack.
+ */
+ int getStackId();
+
+ /**
+ * Returns true if the window is current in multi-windowing mode. i.e. it shares the
+ * screen with other application windows.
+ */
+ public boolean isInMultiWindowMode();
+
+ public int getRotationAnimationHint();
+
+ public boolean isInputMethodWindow();
+
+ public int getDisplayId();
+
+ /**
+ * Returns true if the window owner can add internal system windows.
+ * That is, they have {@link permission#INTERNAL_SYSTEM_WINDOW}.
+ */
+ default boolean canAddInternalSystemWindow() {
+ return false;
+ }
+
+ /**
+ * Returns true if the window owner has the permission to acquire a sleep token when it's
+ * visible. That is, they have the permission {@link permission#DEVICE_POWER}.
+ */
+ boolean canAcquireSleepToken();
+ }
+
+ /**
+ * Representation of a input consumer that the policy has added to the
+ * window manager to consume input events going to windows below it.
+ */
+ public interface InputConsumer {
+ /**
+ * Remove the input consumer from the window manager.
+ */
+ void dismiss();
+ }
+
+ /**
+ * Holds the contents of a starting window. {@link #addSplashScreen} needs to wrap the
+ * contents of the starting window into an class implementing this interface, which then will be
+ * held by WM and released with {@link #remove} when no longer needed.
+ */
+ interface StartingSurface {
+
+ /**
+ * Removes the starting window surface. Do not hold the window manager lock when calling
+ * this method!
+ */
+ void remove();
+ }
+
+ /**
+ * Interface for calling back in to the window manager that is private
+ * between it and the policy.
+ */
+ public interface WindowManagerFuncs {
+ public static final int LID_ABSENT = -1;
+ public static final int LID_CLOSED = 0;
+ public static final int LID_OPEN = 1;
+
+ public static final int CAMERA_LENS_COVER_ABSENT = -1;
+ public static final int CAMERA_LENS_UNCOVERED = 0;
+ public static final int CAMERA_LENS_COVERED = 1;
+
+ /**
+ * Ask the window manager to re-evaluate the system UI flags.
+ */
+ public void reevaluateStatusBarVisibility();
+
+ /**
+ * Add a input consumer which will consume all input events going to any window below it.
+ */
+ public InputConsumer createInputConsumer(Looper looper, String name,
+ InputEventReceiver.Factory inputEventReceiverFactory);
+
+ /**
+ * Returns a code that describes the current state of the lid switch.
+ */
+ public int getLidState();
+
+ /**
+ * Lock the device now.
+ */
+ public void lockDeviceNow();
+
+ /**
+ * Returns a code that descripbes whether the camera lens is covered or not.
+ */
+ public int getCameraLensCoverState();
+
+ /**
+ * Switch the input method, to be precise, input method subtype.
+ *
+ * @param forwardDirection {@code true} to rotate in a forward direction.
+ */
+ public void switchInputMethod(boolean forwardDirection);
+
+ public void shutdown(boolean confirm);
+ public void reboot(boolean confirm);
+ public void rebootSafeMode(boolean confirm);
+
+ /**
+ * Return the window manager lock needed to correctly call "Lw" methods.
+ */
+ public Object getWindowManagerLock();
+
+ /** Register a system listener for touch events */
+ void registerPointerEventListener(PointerEventListener listener);
+
+ /** Unregister a system listener for touch events */
+ void unregisterPointerEventListener(PointerEventListener listener);
+
+ /**
+ * @return The content insets of the docked divider window.
+ */
+ int getDockedDividerInsetsLw();
+
+ /**
+ * Retrieves the {@param outBounds} from the stack with id {@param stackId}.
+ */
+ void getStackBounds(int stackId, Rect outBounds);
+
+ /**
+ * Notifies window manager that {@link #isShowingDreamLw} has changed.
+ */
+ void notifyShowingDreamChanged();
+
+ /**
+ * @return The currently active input method window.
+ */
+ WindowState getInputMethodWindowLw();
+
+ /**
+ * Notifies window manager that {@link #isKeyguardTrustedLw} has changed.
+ */
+ void notifyKeyguardTrustedChanged();
+
+ /**
+ * Notifies the window manager that screen is being turned off.
+ *
+ * @param listener callback to call when display can be turned off
+ */
+ void screenTurningOff(ScreenOffListener listener);
+ }
+
+ public interface PointerEventListener {
+ /**
+ * 1. onPointerEvent will be called on the service.UiThread.
+ * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a
+ * copy() must be made and the copy must be recycled.
+ **/
+ void onPointerEvent(MotionEvent motionEvent);
+
+ /**
+ * @see #onPointerEvent(MotionEvent)
+ **/
+ default void onPointerEvent(MotionEvent motionEvent, int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ onPointerEvent(motionEvent);
+ }
+ }
+ }
+
+ /** Window has been added to the screen. */
+ public static final int TRANSIT_ENTER = 1;
+ /** Window has been removed from the screen. */
+ public static final int TRANSIT_EXIT = 2;
+ /** Window has been made visible. */
+ public static final int TRANSIT_SHOW = 3;
+ /** Window has been made invisible.
+ * TODO: Consider removal as this is unused. */
+ public static final int TRANSIT_HIDE = 4;
+ /** The "application starting" preview window is no longer needed, and will
+ * animate away to show the real window. */
+ public static final int TRANSIT_PREVIEW_DONE = 5;
+
+ // NOTE: screen off reasons are in order of significance, with more
+ // important ones lower than less important ones.
+
+ /** Screen turned off because of a device admin */
+ public final int OFF_BECAUSE_OF_ADMIN = 1;
+ /** Screen turned off because of power button */
+ public final int OFF_BECAUSE_OF_USER = 2;
+ /** Screen turned off because of timeout */
+ public final int OFF_BECAUSE_OF_TIMEOUT = 3;
+
+ /** @hide */
+ @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserRotationMode {}
+
+ /** When not otherwise specified by the activity's screenOrientation, rotation should be
+ * determined by the system (that is, using sensors). */
+ public final int USER_ROTATION_FREE = 0;
+ /** When not otherwise specified by the activity's screenOrientation, rotation is set by
+ * the user. */
+ public final int USER_ROTATION_LOCKED = 1;
+
+ /**
+ * Perform initialization of the policy.
+ *
+ * @param context The system context we are running in.
+ */
+ public void init(Context context, IWindowManager windowManager,
+ WindowManagerFuncs windowManagerFuncs);
+
+ /**
+ * @return true if com.android.internal.R.bool#config_forceDefaultOrientation is true.
+ */
+ public boolean isDefaultOrientationForced();
+
+ /**
+ * Called by window manager once it has the initial, default native
+ * display dimensions.
+ */
+ public void setInitialDisplaySize(Display display, int width, int height, int density);
+
+ /**
+ * Called by window manager to set the overscan region that should be used for the
+ * given display.
+ */
+ public void setDisplayOverscan(Display display, int left, int top, int right, int bottom);
+
+ /**
+ * Check permissions when adding a window.
+ *
+ * @param attrs The window's LayoutParams.
+ * @param outAppOp First element will be filled with the app op corresponding to
+ * this window, or OP_NONE.
+ *
+ * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
+ * else an error code, usually
+ * {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
+ */
+ public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
+
+ /**
+ * Check permissions when adding a window.
+ *
+ * @param attrs The window's LayoutParams.
+ *
+ * @return True if the window may only be shown to the current user, false if the window can
+ * be shown on all users' windows.
+ */
+ public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs);
+
+ /**
+ * Sanitize the layout parameters coming from a client. Allows the policy
+ * to do things like ensure that windows of a specific type can't take
+ * input focus.
+ *
+ * @param attrs The window layout parameters to be modified. These values
+ * are modified in-place.
+ */
+ public void adjustWindowParamsLw(WindowManager.LayoutParams attrs);
+
+ /**
+ * After the window manager has computed the current configuration based
+ * on its knowledge of the display and input devices, it gives the policy
+ * a chance to adjust the information contained in it. If you want to
+ * leave it as-is, simply do nothing.
+ *
+ * <p>This method may be called by any thread in the window manager, but
+ * no internal locks in the window manager will be held.
+ *
+ * @param config The Configuration being computed, for you to change as
+ * desired.
+ * @param keyboardPresence Flags that indicate whether internal or external
+ * keyboards are present.
+ * @param navigationPresence Flags that indicate whether internal or external
+ * navigation devices are present.
+ */
+ public void adjustConfigurationLw(Configuration config, int keyboardPresence,
+ int navigationPresence);
+
+ /**
+ * Returns the layer assignment for the window state. Allows you to control how different
+ * kinds of windows are ordered on-screen.
+ *
+ * @param win The window state
+ * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+ */
+ default int getWindowLayerLw(WindowState win) {
+ return getWindowLayerFromTypeLw(win.getBaseType(), win.canAddInternalSystemWindow());
+ }
+
+ /**
+ * Returns the layer assignment for the window type. Allows you to control how different
+ * kinds of windows are ordered on-screen.
+ *
+ * @param type The type of window being assigned.
+ * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+ */
+ default int getWindowLayerFromTypeLw(int type) {
+ if (isSystemAlertWindowType(type)) {
+ throw new IllegalArgumentException("Use getWindowLayerFromTypeLw() or"
+ + " getWindowLayerLw() for alert window types");
+ }
+ return getWindowLayerFromTypeLw(type, false /* canAddInternalSystemWindow */);
+ }
+
+ /**
+ * Returns the layer assignment for the window type. Allows you to control how different
+ * kinds of windows are ordered on-screen.
+ *
+ * @param type The type of window being assigned.
+ * @param canAddInternalSystemWindow If the owner window associated with the type we are
+ * evaluating can add internal system windows. I.e they have
+ * {@link permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window
+ * types {@link android.view.WindowManager.LayoutParams#isSystemAlertWindowType(int)}
+ * can be assigned layers greater than the layer for
+ * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} Else, their
+ * layers would be lesser.
+ * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+ */
+ default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
+ if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+ return APPLICATION_LAYER;
+ }
+
+ switch (type) {
+ case TYPE_WALLPAPER:
+ // wallpaper is at the bottom, though the window manager may move it.
+ return 1;
+ case TYPE_PRESENTATION:
+ case TYPE_PRIVATE_PRESENTATION:
+ return APPLICATION_LAYER;
+ case TYPE_DOCK_DIVIDER:
+ return APPLICATION_LAYER;
+ case TYPE_QS_DIALOG:
+ return APPLICATION_LAYER;
+ case TYPE_PHONE:
+ return 3;
+ case TYPE_SEARCH_BAR:
+ case TYPE_VOICE_INTERACTION_STARTING:
+ return 4;
+ case TYPE_VOICE_INTERACTION:
+ // voice interaction layer is almost immediately above apps.
+ return 5;
+ case TYPE_INPUT_CONSUMER:
+ return 6;
+ case TYPE_SYSTEM_DIALOG:
+ return 7;
+ case TYPE_TOAST:
+ // toasts and the plugged-in battery thing
+ return 8;
+ case TYPE_PRIORITY_PHONE:
+ // SIM errors and unlock. Not sure if this really should be in a high layer.
+ return 9;
+ case TYPE_SYSTEM_ALERT:
+ // like the ANR / app crashed dialogs
+ return canAddInternalSystemWindow ? 11 : 10;
+ case TYPE_APPLICATION_OVERLAY:
+ return 12;
+ case TYPE_DREAM:
+ // used for Dreams (screensavers with TYPE_DREAM windows)
+ return 13;
+ case TYPE_INPUT_METHOD:
+ // on-screen keyboards and other such input method user interfaces go here.
+ return 14;
+ case TYPE_INPUT_METHOD_DIALOG:
+ // on-screen keyboards and other such input method user interfaces go here.
+ return 15;
+ case TYPE_STATUS_BAR_SUB_PANEL:
+ return 17;
+ case TYPE_STATUS_BAR:
+ return 18;
+ case TYPE_STATUS_BAR_PANEL:
+ return 19;
+ case TYPE_KEYGUARD_DIALOG:
+ return 20;
+ case TYPE_VOLUME_OVERLAY:
+ // the on-screen volume indicator and controller shown when the user
+ // changes the device volume
+ return 21;
+ case TYPE_SYSTEM_OVERLAY:
+ // the on-screen volume indicator and controller shown when the user
+ // changes the device volume
+ return canAddInternalSystemWindow ? 22 : 11;
+ case TYPE_NAVIGATION_BAR:
+ // the navigation bar, if available, shows atop most things
+ return 23;
+ case TYPE_NAVIGATION_BAR_PANEL:
+ // some panels (e.g. search) need to show on top of the navigation bar
+ return 24;
+ case TYPE_SCREENSHOT:
+ // screenshot selection layer shouldn't go above system error, but it should cover
+ // navigation bars at the very least.
+ return 25;
+ case TYPE_SYSTEM_ERROR:
+ // system-level error dialogs
+ return canAddInternalSystemWindow ? 26 : 10;
+ case TYPE_MAGNIFICATION_OVERLAY:
+ // used to highlight the magnified portion of a display
+ return 27;
+ case TYPE_DISPLAY_OVERLAY:
+ // used to simulate secondary display devices
+ return 28;
+ case TYPE_DRAG:
+ // the drag layer: input for drag-and-drop is associated with this window,
+ // which sits above all other focusable windows
+ return 29;
+ case TYPE_ACCESSIBILITY_OVERLAY:
+ // overlay put by accessibility services to intercept user interaction
+ return 30;
+ case TYPE_SECURE_SYSTEM_OVERLAY:
+ return 31;
+ case TYPE_BOOT_PROGRESS:
+ return 32;
+ case TYPE_POINTER:
+ // the (mouse) pointer layer
+ return 33;
+ default:
+ Slog.e("WindowManager", "Unknown window type: " + type);
+ return APPLICATION_LAYER;
+ }
+ }
+
+ int APPLICATION_LAYER = 2;
+ int APPLICATION_MEDIA_SUBLAYER = -2;
+ int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
+ int APPLICATION_PANEL_SUBLAYER = 1;
+ int APPLICATION_SUB_PANEL_SUBLAYER = 2;
+ int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3;
+
+ /**
+ * Return how to Z-order sub-windows in relation to the window they are attached to.
+ * Return positive to have them ordered in front, negative for behind.
+ *
+ * @param type The sub-window type code.
+ *
+ * @return int Layer in relation to the attached window, where positive is
+ * above and negative is below.
+ */
+ default int getSubWindowLayerFromTypeLw(int type) {
+ switch (type) {
+ case TYPE_APPLICATION_PANEL:
+ case TYPE_APPLICATION_ATTACHED_DIALOG:
+ return APPLICATION_PANEL_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA:
+ return APPLICATION_MEDIA_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA_OVERLAY:
+ return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+ case TYPE_APPLICATION_SUB_PANEL:
+ return APPLICATION_SUB_PANEL_SUBLAYER;
+ case TYPE_APPLICATION_ABOVE_SUB_PANEL:
+ return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;
+ }
+ Slog.e("WindowManager", "Unknown sub-window type: " + type);
+ return 0;
+ }
+
+ /**
+ * Get the highest layer (actually one more than) that the wallpaper is
+ * allowed to be in.
+ */
+ public int getMaxWallpaperLayer();
+
+ /**
+ * Return the display width available after excluding any screen
+ * decorations that could never be removed in Honeycomb. That is, system bar or
+ * button bar.
+ */
+ public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return the display height available after excluding any screen
+ * decorations that could never be removed in Honeycomb. That is, system bar or
+ * button bar.
+ */
+ public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return the available screen width that we should report for the
+ * configuration. This must be no larger than
+ * {@link #getNonDecorDisplayWidth(int, int, int)}; it may be smaller than
+ * that to account for more transient decoration like a status bar.
+ */
+ public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return the available screen height that we should report for the
+ * configuration. This must be no larger than
+ * {@link #getNonDecorDisplayHeight(int, int, int)}; it may be smaller than
+ * that to account for more transient decoration like a status bar.
+ */
+ public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return whether the given window can become the Keyguard window. Typically returns true for
+ * the StatusBar.
+ */
+ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
+
+ /**
+ * @return whether {@param win} can be hidden by Keyguard
+ */
+ public boolean canBeHiddenByKeyguardLw(WindowState win);
+
+ /**
+ * Called when the system would like to show a UI to indicate that an
+ * application is starting. You can use this to add a
+ * APPLICATION_STARTING_TYPE window with the given appToken to the window
+ * manager (using the normal window manager APIs) that will be shown until
+ * the application displays its own window. This is called without the
+ * window manager locked so that you can call back into it.
+ *
+ * @param appToken Token of the application being started.
+ * @param packageName The name of the application package being started.
+ * @param theme Resource defining the application's overall visual theme.
+ * @param nonLocalizedLabel The default title label of the application if
+ * no data is found in the resource.
+ * @param labelRes The resource ID the application would like to use as its name.
+ * @param icon The resource ID the application would like to use as its icon.
+ * @param windowFlags Window layout flags.
+ * @param overrideConfig override configuration to consider when generating
+ * context to for resources.
+ * @param displayId Id of the display to show the splash screen at.
+ *
+ * @return The starting surface.
+ *
+ */
+ public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
+ CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
+ int logo, int windowFlags, Configuration overrideConfig, int displayId);
+
+ /**
+ * Prepare for a window being added to the window manager. You can throw an
+ * exception here to prevent the window being added, or do whatever setup
+ * you need to keep track of the window.
+ *
+ * @param win The window being added.
+ * @param attrs The window's LayoutParams.
+ *
+ * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed, else an
+ * error code to abort the add.
+ */
+ public int prepareAddWindowLw(WindowState win,
+ WindowManager.LayoutParams attrs);
+
+ /**
+ * Called when a window is being removed from a window manager. Must not
+ * throw an exception -- clean up as much as possible.
+ *
+ * @param win The window being removed.
+ */
+ public void removeWindowLw(WindowState win);
+
+ /**
+ * Control the animation to run when a window's state changes. Return a
+ * non-0 number to force the animation to a specific resource ID, or 0
+ * to use the default animation.
+ *
+ * @param win The window that is changing.
+ * @param transit What is happening to the window: {@link #TRANSIT_ENTER},
+ * {@link #TRANSIT_EXIT}, {@link #TRANSIT_SHOW}, or
+ * {@link #TRANSIT_HIDE}.
+ *
+ * @return Resource ID of the actual animation to use, or 0 for none.
+ */
+ public int selectAnimationLw(WindowState win, int transit);
+
+ /**
+ * Determine the animation to run for a rotation transition based on the
+ * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
+ * and whether it is currently fullscreen and frontmost.
+ *
+ * @param anim The exiting animation resource id is stored in anim[0], the
+ * entering animation resource id is stored in anim[1].
+ */
+ public void selectRotationAnimationLw(int anim[]);
+
+ /**
+ * Validate whether the current top fullscreen has specified the same
+ * {@link WindowManager.LayoutParams#rotationAnimation} value as that
+ * being passed in from the previous top fullscreen window.
+ *
+ * @param exitAnimId exiting resource id from the previous window.
+ * @param enterAnimId entering resource id from the previous window.
+ * @param forceDefault For rotation animations only, if true ignore the
+ * animation values and just return false.
+ * @return true if the previous values are still valid, false if they
+ * should be replaced with the default.
+ */
+ public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+ boolean forceDefault);
+
+ /**
+ * Create and return an animation to re-display a window that was force hidden by Keyguard.
+ */
+ public Animation createHiddenByKeyguardExit(boolean onWallpaper,
+ boolean goingToNotificationShade);
+
+ /**
+ * Create and return an animation to let the wallpaper disappear after being shown behind
+ * Keyguard.
+ */
+ public Animation createKeyguardWallpaperExit(boolean goingToNotificationShade);
+
+ /**
+ * Called from the input reader thread before a key is enqueued.
+ *
+ * <p>There are some actions that need to be handled here because they
+ * affect the power state of the device, for example, the power keys.
+ * Generally, it's best to keep as little as possible in the queue thread
+ * because it's the most fragile.
+ * @param event The key event.
+ * @param policyFlags The policy flags associated with the key.
+ *
+ * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
+ */
+ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
+
+ /**
+ * Called from the input reader thread before a motion is enqueued when the device is in a
+ * non-interactive state.
+ *
+ * <p>There are some actions that need to be handled here because they
+ * affect the power state of the device, for example, waking on motions.
+ * Generally, it's best to keep as little as possible in the queue thread
+ * because it's the most fragile.
+ * @param policyFlags The policy flags associated with the motion.
+ *
+ * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
+ */
+ public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags);
+
+ /**
+ * Called from the input dispatcher thread before a key is dispatched to a window.
+ *
+ * <p>Allows you to define
+ * behavior for keys that can not be overridden by applications.
+ * This method is called from the input thread, with no locks held.
+ *
+ * @param win The window that currently has focus. This is where the key
+ * event will normally go.
+ * @param event The key event.
+ * @param policyFlags The policy flags associated with the key.
+ * @return 0 if the key should be dispatched immediately, -1 if the key should
+ * not be dispatched ever, or a positive value indicating the number of
+ * milliseconds by which the key dispatch should be delayed before trying
+ * again.
+ */
+ public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags);
+
+ /**
+ * Called from the input dispatcher thread when an application did not handle
+ * a key that was dispatched to it.
+ *
+ * <p>Allows you to define default global behavior for keys that were not handled
+ * by applications. This method is called from the input thread, with no locks held.
+ *
+ * @param win The window that currently has focus. This is where the key
+ * event will normally go.
+ * @param event The key event.
+ * @param policyFlags The policy flags associated with the key.
+ * @return Returns an alternate key event to redispatch as a fallback, or null to give up.
+ * The caller is responsible for recycling the key event.
+ */
+ public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
+
+ /**
+ * Called when layout of the windows is about to start.
+ *
+ * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}.
+ * @param displayWidth The current full width of the screen.
+ * @param displayHeight The current full height of the screen.
+ * @param displayRotation The current rotation being applied to the base window.
+ * @param uiMode The current uiMode in configuration.
+ */
+ public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
+ int displayRotation, int uiMode);
+
+ /**
+ * Returns the bottom-most layer of the system decor, above which no policy decor should
+ * be applied.
+ */
+ public int getSystemDecorLayerLw();
+
+ /**
+ * Return the rectangle of the screen that is available for applications to run in.
+ * This will be called immediately after {@link #beginLayoutLw}.
+ *
+ * @param r The rectangle to be filled with the boundaries available to applications.
+ */
+ public void getContentRectLw(Rect r);
+
+ /**
+ * Called for each window attached to the window manager as layout is
+ * proceeding. The implementation of this function must take care of
+ * setting the window's frame, either here or in finishLayout().
+ *
+ * @param win The window being positioned.
+ * @param attached For sub-windows, the window it is attached to; this
+ * window will already have had layoutWindow() called on it
+ * so you can use its Rect. Otherwise null.
+ */
+ public void layoutWindowLw(WindowState win, WindowState attached);
+
+
+ /**
+ * Return the insets for the areas covered by system windows. These values
+ * are computed on the most recent layout, so they are not guaranteed to
+ * be correct.
+ *
+ * @param attrs The LayoutParams of the window.
+ * @param taskBounds The bounds of the task this window is on or {@code null} if no task is
+ * associated with the window.
+ * @param displayRotation Rotation of the display.
+ * @param displayWidth The width of the display.
+ * @param displayHeight The height of the display.
+ * @param outContentInsets The areas covered by system windows, expressed as positive insets.
+ * @param outStableInsets The areas covered by stable system windows irrespective of their
+ * current visibility. Expressed as positive insets.
+ * @param outOutsets The areas that are not real display, but we would like to treat as such.
+ * @return Whether to always consume the navigation bar.
+ * See {@link #isNavBarForcedShownLw(WindowState)}.
+ */
+ public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
+ int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
+ Rect outStableInsets, Rect outOutsets);
+
+ /**
+ * Called when layout of the windows is finished. After this function has
+ * returned, all windows given to layoutWindow() <em>must</em> have had a
+ * frame assigned.
+ */
+ public void finishLayoutLw();
+
+ /** Layout state may have changed (so another layout will be performed) */
+ static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
+ /** Configuration state may have changed */
+ static final int FINISH_LAYOUT_REDO_CONFIG = 0x0002;
+ /** Wallpaper may need to move */
+ static final int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
+ /** Need to recompute animations */
+ static final int FINISH_LAYOUT_REDO_ANIM = 0x0008;
+
+ /**
+ * Called following layout of all windows before each window has policy applied.
+ *
+ * @param displayWidth The current full width of the screen.
+ * @param displayHeight The current full height of the screen.
+ */
+ public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight);
+
+ /**
+ * Called following layout of all window to apply policy to each window.
+ *
+ * @param win The window being positioned.
+ * @param attrs The LayoutParams of the window.
+ * @param attached For sub-windows, the window it is attached to. Otherwise null.
+ */
+ public void applyPostLayoutPolicyLw(WindowState win,
+ WindowManager.LayoutParams attrs, WindowState attached, WindowState imeTarget);
+
+ /**
+ * Called following layout of all windows and after policy has been applied
+ * to each window. If in this function you do
+ * something that may have modified the animation state of another window,
+ * be sure to return non-zero in order to perform another pass through layout.
+ *
+ * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT},
+ * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER},
+ * or {@link #FINISH_LAYOUT_REDO_ANIM}.
+ */
+ public int finishPostLayoutPolicyLw();
+
+ /**
+ * Return true if it is okay to perform animations for an app transition
+ * that is about to occur. You may return false for this if, for example,
+ * the lock screen is currently displayed so the switch should happen
+ * immediately.
+ */
+ public boolean allowAppAnimationsLw();
+
+
+ /**
+ * A new window has been focused.
+ */
+ public int focusChangedLw(WindowState lastFocus, WindowState newFocus);
+
+ /**
+ * Called when the device has started waking up.
+ */
+ public void startedWakingUp();
+
+ /**
+ * Called when the device has finished waking up.
+ */
+ public void finishedWakingUp();
+
+ /**
+ * Called when the device has started going to sleep.
+ *
+ * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN},
+ * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+ */
+ public void startedGoingToSleep(int why);
+
+ /**
+ * Called when the device has finished going to sleep.
+ *
+ * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN},
+ * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+ */
+ public void finishedGoingToSleep(int why);
+
+ /**
+ * Called when the device is about to turn on the screen to show content.
+ * When waking up, this method will be called once after the call to wakingUp().
+ * When dozing, the method will be called sometime after the call to goingToSleep() and
+ * may be called repeatedly in the case where the screen is pulsing on and off.
+ *
+ * Must call back on the listener to tell it when the higher-level system
+ * is ready for the screen to go on (i.e. the lock screen is shown).
+ */
+ public void screenTurningOn(ScreenOnListener screenOnListener);
+
+ /**
+ * Called when the device has actually turned on the screen, i.e. the display power state has
+ * been set to ON and the screen is unblocked.
+ */
+ public void screenTurnedOn();
+
+ /**
+ * Called when the display would like to be turned off. This gives policy a chance to do some
+ * things before the display power state is actually changed to off.
+ *
+ * @param screenOffListener Must be called to tell that the display power state can actually be
+ * changed now after policy has done its work.
+ */
+ public void screenTurningOff(ScreenOffListener screenOffListener);
+
+ /**
+ * Called when the device has turned the screen off.
+ */
+ public void screenTurnedOff();
+
+ public interface ScreenOnListener {
+ void onScreenOn();
+ }
+
+ /**
+ * See {@link #screenTurnedOff}
+ */
+ public interface ScreenOffListener {
+ void onScreenOff();
+ }
+
+ /**
+ * Return whether the default display is on and not blocked by a black surface.
+ */
+ public boolean isScreenOn();
+
+ /**
+ * @return whether the device is currently allowed to animate.
+ *
+ * Note: this can be true even if it is not appropriate to animate for reasons that are outside
+ * of the policy's authority.
+ */
+ boolean okToAnimate();
+
+ /**
+ * Tell the policy that the lid switch has changed state.
+ * @param whenNanos The time when the change occurred in uptime nanoseconds.
+ * @param lidOpen True if the lid is now open.
+ */
+ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
+
+ /**
+ * Tell the policy that the camera lens has been covered or uncovered.
+ * @param whenNanos The time when the change occurred in uptime nanoseconds.
+ * @param lensCovered True if the lens is covered.
+ */
+ public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered);
+
+ /**
+ * Tell the policy if anyone is requesting that keyguard not come on.
+ *
+ * @param enabled Whether keyguard can be on or not. does not actually
+ * turn it on, unless it was previously disabled with this function.
+ *
+ * @see android.app.KeyguardManager.KeyguardLock#disableKeyguard()
+ * @see android.app.KeyguardManager.KeyguardLock#reenableKeyguard()
+ */
+ @SuppressWarnings("javadoc")
+ public void enableKeyguard(boolean enabled);
+
+ /**
+ * Callback used by {@link WindowManagerPolicy#exitKeyguardSecurely}
+ */
+ interface OnKeyguardExitResult {
+ void onKeyguardExitResult(boolean success);
+ }
+
+ /**
+ * Tell the policy if anyone is requesting the keyguard to exit securely
+ * (this would be called after the keyguard was disabled)
+ * @param callback Callback to send the result back.
+ * @see android.app.KeyguardManager#exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult)
+ */
+ @SuppressWarnings("javadoc")
+ void exitKeyguardSecurely(OnKeyguardExitResult callback);
+
+ /**
+ * isKeyguardLocked
+ *
+ * Return whether the keyguard is currently locked.
+ *
+ * @return true if in keyguard is locked.
+ */
+ public boolean isKeyguardLocked();
+
+ /**
+ * isKeyguardSecure
+ *
+ * Return whether the keyguard requires a password to unlock.
+ * @param userId
+ *
+ * @return true if in keyguard is secure.
+ */
+ public boolean isKeyguardSecure(int userId);
+
+ /**
+ * Return whether the keyguard is currently occluded.
+ *
+ * @return true if in keyguard is occluded, false otherwise
+ */
+ public boolean isKeyguardOccluded();
+
+ /**
+ * @return true if in keyguard is on and not occluded.
+ */
+ public boolean isKeyguardShowingAndNotOccluded();
+
+ /**
+ * @return whether Keyguard is in trusted state and can be dismissed without credentials
+ */
+ public boolean isKeyguardTrustedLw();
+
+ /**
+ * inKeyguardRestrictedKeyInputMode
+ *
+ * if keyguard screen is showing or in restricted key input mode (i.e. in
+ * keyguard password emergency screen). When in such mode, certain keys,
+ * such as the Home key and the right soft keys, don't work.
+ *
+ * @return true if in keyguard restricted input mode.
+ */
+ public boolean inKeyguardRestrictedKeyInputMode();
+
+ /**
+ * Ask the policy to dismiss the keyguard, if it is currently shown.
+ *
+ * @param callback Callback to be informed about the result.
+ */
+ public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback);
+
+ /**
+ * Ask the policy whether the Keyguard has drawn. If the Keyguard is disabled, this method
+ * returns true as soon as we know that Keyguard is disabled.
+ *
+ * @return true if the keyguard has drawn.
+ */
+ public boolean isKeyguardDrawnLw();
+
+ public boolean isShowingDreamLw();
+
+ /**
+ * Given an orientation constant, returns the appropriate surface rotation,
+ * taking into account sensors, docking mode, rotation lock, and other factors.
+ *
+ * @param orientation An orientation constant, such as
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+ * @param lastRotation The most recently used rotation.
+ * @return The surface rotation to use.
+ */
+ public int rotationForOrientationLw(@ActivityInfo.ScreenOrientation int orientation,
+ int lastRotation);
+
+ /**
+ * Given an orientation constant and a rotation, returns true if the rotation
+ * has compatible metrics to the requested orientation. For example, if
+ * the application requested landscape and got seascape, then the rotation
+ * has compatible metrics; if the application requested portrait and got landscape,
+ * then the rotation has incompatible metrics; if the application did not specify
+ * a preference, then anything goes.
+ *
+ * @param orientation An orientation constant, such as
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+ * @param rotation The rotation to check.
+ * @return True if the rotation is compatible with the requested orientation.
+ */
+ public boolean rotationHasCompatibleMetricsLw(@ActivityInfo.ScreenOrientation int orientation,
+ int rotation);
+
+ /**
+ * Called by the window manager when the rotation changes.
+ *
+ * @param rotation The new rotation.
+ */
+ public void setRotationLw(int rotation);
+
+ /**
+ * Called when the system is mostly done booting to set whether
+ * the system should go into safe mode.
+ */
+ public void setSafeMode(boolean safeMode);
+
+ /**
+ * Called when the system is mostly done booting.
+ */
+ public void systemReady();
+
+ /**
+ * Called when the system is done booting to the point where the
+ * user can start interacting with it.
+ */
+ public void systemBooted();
+
+ /**
+ * Show boot time message to the user.
+ */
+ public void showBootMessage(final CharSequence msg, final boolean always);
+
+ /**
+ * Hide the UI for showing boot messages, never to be displayed again.
+ */
+ public void hideBootMessages();
+
+ /**
+ * Called when userActivity is signalled in the power manager.
+ * This is safe to call from any thread, with any window manager locks held or not.
+ */
+ public void userActivity();
+
+ /**
+ * Called when we have finished booting and can now display the home
+ * screen to the user. This will happen after systemReady(), and at
+ * this point the display is active.
+ */
+ public void enableScreenAfterBoot();
+
+ public void setCurrentOrientationLw(@ActivityInfo.ScreenOrientation int newOrientation);
+
+ /**
+ * Call from application to perform haptic feedback on its window.
+ */
+ public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always);
+
+ /**
+ * Called when we have started keeping the screen on because a window
+ * requesting this has become visible.
+ */
+ public void keepScreenOnStartedLw();
+
+ /**
+ * Called when we have stopped keeping the screen on because the last window
+ * requesting this is no longer visible.
+ */
+ public void keepScreenOnStoppedLw();
+
+ /**
+ * Gets the current user rotation mode.
+ *
+ * @return The rotation mode.
+ *
+ * @see WindowManagerPolicy#USER_ROTATION_LOCKED
+ * @see WindowManagerPolicy#USER_ROTATION_FREE
+ */
+ @UserRotationMode
+ public int getUserRotationMode();
+
+ /**
+ * Inform the policy that the user has chosen a preferred orientation ("rotation lock").
+ *
+ * @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
+ * {@link WindowManagerPolicy#USER_ROTATION_FREE}.
+ * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+ * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
+ */
+ public void setUserRotationMode(@UserRotationMode int mode, @Surface.Rotation int rotation);
+
+ /**
+ * Called when a new system UI visibility is being reported, allowing
+ * the policy to adjust what is actually reported.
+ * @param visibility The raw visibility reported by the status bar.
+ * @return The new desired visibility.
+ */
+ public int adjustSystemUiVisibilityLw(int visibility);
+
+ /**
+ * Called by System UI to notify of changes to the visibility of Recents.
+ */
+ public void setRecentsVisibilityLw(boolean visible);
+
+ /**
+ * Called by System UI to notify of changes to the visibility of PIP.
+ */
+ void setPipVisibilityLw(boolean visible);
+
+ /**
+ * Specifies whether there is an on-screen navigation bar separate from the status bar.
+ */
+ public boolean hasNavigationBar();
+
+ /**
+ * Lock the device now.
+ */
+ public void lockNow(Bundle options);
+
+ /**
+ * Set the last used input method window state. This state is used to make IME transition
+ * smooth.
+ * @hide
+ */
+ public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
+
+ /**
+ * An internal callback (from InputMethodManagerService) to notify a state change regarding
+ * whether the back key should dismiss the software keyboard (IME) or not.
+ *
+ * @param newValue {@code true} if the software keyboard is shown and the back key is expected
+ * to dismiss the software keyboard.
+ * @hide
+ */
+ default void setDismissImeOnBackKeyPressed(boolean newValue) {
+ // Default implementation does nothing.
+ }
+
+ /**
+ * Show the recents task list app.
+ * @hide
+ */
+ public void showRecentApps(boolean fromHome);
+
+ /**
+ * Show the global actions dialog.
+ * @hide
+ */
+ public void showGlobalActions();
+
+ /**
+ * @return The current height of the input method window.
+ */
+ public int getInputMethodWindowVisibleHeightLw();
+
+ /**
+ * Called when the current user changes. Guaranteed to be called before the broadcast
+ * of the new user id is made to all listeners.
+ *
+ * @param newUserId The id of the incoming user.
+ */
+ public void setCurrentUserLw(int newUserId);
+
+ /**
+ * For a given user-switch operation, this will be called once with switching=true before the
+ * user-switch and once with switching=false afterwards (or if the user-switch was cancelled).
+ * This gives the policy a chance to alter its behavior for the duration of a user-switch.
+ *
+ * @param switching true if a user-switch is in progress
+ */
+ void setSwitchingUser(boolean switching);
+
+ /**
+ * Print the WindowManagerPolicy's state into the given stream.
+ *
+ * @param prefix Text to print at the front of each line.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(String prefix, PrintWriter writer, String[] args);
+
+ /**
+ * Write the WindowManagerPolicy's state into the protocol buffer.
+ * The message is described in {@link com.android.server.wm.proto.WindowManagerPolicyProto}
+ *
+ * @param proto The protocol buffer output stream to write to.
+ */
+ void writeToProto(ProtoOutputStream proto, long fieldId);
+
+ /**
+ * Returns whether a given window type can be magnified.
+ *
+ * @param windowType The window type.
+ * @return True if the window can be magnified.
+ */
+ public boolean canMagnifyWindow(int windowType);
+
+ /**
+ * Returns whether a given window type is considered a top level one.
+ * A top level window does not have a container, i.e. attached window,
+ * or if it has a container it is laid out as a top-level window, not
+ * as a child of its container.
+ *
+ * @param windowType The window type.
+ * @return True if the window is a top level one.
+ */
+ public boolean isTopLevelWindow(int windowType);
+
+ /**
+ * Notifies the keyguard to start fading out.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
+ */
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
+
+ /**
+ * Calculates the stable insets without running a layout.
+ *
+ * @param displayRotation the current display rotation
+ * @param displayWidth the current display width
+ * @param displayHeight the current display height
+ * @param outInsets the insets to return
+ */
+ public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+ Rect outInsets);
+
+
+ /**
+ * @return true if the navigation bar is forced to stay visible
+ */
+ public boolean isNavBarForcedShownLw(WindowState win);
+
+ /**
+ * @return The side of the screen where navigation bar is positioned.
+ * @see #NAV_BAR_LEFT
+ * @see #NAV_BAR_RIGHT
+ * @see #NAV_BAR_BOTTOM
+ */
+ int getNavBarPosition();
+
+ /**
+ * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
+ * bar or button bar. See {@link #getNonDecorDisplayWidth}.
+ *
+ * @param displayRotation the current display rotation
+ * @param displayWidth the current display width
+ * @param displayHeight the current display height
+ * @param outInsets the insets to return
+ */
+ public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+ Rect outInsets);
+
+ /**
+ * @return True if a specified {@param dockSide} is allowed on the current device, or false
+ * otherwise. It is guaranteed that at least one dock side for a particular orientation
+ * is allowed, so for example, if DOCKED_RIGHT is not allowed, DOCKED_LEFT is allowed.
+ */
+ public boolean isDockSideAllowed(int dockSide);
+
+ /**
+ * Called when the configuration has changed, and it's safe to load new values from resources.
+ */
+ public void onConfigurationChanged();
+
+ public boolean shouldRotateSeamlessly(int oldRotation, int newRotation);
+
+ /**
+ * Called when System UI has been started.
+ */
+ void onSystemUiStarted();
+
+ /**
+ * Checks whether the policy is ready for dismissing the boot animation and completing the boot.
+ *
+ * @return true if ready; false otherwise.
+ */
+ boolean canDismissBootAnimation();
+}
diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java
new file mode 100644
index 00000000..0f21c5c8
--- /dev/null
+++ b/android/view/accessibility/AccessibilityCache.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2012 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.accessibility;
+
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.LongArray;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Cache for AccessibilityWindowInfos and AccessibilityNodeInfos.
+ * It is updated when windows change or nodes change.
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class AccessibilityCache {
+
+ private static final String LOG_TAG = "AccessibilityCache";
+
+ private static final boolean DEBUG = false;
+
+ private static final boolean CHECK_INTEGRITY = Build.IS_ENG;
+
+ /**
+ * {@link AccessibilityEvent} types that are critical for the cache to stay up to date
+ *
+ * When adding new event types in {@link #onAccessibilityEvent}, please add it here also, to
+ * make sure that the events are delivered to cache regardless of
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#eventTypes}
+ */
+ public static final int CACHE_CRITICAL_EVENTS_MASK =
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ | AccessibilityEvent.TYPE_VIEW_FOCUSED
+ | AccessibilityEvent.TYPE_VIEW_SELECTED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+ | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_SCROLLED
+ | AccessibilityEvent.TYPE_WINDOWS_CHANGED
+ | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
+ private final Object mLock = new Object();
+
+ private final AccessibilityNodeRefresher mAccessibilityNodeRefresher;
+
+ private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+
+ private boolean mIsAllWindowsCached;
+
+ private final SparseArray<AccessibilityWindowInfo> mWindowCache =
+ new SparseArray<>();
+
+ private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
+ new SparseArray<>();
+
+ private final SparseArray<AccessibilityWindowInfo> mTempWindowArray =
+ new SparseArray<>();
+
+ public AccessibilityCache(AccessibilityNodeRefresher nodeRefresher) {
+ mAccessibilityNodeRefresher = nodeRefresher;
+ }
+
+ public void setWindows(List<AccessibilityWindowInfo> windows) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Set windows");
+ }
+ clearWindowCache();
+ if (windows == null) {
+ return;
+ }
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ final AccessibilityWindowInfo window = windows.get(i);
+ addWindow(window);
+ }
+ mIsAllWindowsCached = true;
+ }
+ }
+
+ public void addWindow(AccessibilityWindowInfo window) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Caching window: " + window.getId());
+ }
+ final int windowId = window.getId();
+ AccessibilityWindowInfo oldWindow = mWindowCache.get(windowId);
+ if (oldWindow != null) {
+ oldWindow.recycle();
+ }
+ mWindowCache.put(windowId, AccessibilityWindowInfo.obtain(window));
+ }
+ }
+
+ /**
+ * Notifies the cache that the something in the UI changed. As a result
+ * the cache will either refresh some nodes or evict some nodes.
+ *
+ * Note: any event that ends up affecting the cache should also be present in
+ * {@link #CACHE_CRITICAL_EVENTS_MASK}
+ *
+ * @param event An event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+ if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ }
+ mAccessibilityFocus = event.getSourceNodeId();
+ refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ } break;
+
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ if (mAccessibilityFocus == event.getSourceNodeId()) {
+ refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ }
+ } break;
+
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+ if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
+ }
+ mInputFocus = event.getSourceNodeId();
+ refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
+ } break;
+
+ case AccessibilityEvent.TYPE_VIEW_SELECTED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_CLICKED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId());
+ } break;
+
+ case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
+ synchronized (mLock) {
+ final int windowId = event.getWindowId();
+ final long sourceId = event.getSourceNodeId();
+ if ((event.getContentChangeTypes()
+ & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+ clearSubTreeLocked(windowId, sourceId);
+ } else {
+ refreshCachedNodeLocked(windowId, sourceId);
+ }
+ }
+ } break;
+
+ case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
+ clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
+ } break;
+
+ case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+ case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+ clear();
+ } break;
+ }
+ }
+
+ if (CHECK_INTEGRITY) {
+ checkIntegrity();
+ }
+ }
+
+ private void refreshCachedNodeLocked(int windowId, long sourceId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Refreshing cached node.");
+ }
+
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ return;
+ }
+ AccessibilityNodeInfo cachedInfo = nodes.get(sourceId);
+ // If the source is not in the cache - nothing to do.
+ if (cachedInfo == null) {
+ return;
+ }
+ // The node changed so we will just refresh it right now.
+ if (mAccessibilityNodeRefresher.refreshNode(cachedInfo, true)) {
+ return;
+ }
+ // Weird, we could not refresh. Just evict the entire sub-tree.
+ clearSubTreeLocked(windowId, sourceId);
+ }
+
+ /**
+ * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
+ * window and the accessibility id of the node.
+ *
+ * @param windowId The id of the window hosting the node.
+ * @param accessibilityNodeId The info accessibility node id.
+ * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
+ */
+ public AccessibilityNodeInfo getNode(int windowId, long accessibilityNodeId) {
+ synchronized(mLock) {
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ return null;
+ }
+ AccessibilityNodeInfo info = nodes.get(accessibilityNodeId);
+ if (info != null) {
+ // Return a copy since the client calls to AccessibilityNodeInfo#recycle()
+ // will wipe the data of the cached info.
+ info = AccessibilityNodeInfo.obtain(info);
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info);
+ }
+ return info;
+ }
+ }
+
+ public List<AccessibilityWindowInfo> getWindows() {
+ synchronized (mLock) {
+ if (!mIsAllWindowsCached) {
+ return null;
+ }
+
+ final int windowCount = mWindowCache.size();
+ if (windowCount > 0) {
+ // Careful to return the windows in a decreasing layer order.
+ SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
+ sortedWindows.clear();
+
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+ sortedWindows.put(window.getLayer(), window);
+ }
+
+ // It's possible in transient conditions for two windows to share the same
+ // layer, which results in sortedWindows being smaller than mWindowCache
+ final int sortedWindowCount = sortedWindows.size();
+ List<AccessibilityWindowInfo> windows = new ArrayList<>(sortedWindowCount);
+ for (int i = sortedWindowCount - 1; i >= 0; i--) {
+ AccessibilityWindowInfo window = sortedWindows.valueAt(i);
+ windows.add(AccessibilityWindowInfo.obtain(window));
+ sortedWindows.removeAt(i);
+ }
+
+ return windows;
+ }
+ return null;
+ }
+ }
+
+ public AccessibilityWindowInfo getWindow(int windowId) {
+ synchronized (mLock) {
+ AccessibilityWindowInfo window = mWindowCache.get(windowId);
+ if (window != null) {
+ return AccessibilityWindowInfo.obtain(window);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Caches an {@link AccessibilityNodeInfo}.
+ *
+ * @param info The node to cache.
+ */
+ public void add(AccessibilityNodeInfo info) {
+ synchronized(mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "add(" + info + ")");
+ }
+
+ final int windowId = info.getWindowId();
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ nodes = new LongSparseArray<>();
+ mNodeCache.put(windowId, nodes);
+ }
+
+ final long sourceId = info.getSourceNodeId();
+ AccessibilityNodeInfo oldInfo = nodes.get(sourceId);
+ if (oldInfo != null) {
+ // If the added node is in the cache we have to be careful if
+ // the new one represents a source state where some of the
+ // children have been removed to remove the descendants that
+ // are no longer present.
+ final LongArray newChildrenIds = info.getChildNodeIds();
+
+ final int oldChildCount = oldInfo.getChildCount();
+ for (int i = 0; i < oldChildCount; i++) {
+ if (nodes.get(sourceId) == null) {
+ // We've removed (and thus recycled) this node because it was its own
+ // ancestor (the app gave us bad data), we can't continue using it.
+ // Clear the cache for this window and give up on adding the node.
+ clearNodesForWindowLocked(windowId);
+ return;
+ }
+ final long oldChildId = oldInfo.getChildId(i);
+ // If the child is no longer present, remove the sub-tree.
+ if (newChildrenIds == null || newChildrenIds.indexOf(oldChildId) < 0) {
+ clearSubTreeLocked(windowId, oldChildId);
+ }
+ }
+
+ // Also be careful if the parent has changed since the new
+ // parent may be a predecessor of the old parent which will
+ // add cycles to the cache.
+ final long oldParentId = oldInfo.getParentNodeId();
+ if (info.getParentNodeId() != oldParentId) {
+ clearSubTreeLocked(windowId, oldParentId);
+ } else {
+ oldInfo.recycle();
+ }
+ }
+
+ // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
+ // will wipe the data of the cached info.
+ AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
+ nodes.put(sourceId, clone);
+ if (clone.isAccessibilityFocused()) {
+ mAccessibilityFocus = sourceId;
+ }
+ if (clone.isFocused()) {
+ mInputFocus = sourceId;
+ }
+ }
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public void clear() {
+ synchronized(mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "clear()");
+ }
+ clearWindowCache();
+ final int nodesForWindowCount = mNodeCache.size();
+ for (int i = 0; i < nodesForWindowCount; i++) {
+ final int windowId = mNodeCache.keyAt(i);
+ clearNodesForWindowLocked(windowId);
+ }
+
+ mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ }
+ }
+
+ private void clearWindowCache() {
+ final int windowCount = mWindowCache.size();
+ for (int i = windowCount - 1; i >= 0; i--) {
+ AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+ window.recycle();
+ mWindowCache.removeAt(i);
+ }
+ mIsAllWindowsCached = false;
+ }
+
+ /**
+ * Clears nodes for the window with the given id
+ */
+ private void clearNodesForWindowLocked(int windowId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "clearNodesForWindowLocked(" + windowId + ")");
+ }
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ return;
+ }
+ // Recycle the nodes before clearing the cache.
+ final int nodeCount = nodes.size();
+ for (int i = nodeCount - 1; i >= 0; i--) {
+ AccessibilityNodeInfo info = nodes.valueAt(i);
+ nodes.removeAt(i);
+ info.recycle();
+ }
+ mNodeCache.remove(windowId);
+ }
+
+ /**
+ * Clears a subtree rooted at the node with the given id that is
+ * hosted in a given window.
+ *
+ * @param windowId The id of the hosting window.
+ * @param rootNodeId The root id.
+ */
+ private void clearSubTreeLocked(int windowId, long rootNodeId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Clearing cached subtree.");
+ }
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes != null) {
+ clearSubTreeRecursiveLocked(nodes, rootNodeId);
+ }
+ }
+
+ /**
+ * Clears a subtree given a pointer to the root id and the nodes
+ * in the hosting window.
+ *
+ * @param nodes The nodes in the hosting window.
+ * @param rootNodeId The id of the root to evict.
+ */
+ private void clearSubTreeRecursiveLocked(LongSparseArray<AccessibilityNodeInfo> nodes,
+ long rootNodeId) {
+ AccessibilityNodeInfo current = nodes.get(rootNodeId);
+ if (current == null) {
+ return;
+ }
+ nodes.remove(rootNodeId);
+ final int childCount = current.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final long childNodeId = current.getChildId(i);
+ clearSubTreeRecursiveLocked(nodes, childNodeId);
+ }
+ current.recycle();
+ }
+
+ /**
+ * Check the integrity of the cache which is nodes from different windows
+ * are not mixed, there is a single active window, there is a single focused
+ * window, for every window there are no duplicates nodes, all nodes for a
+ * window are connected, for every window there is a single input focused
+ * node, and for every window there is a single accessibility focused node.
+ */
+ public void checkIntegrity() {
+ synchronized (mLock) {
+ // Get the root.
+ if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
+ return;
+ }
+
+ AccessibilityWindowInfo focusedWindow = null;
+ AccessibilityWindowInfo activeWindow = null;
+
+ final int windowCount = mWindowCache.size();
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+
+ // Check for one active window.
+ if (window.isActive()) {
+ if (activeWindow != null) {
+ Log.e(LOG_TAG, "Duplicate active window:" + window);
+ } else {
+ activeWindow = window;
+ }
+ }
+
+ // Check for one focused window.
+ if (window.isFocused()) {
+ if (focusedWindow != null) {
+ Log.e(LOG_TAG, "Duplicate focused window:" + window);
+ } else {
+ focusedWindow = window;
+ }
+ }
+ }
+
+ // Traverse the tree and do some checks.
+ AccessibilityNodeInfo accessFocus = null;
+ AccessibilityNodeInfo inputFocus = null;
+
+ final int nodesForWindowCount = mNodeCache.size();
+ for (int i = 0; i < nodesForWindowCount; i++) {
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.valueAt(i);
+ if (nodes.size() <= 0) {
+ continue;
+ }
+
+ ArraySet<AccessibilityNodeInfo> seen = new ArraySet<>();
+ final int windowId = mNodeCache.keyAt(i);
+
+ final int nodeCount = nodes.size();
+ for (int j = 0; j < nodeCount; j++) {
+ AccessibilityNodeInfo node = nodes.valueAt(j);
+
+ // Check for duplicates
+ if (!seen.add(node)) {
+ Log.e(LOG_TAG, "Duplicate node: " + node
+ + " in window:" + windowId);
+ // Stop now as we potentially found a loop.
+ continue;
+ }
+
+ // Check for one accessibility focus.
+ if (node.isAccessibilityFocused()) {
+ if (accessFocus != null) {
+ Log.e(LOG_TAG, "Duplicate accessibility focus:" + node
+ + " in window:" + windowId);
+ } else {
+ accessFocus = node;
+ }
+ }
+
+ // Check for one input focus.
+ if (node.isFocused()) {
+ if (inputFocus != null) {
+ Log.e(LOG_TAG, "Duplicate input focus: " + node
+ + " in window:" + windowId);
+ } else {
+ inputFocus = node;
+ }
+ }
+
+ // The node should be a child of its parent if we have the parent.
+ AccessibilityNodeInfo nodeParent = nodes.get(node.getParentNodeId());
+ if (nodeParent != null) {
+ boolean childOfItsParent = false;
+ final int childCount = nodeParent.getChildCount();
+ for (int k = 0; k < childCount; k++) {
+ AccessibilityNodeInfo child = nodes.get(nodeParent.getChildId(k));
+ if (child == node) {
+ childOfItsParent = true;
+ break;
+ }
+ }
+ if (!childOfItsParent) {
+ Log.e(LOG_TAG, "Invalid parent-child relation between parent: "
+ + nodeParent + " and child: " + node);
+ }
+ }
+
+ // The node should be the parent of its child if we have the child.
+ final int childCount = node.getChildCount();
+ for (int k = 0; k < childCount; k++) {
+ AccessibilityNodeInfo child = nodes.get(node.getChildId(k));
+ if (child != null) {
+ AccessibilityNodeInfo parent = nodes.get(child.getParentNodeId());
+ if (parent != node) {
+ Log.e(LOG_TAG, "Invalid child-parent relation between child: "
+ + node + " and parent: " + nodeParent);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Layer of indirection included to break dependency chain for testing
+ public static class AccessibilityNodeRefresher {
+ public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
+ return info.refresh(null, bypassCache);
+ }
+ }
+}
diff --git a/android/view/accessibility/AccessibilityEvent.java b/android/view/accessibility/AccessibilityEvent.java
new file mode 100644
index 00000000..5adea669
--- /dev/null
+++ b/android/view/accessibility/AccessibilityEvent.java
@@ -0,0 +1,1328 @@
+/*
+ * Copyright (C) 2009 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.accessibility;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Pools.SynchronizedPool;
+
+import com.android.internal.util.BitUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * This class represents accessibility events that are sent by the system when
+ * something notable happens in the user interface. For example, when a
+ * {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc.
+ * </p>
+ * <p>
+ * An accessibility event is fired by an individual view which populates the event with
+ * data for its state and requests from its parent to send the event to interested
+ * parties. The parent can optionally add an {@link AccessibilityRecord} for itself before
+ * dispatching a similar request to its parent. A parent can also choose not to respect the
+ * request for sending an event. The accessibility event is sent by the topmost view in the
+ * view tree. Therefore, an {@link android.accessibilityservice.AccessibilityService} can
+ * explore all records in an accessibility event to obtain more information about the
+ * context in which the event was fired.
+ * </p>
+ * <p>
+ * The main purpose of an accessibility event is to expose enough information for an
+ * {@link android.accessibilityservice.AccessibilityService} to provide meaningful feedback
+ * to the user. Sometimes however, an accessibility service may need more contextual
+ * information then the one in the event pay-load. In such cases the service can obtain
+ * the event source which is an {@link AccessibilityNodeInfo} (snapshot of a View state)
+ * which can be used for exploring the window content. Note that the privilege for accessing
+ * an event's source, thus the window content, has to be explicitly requested. For more
+ * details refer to {@link android.accessibilityservice.AccessibilityService}. If an
+ * accessibility service has not requested to retrieve the window content the event will
+ * not contain reference to its source. Also for events of type
+ * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available.
+ * </p>
+ * <p>
+ * This class represents various semantically different accessibility event
+ * types. Each event type has an associated set of related properties. In other
+ * words, each event type is characterized via a subset of the properties exposed
+ * by this class. For each event type there is a corresponding constant defined
+ * in this class. Follows a specification of the event types and their associated properties:
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating and processing AccessibilityEvents, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ * <p>
+ * <b>VIEW TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>View clicked</b> - represents the event of clicking on a {@link android.view.View}
+ * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.</br>
+ * <em>Type:</em>{@link #TYPE_VIEW_CLICKED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #isPassword()} - Whether the source is password.</li>
+ * <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View long clicked</b> - represents the event of long clicking on a {@link android.view.View}
+ * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc </br>
+ * <em>Type:</em>{@link #TYPE_VIEW_LONG_CLICKED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #isPassword()} - Whether the source is password.</li>
+ * <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View selected</b> - represents the event of selecting an item usually in
+ * the context of an {@link android.widget.AdapterView}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_SELECTED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #isPassword()} - Whether the source is password.</li>
+ * <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getItemCount()} - The number of selectable items of the source.</li>
+ * <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View focused</b> - represents the event of focusing a
+ * {@link android.view.View}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_FOCUSED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #isPassword()} - Whether the source is password.</li>
+ * <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getItemCount()} - The number of focusable items on the screen.</li>
+ * <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View text changed</b> - represents the event of changing the text of an
+ * {@link android.widget.EditText}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_TEXT_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #isPassword()} - Whether the source is password.</li>
+ * <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getFromIndex()} - The text change start index.</li>
+ * <li>{@link #getAddedCount()} - The number of added characters.</li>
+ * <li>{@link #getRemovedCount()} - The number of removed characters.</li>
+ * <li>{@link #getBeforeText()} - The text of the source before the change.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View text selection changed</b> - represents the event of changing the text
+ * selection of an {@link android.widget.EditText}.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} </br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #isPassword()} - Whether the source is password.</li>
+ * <li>{@link #getFromIndex()} - The selection start index.</li>
+ * <li>{@link #getToIndex()} - The selection end index.</li>
+ * <li>{@link #getItemCount()} - The length of the source text.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * </ul>
+ * </p>
+ * <b>View text traversed at movement granularity</b> - represents the event of traversing the
+ * text of a view at a given granularity. For example, moving to the next word.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY} </br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text
+ * was traversed.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #getFromIndex()} - The start the text that was skipped over in this movement.
+ * This is the starting point when moving forward through the text, but not when moving
+ * back.</li>
+ * <li>{@link #getToIndex()} - The end of the text that was skipped over in this movement.
+ * This is the ending point when moving forward through the text, but not when moving
+ * back.</li>
+ * <li>{@link #isPassword()} - Whether the source is password.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text
+ * was traversed.</li>
+ * <li>{@link #getAction()} - Gets traversal action which specifies the direction.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>View scrolled</b> - represents the event of scrolling a view. If
+ * the source is a descendant of {@link android.widget.AdapterView} the
+ * scroll is reported in terms of visible items - the first visible item,
+ * the last visible item, and the total items - because the the source
+ * is unaware of its pixel size since its adapter is responsible for
+ * creating views. In all other cases the scroll is reported as the current
+ * scroll on the X and Y axis respectively plus the height of the source in
+ * pixels.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * <em>Note:</em> This event type is not dispatched to descendants though
+ * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
+ * source {@link android.view.View} and the sub-tree rooted at it will not receive
+ * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
+ * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
+ * text content to such events is by setting the
+ * {@link android.R.styleable#View_contentDescription contentDescription} of the source
+ * view.</br>
+ * </p>
+ * <p>
+ * <b>TRANSITION TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>Window state changed</b> - represents the event of opening a
+ * {@link android.widget.PopupWindow}, {@link android.view.Menu},
+ * {@link android.app.Dialog}, etc.</br>
+ * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>Window content changed</b> - represents the event of change in the
+ * content of a window. This change can be adding/removing view, changing
+ * a view size, etc.</br>
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This event is fired only for the window source of the
+ * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED}
+ * and its purpose is to notify clients that the content of the user interaction
+ * window has changed.</br>
+ * <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getContentChangeTypes()} - The type of content changes.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * </ul>
+ * <em>Note:</em> This event type is not dispatched to descendants though
+ * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
+ * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
+ * source {@link android.view.View} and the sub-tree rooted at it will not receive
+ * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
+ * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
+ * text content to such events is by setting the
+ * {@link android.R.styleable#View_contentDescription contentDescription} of the source
+ * view.</br>
+ * </p>
+ * <p>
+ * <b>Windows changed</b> - represents the event of changes in the windows shown on
+ * the screen such as a window appeared, a window disappeared, a window size changed,
+ * a window layer changed, etc.</br>
+ * <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * </ul>
+ * <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window
+ * source of the event via {@link AccessibilityEvent#getSource()} to get the source
+ * node on which then call {@link AccessibilityNodeInfo#getWindow()
+ * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can
+ * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows()
+ * android.accessibilityservice.AccessibilityService.getWindows()}.
+ * </p>
+ * <p>
+ * <b>NOTIFICATION TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>Notification state changed</b> - represents the event showing a transient piece of information
+ * to the user. This information may be a {@link android.app.Notification} or
+ * {@link android.widget.Toast}.</br>
+ * <em>Type:</em> {@link #TYPE_NOTIFICATION_STATE_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}, if
+ * applicable.</li>
+ * <li>{@link #getText()} - Displayed text of the {@link android.widget.Toast}, if applicable,
+ * or may contain text from the {@link android.app.Notification}, although
+ * {@link #getParcelableData()} is a richer set of data for {@link android.app.Notification}.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>EXPLORATION TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>View hover enter</b> - represents the event of beginning to hover
+ * over a {@link android.view.View}. The hover may be generated via
+ * exploring the screen by touch or via a pointing device.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_HOVER_ENTER}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <b>View hover exit</b> - represents the event of stopping to hover
+ * over a {@link android.view.View}. The hover may be generated via
+ * exploring the screen by touch or via a pointing device.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_HOVER_EXIT}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>Touch interaction start</b> - represents the event of starting a touch
+ * interaction, which is the user starts touching the screen.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch interaction end</b> - represents the event of ending a touch
+ * interaction, which is the user stops touching the screen.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch exploration gesture start</b> - represents the event of starting a touch
+ * exploring gesture.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch exploration gesture end</b> - represents the event of ending a touch
+ * exploring gesture.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch gesture detection start</b> - represents the event of starting a user
+ * gesture detection.</br>
+ * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch gesture detection end</b> - represents the event of ending a user
+ * gesture detection.</br>
+ * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>MISCELLANEOUS TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>Announcement</b> - represents the event of an application making an
+ * announcement. Usually this announcement is related to some sort of a context
+ * change for which none of the events representing UI transitions is a good fit.
+ * For example, announcing a new page in a book.</br>
+ * <em>Type:</em> {@link #TYPE_ANNOUNCEMENT}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the announcement.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * </ul>
+ * </p>
+ *
+ * @see android.view.accessibility.AccessibilityManager
+ * @see android.accessibilityservice.AccessibilityService
+ * @see AccessibilityNodeInfo
+ */
+public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
+ private static final boolean DEBUG = false;
+
+ /**
+ * Invalid selection/focus position.
+ *
+ * @see #getCurrentItemIndex()
+ */
+ public static final int INVALID_POSITION = -1;
+
+ /**
+ * Maximum length of the text fields.
+ *
+ * @see #getBeforeText()
+ * @see #getText()
+ * </br>
+ * Note: This constant is no longer needed since there
+ * is no limit on the length of text that is contained
+ * in an accessibility event anymore.
+ */
+ @Deprecated
+ public static final int MAX_TEXT_LENGTH = 500;
+
+ /**
+ * Represents the event of clicking on a {@link android.view.View} like
+ * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+ */
+ public static final int TYPE_VIEW_CLICKED = 0x00000001;
+
+ /**
+ * Represents the event of long clicking on a {@link android.view.View} like
+ * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+ */
+ public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;
+
+ /**
+ * Represents the event of selecting an item usually in the context of an
+ * {@link android.widget.AdapterView}.
+ */
+ public static final int TYPE_VIEW_SELECTED = 0x00000004;
+
+ /**
+ * Represents the event of setting input focus of a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_FOCUSED = 0x00000008;
+
+ /**
+ * Represents the event of changing the text of an {@link android.widget.EditText}.
+ */
+ public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
+
+ /**
+ * Represents the event of opening a {@link android.widget.PopupWindow},
+ * {@link android.view.Menu}, {@link android.app.Dialog}, etc.
+ */
+ public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
+
+ /**
+ * Represents the event showing a {@link android.app.Notification}.
+ */
+ public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
+
+ /**
+ * Represents the event of a hover enter over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
+
+ /**
+ * Represents the event of a hover exit over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
+
+ /**
+ * Represents the event of starting a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
+
+ /**
+ * Represents the event of ending a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
+
+ /**
+ * Represents the event of changing the content of a window and more
+ * specifically the sub-tree rooted at the event's source.
+ */
+ public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
+
+ /**
+ * Represents the event of scrolling a view. This event type is generally not sent directly.
+ * @see View#onScrollChanged(int, int, int, int)
+ */
+ public static final int TYPE_VIEW_SCROLLED = 0x00001000;
+
+ /**
+ * Represents the event of changing the selection in an {@link android.widget.EditText}.
+ */
+ public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000;
+
+ /**
+ * Represents the event of an application making an announcement.
+ */
+ public static final int TYPE_ANNOUNCEMENT = 0x00004000;
+
+ /**
+ * Represents the event of gaining accessibility focus.
+ */
+ public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;
+
+ /**
+ * Represents the event of clearing accessibility focus.
+ */
+ public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;
+
+ /**
+ * Represents the event of traversing the text of a view at a given movement granularity.
+ */
+ public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;
+
+ /**
+ * Represents the event of beginning gesture detection.
+ */
+ public static final int TYPE_GESTURE_DETECTION_START = 0x00040000;
+
+ /**
+ * Represents the event of ending gesture detection.
+ */
+ public static final int TYPE_GESTURE_DETECTION_END = 0x00080000;
+
+ /**
+ * Represents the event of the user starting to touch the screen.
+ */
+ public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000;
+
+ /**
+ * Represents the event of the user ending to touch the screen.
+ */
+ public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
+
+ /**
+ * Represents the event change in the windows shown on the screen.
+ */
+ public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
+
+ /**
+ * Represents the event of a context click on a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_CONTEXT_CLICKED = 0x00800000;
+
+ /**
+ * Represents the event of the assistant currently reading the users screen context.
+ */
+ public static final int TYPE_ASSIST_READING_CONTEXT = 0x01000000;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * The type of change is not defined.
+ */
+ public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * A node in the subtree rooted at the source node was added or removed.
+ */
+ public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * The node's text changed.
+ */
+ public static final int CONTENT_CHANGE_TYPE_TEXT = 0x00000002;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * The node's content description changed.
+ */
+ public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
+
+ /**
+ * Mask for {@link AccessibilityEvent} all types.
+ *
+ * @see #TYPE_VIEW_CLICKED
+ * @see #TYPE_VIEW_LONG_CLICKED
+ * @see #TYPE_VIEW_SELECTED
+ * @see #TYPE_VIEW_FOCUSED
+ * @see #TYPE_VIEW_TEXT_CHANGED
+ * @see #TYPE_WINDOW_STATE_CHANGED
+ * @see #TYPE_NOTIFICATION_STATE_CHANGED
+ * @see #TYPE_VIEW_HOVER_ENTER
+ * @see #TYPE_VIEW_HOVER_EXIT
+ * @see #TYPE_TOUCH_EXPLORATION_GESTURE_START
+ * @see #TYPE_TOUCH_EXPLORATION_GESTURE_END
+ * @see #TYPE_WINDOW_CONTENT_CHANGED
+ * @see #TYPE_VIEW_SCROLLED
+ * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED
+ * @see #TYPE_ANNOUNCEMENT
+ * @see #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ * @see #TYPE_GESTURE_DETECTION_START
+ * @see #TYPE_GESTURE_DETECTION_END
+ * @see #TYPE_TOUCH_INTERACTION_START
+ * @see #TYPE_TOUCH_INTERACTION_END
+ * @see #TYPE_WINDOWS_CHANGED
+ * @see #TYPE_VIEW_CONTEXT_CLICKED
+ */
+ public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
+
+ private static final int MAX_POOL_SIZE = 10;
+ private static final SynchronizedPool<AccessibilityEvent> sPool =
+ new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE);
+
+ private int mEventType;
+ private CharSequence mPackageName;
+ private long mEventTime;
+ int mMovementGranularity;
+ int mAction;
+ int mContentChangeTypes;
+
+ private ArrayList<AccessibilityRecord> mRecords;
+
+ /*
+ * Hide constructor from clients.
+ */
+ private AccessibilityEvent() {
+ }
+
+ /**
+ * Initialize an event from another one.
+ *
+ * @param event The event to initialize from.
+ */
+ void init(AccessibilityEvent event) {
+ super.init(event);
+ mEventType = event.mEventType;
+ mMovementGranularity = event.mMovementGranularity;
+ mAction = event.mAction;
+ mContentChangeTypes = event.mContentChangeTypes;
+ mEventTime = event.mEventTime;
+ mPackageName = event.mPackageName;
+ }
+
+ /**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ @Override
+ public void setSealed(boolean sealed) {
+ super.setSealed(sealed);
+ final List<AccessibilityRecord> records = mRecords;
+ if (records != null) {
+ final int recordCount = records.size();
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = records.get(i);
+ record.setSealed(sealed);
+ }
+ }
+ }
+
+ /**
+ * Gets the number of records contained in the event.
+ *
+ * @return The number of records.
+ */
+ public int getRecordCount() {
+ return mRecords == null ? 0 : mRecords.size();
+ }
+
+ /**
+ * Appends an {@link AccessibilityRecord} to the end of event records.
+ *
+ * @param record The record to append.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void appendRecord(AccessibilityRecord record) {
+ enforceNotSealed();
+ if (mRecords == null) {
+ mRecords = new ArrayList<AccessibilityRecord>();
+ }
+ mRecords.add(record);
+ }
+
+ /**
+ * Gets the record at a given index.
+ *
+ * @param index The index.
+ * @return The record at the specified index.
+ */
+ public AccessibilityRecord getRecord(int index) {
+ if (mRecords == null) {
+ throw new IndexOutOfBoundsException("Invalid index " + index + ", size is 0");
+ }
+ return mRecords.get(index);
+ }
+
+ /**
+ * Gets the event type.
+ *
+ * @return The event type.
+ */
+ public int getEventType() {
+ return mEventType;
+ }
+
+ /**
+ * Gets the bit mask of change types signaled by an
+ * {@link #TYPE_WINDOW_CONTENT_CHANGED} event. A single event may represent
+ * multiple change types.
+ *
+ * @return The bit mask of change types. One or more of:
+ * <ul>
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
+ * </ul>
+ */
+ public int getContentChangeTypes() {
+ return mContentChangeTypes;
+ }
+
+ private static String contentChangeTypesToString(int types) {
+ return BitUtils.flagsToString(types, AccessibilityEvent::singleContentChangeTypeToString);
+ }
+
+ private static String singleContentChangeTypeToString(int type) {
+ switch (type) {
+ case CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION: {
+ return "CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION";
+ }
+ case CONTENT_CHANGE_TYPE_SUBTREE: return "CONTENT_CHANGE_TYPE_SUBTREE";
+ case CONTENT_CHANGE_TYPE_TEXT: return "CONTENT_CHANGE_TYPE_TEXT";
+ case CONTENT_CHANGE_TYPE_UNDEFINED: return "CONTENT_CHANGE_TYPE_UNDEFINED";
+ default: return Integer.toHexString(type);
+ }
+ }
+
+ /**
+ * Sets the bit mask of node tree changes signaled by an
+ * {@link #TYPE_WINDOW_CONTENT_CHANGED} event.
+ *
+ * @param changeTypes The bit mask of change types.
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @see #getContentChangeTypes()
+ */
+ public void setContentChangeTypes(int changeTypes) {
+ enforceNotSealed();
+ mContentChangeTypes = changeTypes;
+ }
+
+ /**
+ * Sets the event type.
+ *
+ * @param eventType The event type.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEventType(int eventType) {
+ enforceNotSealed();
+ mEventType = eventType;
+ }
+
+ /**
+ * Gets the time in which this event was sent.
+ *
+ * @return The event time.
+ */
+ public long getEventTime() {
+ return mEventTime;
+ }
+
+ /**
+ * Sets the time in which this event was sent.
+ *
+ * @param eventTime The event time.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEventTime(long eventTime) {
+ enforceNotSealed();
+ mEventTime = eventTime;
+ }
+
+ /**
+ * Gets the package name of the source.
+ *
+ * @return The package name.
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sets the package name of the source.
+ *
+ * @param packageName The package name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPackageName(CharSequence packageName) {
+ enforceNotSealed();
+ mPackageName = packageName;
+ }
+
+ /**
+ * Sets the movement granularity that was traversed.
+ *
+ * @param granularity The granularity.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setMovementGranularity(int granularity) {
+ enforceNotSealed();
+ mMovementGranularity = granularity;
+ }
+
+ /**
+ * Gets the movement granularity that was traversed.
+ *
+ * @return The granularity.
+ */
+ public int getMovementGranularity() {
+ return mMovementGranularity;
+ }
+
+ /**
+ * Sets the performed action that triggered this event.
+ * <p>
+ * Valid actions are defined in {@link AccessibilityNodeInfo}:
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}
+ * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+ * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_FOCUS}
+ * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_SELECTION}
+ * <li>{@link AccessibilityNodeInfo#ACTION_CLICK}
+ * <li>etc.
+ * </ul>
+ *
+ * @param action The action.
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @see AccessibilityNodeInfo#performAction(int)
+ */
+ public void setAction(int action) {
+ enforceNotSealed();
+ mAction = action;
+ }
+
+ /**
+ * Gets the performed action that triggered this event.
+ *
+ * @return The action.
+ */
+ public int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated with its type property set.
+ *
+ * @param eventType The event type.
+ * @return An instance.
+ */
+ public static AccessibilityEvent obtain(int eventType) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(eventType);
+ return event;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * created. The returned instance is initialized from the given
+ * <code>event</code>.
+ *
+ * @param event The other event.
+ * @return An instance.
+ */
+ public static AccessibilityEvent obtain(AccessibilityEvent event) {
+ AccessibilityEvent eventClone = AccessibilityEvent.obtain();
+ eventClone.init(event);
+
+ if (event.mRecords != null) {
+ final int recordCount = event.mRecords.size();
+ eventClone.mRecords = new ArrayList<AccessibilityRecord>(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ final AccessibilityRecord record = event.mRecords.get(i);
+ final AccessibilityRecord recordClone = AccessibilityRecord.obtain(record);
+ eventClone.mRecords.add(recordClone);
+ }
+ }
+
+ return eventClone;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityEvent obtain() {
+ AccessibilityEvent event = sPool.acquire();
+ return (event != null) ? event : new AccessibilityEvent();
+ }
+
+ /**
+ * Recycles an instance back to be reused.
+ * <p>
+ * <b>Note: You must not touch the object after calling this function.</b>
+ * </p>
+ *
+ * @throws IllegalStateException If the event is already recycled.
+ */
+ @Override
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ /**
+ * Clears the state of this instance.
+ *
+ * @hide
+ */
+ @Override
+ protected void clear() {
+ super.clear();
+ mEventType = 0;
+ mMovementGranularity = 0;
+ mAction = 0;
+ mContentChangeTypes = 0;
+ mPackageName = null;
+ mEventTime = 0;
+ if (mRecords != null) {
+ while (!mRecords.isEmpty()) {
+ AccessibilityRecord record = mRecords.remove(0);
+ record.recycle();
+ }
+ }
+ }
+
+ /**
+ * Creates a new instance from a {@link Parcel}.
+ *
+ * @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
+ */
+ public void initFromParcel(Parcel parcel) {
+ mSealed = (parcel.readInt() == 1);
+ mEventType = parcel.readInt();
+ mMovementGranularity = parcel.readInt();
+ mAction = parcel.readInt();
+ mContentChangeTypes = parcel.readInt();
+ mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mEventTime = parcel.readLong();
+ mConnectionId = parcel.readInt();
+ readAccessibilityRecordFromParcel(this, parcel);
+
+ // Read the records.
+ final int recordCount = parcel.readInt();
+ if (recordCount > 0) {
+ mRecords = new ArrayList<AccessibilityRecord>(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = AccessibilityRecord.obtain();
+ readAccessibilityRecordFromParcel(record, parcel);
+ record.mConnectionId = mConnectionId;
+ mRecords.add(record);
+ }
+ }
+ }
+
+ /**
+ * Reads an {@link AccessibilityRecord} from a parcel.
+ *
+ * @param record The record to initialize.
+ * @param parcel The parcel to read from.
+ */
+ private void readAccessibilityRecordFromParcel(AccessibilityRecord record,
+ Parcel parcel) {
+ record.mBooleanProperties = parcel.readInt();
+ record.mCurrentItemIndex = parcel.readInt();
+ record.mItemCount = parcel.readInt();
+ record.mFromIndex = parcel.readInt();
+ record.mToIndex = parcel.readInt();
+ record.mScrollX = parcel.readInt();
+ record.mScrollY = parcel.readInt();
+ record.mScrollDeltaX = parcel.readInt();
+ record.mScrollDeltaY = parcel.readInt();
+ record.mMaxScrollX = parcel.readInt();
+ record.mMaxScrollY = parcel.readInt();
+ record.mAddedCount = parcel.readInt();
+ record.mRemovedCount = parcel.readInt();
+ record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mParcelableData = parcel.readParcelable(null);
+ parcel.readList(record.mText, null);
+ record.mSourceWindowId = parcel.readInt();
+ record.mSourceNodeId = parcel.readLong();
+ record.mSealed = (parcel.readInt() == 1);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(isSealed() ? 1 : 0);
+ parcel.writeInt(mEventType);
+ parcel.writeInt(mMovementGranularity);
+ parcel.writeInt(mAction);
+ parcel.writeInt(mContentChangeTypes);
+ TextUtils.writeToParcel(mPackageName, parcel, 0);
+ parcel.writeLong(mEventTime);
+ parcel.writeInt(mConnectionId);
+ writeAccessibilityRecordToParcel(this, parcel, flags);
+
+ // Write the records.
+ final int recordCount = getRecordCount();
+ parcel.writeInt(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ writeAccessibilityRecordToParcel(record, parcel, flags);
+ }
+ }
+
+ /**
+ * Writes an {@link AccessibilityRecord} to a parcel.
+ *
+ * @param record The record to write.
+ * @param parcel The parcel to which to write.
+ */
+ private void writeAccessibilityRecordToParcel(AccessibilityRecord record, Parcel parcel,
+ int flags) {
+ parcel.writeInt(record.mBooleanProperties);
+ parcel.writeInt(record.mCurrentItemIndex);
+ parcel.writeInt(record.mItemCount);
+ parcel.writeInt(record.mFromIndex);
+ parcel.writeInt(record.mToIndex);
+ parcel.writeInt(record.mScrollX);
+ parcel.writeInt(record.mScrollY);
+ parcel.writeInt(record.mScrollDeltaX);
+ parcel.writeInt(record.mScrollDeltaY);
+ parcel.writeInt(record.mMaxScrollX);
+ parcel.writeInt(record.mMaxScrollY);
+ parcel.writeInt(record.mAddedCount);
+ parcel.writeInt(record.mRemovedCount);
+ TextUtils.writeToParcel(record.mClassName, parcel, flags);
+ TextUtils.writeToParcel(record.mContentDescription, parcel, flags);
+ TextUtils.writeToParcel(record.mBeforeText, parcel, flags);
+ parcel.writeParcelable(record.mParcelableData, flags);
+ parcel.writeList(record.mText);
+ parcel.writeInt(record.mSourceWindowId);
+ parcel.writeLong(record.mSourceNodeId);
+ parcel.writeInt(record.mSealed ? 1 : 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("EventType: ").append(eventTypeToString(mEventType));
+ builder.append("; EventTime: ").append(mEventTime);
+ builder.append("; PackageName: ").append(mPackageName);
+ builder.append("; MovementGranularity: ").append(mMovementGranularity);
+ builder.append("; Action: ").append(mAction);
+ builder.append(super.toString());
+ if (DEBUG) {
+ builder.append("\n");
+ builder.append("; ContentChangeTypes: ").append(
+ contentChangeTypesToString(mContentChangeTypes));
+ builder.append("; sourceWindowId: ").append(mSourceWindowId);
+ builder.append("; mSourceNodeId: ").append(mSourceNodeId);
+ for (int i = 0; i < getRecordCount(); i++) {
+ final AccessibilityRecord record = getRecord(i);
+ builder.append(" Record ");
+ builder.append(i);
+ builder.append(":");
+ builder.append(" [ ClassName: " + record.mClassName);
+ builder.append("; Text: " + record.mText);
+ builder.append("; ContentDescription: " + record.mContentDescription);
+ builder.append("; ItemCount: " + record.mItemCount);
+ builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex);
+ builder.append("; IsEnabled: " + record.isEnabled());
+ builder.append("; IsPassword: " + record.isPassword());
+ builder.append("; IsChecked: " + record.isChecked());
+ builder.append("; IsFullScreen: " + record.isFullScreen());
+ builder.append("; Scrollable: " + record.isScrollable());
+ builder.append("; BeforeText: " + record.mBeforeText);
+ builder.append("; FromIndex: " + record.mFromIndex);
+ builder.append("; ToIndex: " + record.mToIndex);
+ builder.append("; ScrollX: " + record.mScrollX);
+ builder.append("; ScrollY: " + record.mScrollY);
+ builder.append("; AddedCount: " + record.mAddedCount);
+ builder.append("; RemovedCount: " + record.mRemovedCount);
+ builder.append("; ParcelableData: " + record.mParcelableData);
+ builder.append(" ]");
+ builder.append("\n");
+ }
+ } else {
+ builder.append("; recordCount: ").append(getRecordCount());
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Returns the string representation of an event type. For example,
+ * {@link #TYPE_VIEW_CLICKED} is represented by the string TYPE_VIEW_CLICKED.
+ *
+ * @param eventType The event type
+ * @return The string representation.
+ */
+ public static String eventTypeToString(int eventType) {
+ if (eventType == TYPES_ALL_MASK) {
+ return "TYPES_ALL_MASK";
+ }
+ StringBuilder builder = new StringBuilder();
+ int eventTypeCount = 0;
+ while (eventType != 0) {
+ final int eventTypeFlag = 1 << Integer.numberOfTrailingZeros(eventType);
+ eventType &= ~eventTypeFlag;
+
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append(singleEventTypeToString(eventTypeFlag));
+
+ eventTypeCount++;
+ }
+ if (eventTypeCount > 1) {
+ builder.insert(0, '[');
+ builder.append(']');
+ }
+ return builder.toString();
+ }
+
+ private static String singleEventTypeToString(int eventType) {
+ switch (eventType) {
+ case TYPE_VIEW_CLICKED: return "TYPE_VIEW_CLICKED";
+ case TYPE_VIEW_LONG_CLICKED: return "TYPE_VIEW_LONG_CLICKED";
+ case TYPE_VIEW_SELECTED: return "TYPE_VIEW_SELECTED";
+ case TYPE_VIEW_FOCUSED: return "TYPE_VIEW_FOCUSED";
+ case TYPE_VIEW_TEXT_CHANGED: return "TYPE_VIEW_TEXT_CHANGED";
+ case TYPE_WINDOW_STATE_CHANGED: return "TYPE_WINDOW_STATE_CHANGED";
+ case TYPE_VIEW_HOVER_ENTER: return "TYPE_VIEW_HOVER_ENTER";
+ case TYPE_VIEW_HOVER_EXIT: return "TYPE_VIEW_HOVER_EXIT";
+ case TYPE_NOTIFICATION_STATE_CHANGED: return "TYPE_NOTIFICATION_STATE_CHANGED";
+ case TYPE_TOUCH_EXPLORATION_GESTURE_START: {
+ return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
+ }
+ case TYPE_TOUCH_EXPLORATION_GESTURE_END: return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
+ case TYPE_WINDOW_CONTENT_CHANGED: return "TYPE_WINDOW_CONTENT_CHANGED";
+ case TYPE_VIEW_TEXT_SELECTION_CHANGED: return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
+ case TYPE_VIEW_SCROLLED: return "TYPE_VIEW_SCROLLED";
+ case TYPE_ANNOUNCEMENT: return "TYPE_ANNOUNCEMENT";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED: return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
+ }
+ case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: {
+ return "TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY";
+ }
+ case TYPE_GESTURE_DETECTION_START: return "TYPE_GESTURE_DETECTION_START";
+ case TYPE_GESTURE_DETECTION_END: return "TYPE_GESTURE_DETECTION_END";
+ case TYPE_TOUCH_INTERACTION_START: return "TYPE_TOUCH_INTERACTION_START";
+ case TYPE_TOUCH_INTERACTION_END: return "TYPE_TOUCH_INTERACTION_END";
+ case TYPE_WINDOWS_CHANGED: return "TYPE_WINDOWS_CHANGED";
+ case TYPE_VIEW_CONTEXT_CLICKED: return "TYPE_VIEW_CONTEXT_CLICKED";
+ case TYPE_ASSIST_READING_CONTEXT: return "TYPE_ASSIST_READING_CONTEXT";
+ default: return Integer.toHexString(eventType);
+ }
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AccessibilityEvent> CREATOR =
+ new Parcelable.Creator<AccessibilityEvent>() {
+ public AccessibilityEvent createFromParcel(Parcel parcel) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.initFromParcel(parcel);
+ return event;
+ }
+
+ public AccessibilityEvent[] newArray(int size) {
+ return new AccessibilityEvent[size];
+ }
+ };
+}
diff --git a/android/view/accessibility/AccessibilityEventSource.java b/android/view/accessibility/AccessibilityEventSource.java
new file mode 100644
index 00000000..525ba9e1
--- /dev/null
+++ b/android/view/accessibility/AccessibilityEventSource.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.accessibility;
+
+/**
+ * This interface is implemented by classes source of {@link AccessibilityEvent}s.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about making applications accessible, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ */
+public interface AccessibilityEventSource {
+
+ /**
+ * Handles the request for sending an {@link AccessibilityEvent} given
+ * the event type. The method must first check if accessibility is on
+ * via calling {@link AccessibilityManager#isEnabled() AccessibilityManager.isEnabled()},
+ * obtain an {@link AccessibilityEvent} from the event pool through calling
+ * {@link AccessibilityEvent#obtain(int) AccessibilityEvent.obtain(int)}, populate the
+ * event, and send it for dispatch via calling
+ * {@link AccessibilityManager#sendAccessibilityEvent(AccessibilityEvent)
+ * AccessibilityManager.sendAccessibilityEvent(AccessibilityEvent)}.
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ *
+ * @param eventType The event type.
+ */
+ public void sendAccessibilityEvent(int eventType);
+
+ /**
+ * Handles the request for sending an {@link AccessibilityEvent}. The
+ * method does not guarantee to check if accessibility is on before
+ * sending the event for dispatch. It is responsibility of the caller
+ * to do the check via calling {@link AccessibilityManager#isEnabled()
+ * AccessibilityManager.isEnabled()}.
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ *
+ * @param event The event.
+ */
+ public void sendAccessibilityEventUnchecked(AccessibilityEvent event);
+}
diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java
new file mode 100644
index 00000000..19213ca0
--- /dev/null
+++ b/android/view/accessibility/AccessibilityInteractionClient.java
@@ -0,0 +1,825 @@
+/*
+ ** Copyright 2011, 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.accessibility;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class is a singleton that performs accessibility interaction
+ * which is it queries remote view hierarchies about snapshots of their
+ * views as well requests from these hierarchies to perform certain
+ * actions on their views.
+ *
+ * Rationale: The content retrieval APIs are synchronous from a client's
+ * perspective but internally they are asynchronous. The client thread
+ * calls into the system requesting an action and providing a callback
+ * to receive the result after which it waits up to a timeout for that
+ * result. The system enforces security and the delegates the request
+ * to a given view hierarchy where a message is posted (from a binder
+ * thread) describing what to be performed by the main UI thread the
+ * result of which it delivered via the mentioned callback. However,
+ * the blocked client thread and the main UI thread of the target view
+ * hierarchy can be the same thread, for example an accessibility service
+ * and an activity run in the same process, thus they are executed on the
+ * same main thread. In such a case the retrieval will fail since the UI
+ * thread that has to process the message describing the work to be done
+ * is blocked waiting for a result is has to compute! To avoid this scenario
+ * when making a call the client also passes its process and thread ids so
+ * the accessed view hierarchy can detect if the client making the request
+ * is running in its main UI thread. In such a case the view hierarchy,
+ * specifically the binder thread performing the IPC to it, does not post a
+ * message to be run on the UI thread but passes it to the singleton
+ * interaction client through which all interactions occur and the latter is
+ * responsible to execute the message before starting to wait for the
+ * asynchronous result delivered via the callback. In this case the expected
+ * result is already received so no waiting is performed.
+ *
+ * @hide
+ */
+public final class AccessibilityInteractionClient
+ extends IAccessibilityInteractionConnectionCallback.Stub {
+
+ public static final int NO_ID = -1;
+
+ private static final String LOG_TAG = "AccessibilityInteractionClient";
+
+ private static final boolean DEBUG = false;
+
+ private static final boolean CHECK_INTEGRITY = true;
+
+ private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
+
+ private static final Object sStaticLock = new Object();
+
+ private static final LongSparseArray<AccessibilityInteractionClient> sClients =
+ new LongSparseArray<>();
+
+ private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
+
+ private final Object mInstanceLock = new Object();
+
+ private volatile int mInteractionId = -1;
+
+ private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
+
+ private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult;
+
+ private boolean mPerformAccessibilityActionResult;
+
+ private Message mSameThreadMessage;
+
+ private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
+ new SparseArray<>();
+
+ private static final AccessibilityCache sAccessibilityCache =
+ new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
+
+ /**
+ * @return The client for the current thread.
+ */
+ public static AccessibilityInteractionClient getInstance() {
+ final long threadId = Thread.currentThread().getId();
+ return getInstanceForThread(threadId);
+ }
+
+ /**
+ * <strong>Note:</strong> We keep one instance per interrogating thread since
+ * the instance contains state which can lead to undesired thread interleavings.
+ * We do not have a thread local variable since other threads should be able to
+ * look up the correct client knowing a thread id. See ViewRootImpl for details.
+ *
+ * @return The client for a given <code>threadId</code>.
+ */
+ public static AccessibilityInteractionClient getInstanceForThread(long threadId) {
+ synchronized (sStaticLock) {
+ AccessibilityInteractionClient client = sClients.get(threadId);
+ if (client == null) {
+ client = new AccessibilityInteractionClient();
+ sClients.put(threadId, client);
+ }
+ return client;
+ }
+ }
+
+ private AccessibilityInteractionClient() {
+ /* reducing constructor visibility */
+ }
+
+ /**
+ * Sets the message to be processed if the interacted view hierarchy
+ * and the interacting client are running in the same thread.
+ *
+ * @param message The message.
+ */
+ public void setSameThreadMessage(Message message) {
+ synchronized (mInstanceLock) {
+ mSameThreadMessage = message;
+ mInstanceLock.notifyAll();
+ }
+ }
+
+ /**
+ * Gets the root {@link AccessibilityNodeInfo} in the currently active window.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
+ return findAccessibilityNodeInfoByAccessibilityId(connectionId,
+ AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
+ false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+ }
+
+ /**
+ * Gets the info for a window.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @return The {@link AccessibilityWindowInfo}.
+ */
+ public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ AccessibilityWindowInfo window = sAccessibilityCache.getWindow(
+ accessibilityWindowId);
+ if (window != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache hit");
+ }
+ return window;
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache miss");
+ }
+ final long identityToken = Binder.clearCallingIdentity();
+ window = connection.getWindow(accessibilityWindowId);
+ Binder.restoreCallingIdentity(identityToken);
+ if (window != null) {
+ sAccessibilityCache.addWindow(window);
+ return window;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote getWindow", re);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the info for all windows.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @return The {@link AccessibilityWindowInfo} list.
+ */
+ public List<AccessibilityWindowInfo> getWindows(int connectionId) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows();
+ if (windows != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Windows cache hit");
+ }
+ return windows;
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Windows cache miss");
+ }
+ final long identityToken = Binder.clearCallingIdentity();
+ windows = connection.getWindows();
+ Binder.restoreCallingIdentity(identityToken);
+ if (windows != null) {
+ sAccessibilityCache.setWindows(windows);
+ return windows;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote getWindows", re);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param bypassCache Whether to bypass the cache while looking for the node.
+ * @param prefetchFlags flags to guide prefetching.
+ * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ */
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
+ int prefetchFlags, Bundle arguments) {
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
+ && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
+ throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
+ + " requires FLAG_PREFETCH_PREDECESSORS");
+ }
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ if (!bypassCache) {
+ AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
+ accessibilityWindowId, accessibilityNodeId);
+ if (cachedInfo != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Node cache hit");
+ }
+ return cachedInfo;
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Node cache miss");
+ }
+ }
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final long identityToken = Binder.clearCallingIdentity();
+ final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
+ accessibilityWindowId, accessibilityNodeId, interactionId, this,
+ prefetchFlags, Thread.currentThread().getId(), arguments);
+ Binder.restoreCallingIdentity(identityToken);
+ if (success) {
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ if (infos != null && !infos.isEmpty()) {
+ for (int i = 1; i < infos.size(); i++) {
+ infos.get(i).recycle();
+ }
+ return infos.get(0);
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote"
+ + " findAccessibilityNodeInfoByAccessibilityId", re);
+ }
+ return null;
+ }
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
+ * the window whose id is specified and starts from the node whose accessibility
+ * id is specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, String viewId) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final long identityToken = Binder.clearCallingIdentity();
+ final boolean success = connection.findAccessibilityNodeInfosByViewId(
+ accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+ Thread.currentThread().getId());
+ Binder.restoreCallingIdentity(identityToken);
+ if (success) {
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+ interactionId);
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote"
+ + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
+ * insensitive containment. The search is performed in the window whose
+ * id is specified and starts from the node whose accessibility id is
+ * specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param text The searched text.
+ * @return A list of found {@link AccessibilityNodeInfo}s.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, String text) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final long identityToken = Binder.clearCallingIdentity();
+ final boolean success = connection.findAccessibilityNodeInfosByText(
+ accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
+ Thread.currentThread().getId());
+ Binder.restoreCallingIdentity(identityToken);
+ if (success) {
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
+ interactionId);
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote"
+ + " findAccessibilityNodeInfosByViewText", re);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the
+ * specified focus type. The search is performed in the window whose id is specified
+ * and starts from the node whose accessibility id is specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param focusType The focus type.
+ * @return The accessibility focused {@link AccessibilityNodeInfo}.
+ */
+ public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId,
+ long accessibilityNodeId, int focusType) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final long identityToken = Binder.clearCallingIdentity();
+ final boolean success = connection.findFocus(accessibilityWindowId,
+ accessibilityNodeId, focusType, interactionId, this,
+ Thread.currentThread().getId());
+ Binder.restoreCallingIdentity(identityToken);
+ if (success) {
+ AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ return info;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote findFocus", re);
+ }
+ return null;
+ }
+
+ /**
+ * Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}.
+ * The search is performed in the window whose id is specified and starts from the
+ * node whose accessibility id is specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param direction The direction in which to search for focusable.
+ * @return The accessibility focused {@link AccessibilityNodeInfo}.
+ */
+ public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWindowId,
+ long accessibilityNodeId, int direction) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final long identityToken = Binder.clearCallingIdentity();
+ final boolean success = connection.focusSearch(accessibilityWindowId,
+ accessibilityNodeId, direction, interactionId, this,
+ Thread.currentThread().getId());
+ Binder.restoreCallingIdentity(identityToken);
+ if (success) {
+ AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ return info;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
+ }
+ return null;
+ }
+
+ /**
+ * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param action The action to perform.
+ * @param arguments Optional action arguments.
+ * @return Whether the action was performed.
+ */
+ public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId,
+ long accessibilityNodeId, int action, Bundle arguments) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final long identityToken = Binder.clearCallingIdentity();
+ final boolean success = connection.performAccessibilityAction(
+ accessibilityWindowId, accessibilityNodeId, action, arguments,
+ interactionId, this, Thread.currentThread().getId());
+ Binder.restoreCallingIdentity(identityToken);
+ if (success) {
+ return getPerformAccessibilityActionResultAndClear(interactionId);
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
+ }
+ return false;
+ }
+
+ public void clearCache() {
+ sAccessibilityCache.clear();
+ }
+
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ sAccessibilityCache.onAccessibilityEvent(event);
+ }
+
+ /**
+ * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
+ *
+ * @param interactionId The interaction id to match the result with the request.
+ * @return The result {@link AccessibilityNodeInfo}.
+ */
+ private AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) {
+ synchronized (mInstanceLock) {
+ final boolean success = waitForResultTimedLocked(interactionId);
+ AccessibilityNodeInfo result = success ? mFindAccessibilityNodeInfoResult : null;
+ clearResultLocked();
+ return result;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
+ int interactionId) {
+ synchronized (mInstanceLock) {
+ if (interactionId > mInteractionId) {
+ mFindAccessibilityNodeInfoResult = info;
+ mInteractionId = interactionId;
+ }
+ mInstanceLock.notifyAll();
+ }
+ }
+
+ /**
+ * Gets the the result of an async request that returns {@link AccessibilityNodeInfo}s.
+ *
+ * @param interactionId The interaction id to match the result with the request.
+ * @return The result {@link AccessibilityNodeInfo}s.
+ */
+ private List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear(
+ int interactionId) {
+ synchronized (mInstanceLock) {
+ final boolean success = waitForResultTimedLocked(interactionId);
+ List<AccessibilityNodeInfo> result = null;
+ if (success) {
+ result = mFindAccessibilityNodeInfosResult;
+ } else {
+ result = Collections.emptyList();
+ }
+ clearResultLocked();
+ if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) {
+ checkFindAccessibilityNodeInfoResultIntegrity(result);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
+ int interactionId) {
+ synchronized (mInstanceLock) {
+ if (interactionId > mInteractionId) {
+ if (infos != null) {
+ // If the call is not an IPC, i.e. it is made from the same process, we need to
+ // instantiate new result list to avoid passing internal instances to clients.
+ final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid());
+ if (!isIpcCall) {
+ mFindAccessibilityNodeInfosResult = new ArrayList<>(infos);
+ } else {
+ mFindAccessibilityNodeInfosResult = infos;
+ }
+ } else {
+ mFindAccessibilityNodeInfosResult = Collections.emptyList();
+ }
+ mInteractionId = interactionId;
+ }
+ mInstanceLock.notifyAll();
+ }
+ }
+
+ /**
+ * Gets the result of a request to perform an accessibility action.
+ *
+ * @param interactionId The interaction id to match the result with the request.
+ * @return Whether the action was performed.
+ */
+ private boolean getPerformAccessibilityActionResultAndClear(int interactionId) {
+ synchronized (mInstanceLock) {
+ final boolean success = waitForResultTimedLocked(interactionId);
+ final boolean result = success ? mPerformAccessibilityActionResult : false;
+ clearResultLocked();
+ return result;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
+ synchronized (mInstanceLock) {
+ if (interactionId > mInteractionId) {
+ mPerformAccessibilityActionResult = succeeded;
+ mInteractionId = interactionId;
+ }
+ mInstanceLock.notifyAll();
+ }
+ }
+
+ /**
+ * Clears the result state.
+ */
+ private void clearResultLocked() {
+ mInteractionId = -1;
+ mFindAccessibilityNodeInfoResult = null;
+ mFindAccessibilityNodeInfosResult = null;
+ mPerformAccessibilityActionResult = false;
+ }
+
+ /**
+ * Waits up to a given bound for a result of a request and returns it.
+ *
+ * @param interactionId The interaction id to match the result with the request.
+ * @return Whether the result was received.
+ */
+ private boolean waitForResultTimedLocked(int interactionId) {
+ long waitTimeMillis = TIMEOUT_INTERACTION_MILLIS;
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ try {
+ Message sameProcessMessage = getSameProcessMessageAndClear();
+ if (sameProcessMessage != null) {
+ sameProcessMessage.getTarget().handleMessage(sameProcessMessage);
+ }
+
+ if (mInteractionId == interactionId) {
+ return true;
+ }
+ if (mInteractionId > interactionId) {
+ return false;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
+ if (waitTimeMillis <= 0) {
+ return false;
+ }
+ mInstanceLock.wait(waitTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+
+ /**
+ * Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
+ *
+ * @param info The info.
+ * @param connectionId The id of the connection to the system.
+ */
+ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
+ int connectionId) {
+ if (info != null) {
+ info.setConnectionId(connectionId);
+ info.setSealed(true);
+ sAccessibilityCache.add(info);
+ }
+ }
+
+ /**
+ * Finalize {@link AccessibilityNodeInfo}s before passing them to the client.
+ *
+ * @param infos The {@link AccessibilityNodeInfo}s.
+ * @param connectionId The id of the connection to the system.
+ */
+ private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
+ int connectionId) {
+ if (infos != null) {
+ final int infosCount = infos.size();
+ for (int i = 0; i < infosCount; i++) {
+ AccessibilityNodeInfo info = infos.get(i);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ }
+ }
+ }
+
+ /**
+ * Gets the message stored if the interacted and interacting
+ * threads are the same.
+ *
+ * @return The message.
+ */
+ private Message getSameProcessMessageAndClear() {
+ synchronized (mInstanceLock) {
+ Message result = mSameThreadMessage;
+ mSameThreadMessage = null;
+ return result;
+ }
+ }
+
+ /**
+ * Gets a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ * @return The cached connection if such.
+ */
+ public IAccessibilityServiceConnection getConnection(int connectionId) {
+ synchronized (sConnectionCache) {
+ return sConnectionCache.get(connectionId);
+ }
+ }
+
+ /**
+ * Adds a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ * @param connection The connection.
+ */
+ public void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
+ synchronized (sConnectionCache) {
+ sConnectionCache.put(connectionId, connection);
+ }
+ }
+
+ /**
+ * Removes a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ */
+ public void removeConnection(int connectionId) {
+ synchronized (sConnectionCache) {
+ sConnectionCache.remove(connectionId);
+ }
+ }
+
+ /**
+ * Checks whether the infos are a fully connected tree with no duplicates.
+ *
+ * @param infos The result list to check.
+ */
+ private void checkFindAccessibilityNodeInfoResultIntegrity(List<AccessibilityNodeInfo> infos) {
+ if (infos.size() == 0) {
+ return;
+ }
+ // Find the root node.
+ AccessibilityNodeInfo root = infos.get(0);
+ final int infoCount = infos.size();
+ for (int i = 1; i < infoCount; i++) {
+ for (int j = i; j < infoCount; j++) {
+ AccessibilityNodeInfo candidate = infos.get(j);
+ if (root.getParentNodeId() == candidate.getSourceNodeId()) {
+ root = candidate;
+ break;
+ }
+ }
+ }
+ if (root == null) {
+ Log.e(LOG_TAG, "No root.");
+ }
+ // Check for duplicates.
+ HashSet<AccessibilityNodeInfo> seen = new HashSet<>();
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
+ fringe.add(root);
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+ if (!seen.add(current)) {
+ Log.e(LOG_TAG, "Duplicate node.");
+ return;
+ }
+ final int childCount = current.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final long childId = current.getChildId(i);
+ for (int j = 0; j < infoCount; j++) {
+ AccessibilityNodeInfo child = infos.get(j);
+ if (child.getSourceNodeId() == childId) {
+ fringe.add(child);
+ }
+ }
+ }
+ }
+ final int disconnectedCount = infos.size() - seen.size();
+ if (disconnectedCount > 0) {
+ Log.e(LOG_TAG, disconnectedCount + " Disconnected nodes.");
+ }
+ }
+}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
new file mode 100644
index 00000000..0b9bc576
--- /dev/null
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -0,0 +1,1138 @@
+/*
+ * Copyright (C) 2009 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.accessibility;
+
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
+
+import android.Manifest;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.IWindow;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IntPair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
+ * and provides facilities for querying the accessibility state of the system.
+ * Accessibility events are generated when something notable happens in the user interface,
+ * for example an {@link android.app.Activity} starts, the focus or selection of a
+ * {@link android.view.View} changes etc. Parties interested in handling accessibility
+ * events implement and register an accessibility service which extends
+ * {@link android.accessibilityservice.AccessibilityService}.
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
+ */
+@SystemService(Context.ACCESSIBILITY_SERVICE)
+public final class AccessibilityManager {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = "AccessibilityManager";
+
+ /** @hide */
+ public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
+
+ /** @hide */
+ public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+
+ /** @hide */
+ public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
+
+ /** @hide */
+ public static final int DALTONIZER_DISABLED = -1;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
+
+ /** @hide */
+ public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
+
+ /** @hide */
+ public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+
+ /**
+ * Activity action: Launch UI to manage which accessibility service or feature is assigned
+ * to the navigation bar Accessibility button.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+ "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
+ static final Object sInstanceSync = new Object();
+
+ private static AccessibilityManager sInstance;
+
+ private final Object mLock = new Object();
+
+ private IAccessibilityManager mService;
+
+ final int mUserId;
+
+ final Handler mHandler;
+
+ final Handler.Callback mCallback;
+
+ boolean mIsEnabled;
+
+ int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+
+ boolean mIsTouchExplorationEnabled;
+
+ boolean mIsHighTextContrastEnabled;
+
+ private final ArrayMap<AccessibilityStateChangeListener, Handler>
+ mAccessibilityStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<TouchExplorationStateChangeListener, Handler>
+ mTouchExplorationStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<HighTextContrastChangeListener, Handler>
+ mHighTextContrastStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
+ mServicesStateChangeListeners = new ArrayMap<>();
+
+ /**
+ * Map from a view's accessibility id to the list of request preparers set for that view
+ */
+ private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
+
+ /**
+ * Listener for the system accessibility state. To listen for changes to the
+ * accessibility state on the device, implement this interface and register
+ * it with the system by calling {@link #addAccessibilityStateChangeListener}.
+ */
+ public interface AccessibilityStateChangeListener {
+
+ /**
+ * Called when the accessibility enabled state changes.
+ *
+ * @param enabled Whether accessibility is enabled.
+ */
+ void onAccessibilityStateChanged(boolean enabled);
+ }
+
+ /**
+ * Listener for the system touch exploration state. To listen for changes to
+ * the touch exploration state on the device, implement this interface and
+ * register it with the system by calling
+ * {@link #addTouchExplorationStateChangeListener}.
+ */
+ public interface TouchExplorationStateChangeListener {
+
+ /**
+ * Called when the touch exploration enabled state changes.
+ *
+ * @param enabled Whether touch exploration is enabled.
+ */
+ void onTouchExplorationStateChanged(boolean enabled);
+ }
+
+ /**
+ * Listener for changes to the state of accessibility services. Changes include services being
+ * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
+ * {@see #addAccessibilityServicesStateChangeListener}.
+ *
+ * @hide
+ */
+ public interface AccessibilityServicesStateChangeListener {
+
+ /**
+ * Called when the state of accessibility services changes.
+ *
+ * @param manager The manager that is calling back
+ */
+ void onAccessibilityServicesStateChanged(AccessibilityManager manager);
+ }
+
+ /**
+ * Listener for the system high text contrast state. To listen for changes to
+ * the high text contrast state on the device, implement this interface and
+ * register it with the system by calling
+ * {@link #addHighTextContrastStateChangeListener}.
+ *
+ * @hide
+ */
+ public interface HighTextContrastChangeListener {
+
+ /**
+ * Called when the high text contrast enabled state changes.
+ *
+ * @param enabled Whether high text contrast is enabled.
+ */
+ void onHighTextContrastStateChanged(boolean enabled);
+ }
+
+ private final IAccessibilityManagerClient.Stub mClient =
+ new IAccessibilityManagerClient.Stub() {
+ @Override
+ public void setState(int state) {
+ // We do not want to change this immediately as the application may
+ // have already checked that accessibility is on and fired an event,
+ // that is now propagating up the view tree, Hence, if accessibility
+ // is now off an exception will be thrown. We want to have the exception
+ // enforcement to guard against apps that fire unnecessary accessibility
+ // events when accessibility is off.
+ mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
+ }
+
+ @Override
+ public void notifyServicesStateChanged() {
+ final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mServicesStateChangeListeners.isEmpty()) {
+ return;
+ }
+ listeners = new ArrayMap<>(mServicesStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final AccessibilityServicesStateChangeListener listener =
+ mServicesStateChangeListeners.keyAt(i);
+ mServicesStateChangeListeners.valueAt(i).post(() -> listener
+ .onAccessibilityServicesStateChanged(AccessibilityManager.this));
+ }
+ }
+
+ @Override
+ public void setRelevantEventTypes(int eventTypes) {
+ mRelevantEventTypes = eventTypes;
+ }
+ };
+
+ /**
+ * Get an AccessibilityManager instance (create one if necessary).
+ *
+ * @param context Context in which this manager operates.
+ *
+ * @hide
+ */
+ public static AccessibilityManager getInstance(Context context) {
+ synchronized (sInstanceSync) {
+ if (sInstance == null) {
+ final int userId;
+ if (Binder.getCallingUid() == Process.SYSTEM_UID
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS)
+ == PackageManager.PERMISSION_GRANTED
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ userId = UserHandle.USER_CURRENT;
+ } else {
+ userId = UserHandle.myUserId();
+ }
+ sInstance = new AccessibilityManager(context, null, userId);
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param context A {@link Context}.
+ * @param service An interface to the backing service.
+ * @param userId User id under which to run.
+ *
+ * @hide
+ */
+ public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
+ // Constructor can't be chained because we can't create an instance of an inner class
+ // before calling another constructor.
+ mCallback = new MyCallback();
+ mHandler = new Handler(context.getMainLooper(), mCallback);
+ mUserId = userId;
+ synchronized (mLock) {
+ tryConnectToServiceLocked(service);
+ }
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param handler The handler to use
+ * @param service An interface to the backing service.
+ * @param userId User id under which to run.
+ *
+ * @hide
+ */
+ public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+ mCallback = new MyCallback();
+ mHandler = handler;
+ mUserId = userId;
+ synchronized (mLock) {
+ tryConnectToServiceLocked(service);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public IAccessibilityManagerClient getClient() {
+ return mClient;
+ }
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public Handler.Callback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Returns if the accessibility in the system is enabled.
+ *
+ * @return True if accessibility is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsEnabled;
+ }
+ }
+
+ /**
+ * Returns if the touch exploration in the system is enabled.
+ *
+ * @return True if touch exploration is enabled, false otherwise.
+ */
+ public boolean isTouchExplorationEnabled() {
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsTouchExplorationEnabled;
+ }
+ }
+
+ /**
+ * Returns if the high text contrast in the system is enabled.
+ * <p>
+ * <strong>Note:</strong> You need to query this only if you application is
+ * doing its own rendering and does not rely on the platform rendering pipeline.
+ * </p>
+ *
+ * @return True if high text contrast is enabled, false otherwise.
+ *
+ * @hide
+ */
+ public boolean isHighTextContrastEnabled() {
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsHighTextContrastEnabled;
+ }
+ }
+
+ /**
+ * Sends an {@link AccessibilityEvent}.
+ *
+ * @param event The event to send.
+ *
+ * @throws IllegalStateException if accessibility is not enabled.
+ *
+ * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
+ * events is through calling
+ * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+ * instead of this method to allow predecessors to augment/filter events sent by
+ * their descendants.
+ */
+ public void sendAccessibilityEvent(AccessibilityEvent event) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ Looper myLooper = Looper.myLooper();
+ if (myLooper == Looper.getMainLooper()) {
+ throw new IllegalStateException(
+ "Accessibility off. Did you forget to check that?");
+ } else {
+ // If we're not running on the thread with the main looper, it's possible for
+ // the state of accessibility to change between checking isEnabled and
+ // calling this method. So just log the error rather than throwing the
+ // exception.
+ Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
+ return;
+ }
+ }
+ if ((event.getEventType() & mRelevantEventTypes) == 0) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
+ + " that is not among "
+ + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
+ }
+ return;
+ }
+ userId = mUserId;
+ }
+ try {
+ event.setEventTime(SystemClock.uptimeMillis());
+ // it is possible that this manager is in the same process as the service but
+ // client using it is called through Binder from another process. Example: MMS
+ // app adds a SMS notification and the NotificationManagerService calls this method
+ long identityToken = Binder.clearCallingIdentity();
+ service.sendAccessibilityEvent(event, userId);
+ Binder.restoreCallingIdentity(identityToken);
+ if (DEBUG) {
+ Log.i(LOG_TAG, event + " sent");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error during sending " + event + " ", re);
+ } finally {
+ event.recycle();
+ }
+ }
+
+ /**
+ * Requests feedback interruption from all accessibility services.
+ */
+ public void interrupt() {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ Looper myLooper = Looper.myLooper();
+ if (myLooper == Looper.getMainLooper()) {
+ throw new IllegalStateException(
+ "Accessibility off. Did you forget to check that?");
+ } else {
+ // If we're not running on the thread with the main looper, it's possible for
+ // the state of accessibility to change between checking isEnabled and
+ // calling this method. So just log the error rather than throwing the
+ // exception.
+ Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
+ return;
+ }
+ }
+ userId = mUserId;
+ }
+ try {
+ service.interrupt(userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requested interrupt from all services");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
+ }
+ }
+
+ /**
+ * Returns the {@link ServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link ServiceInfo}s.
+ *
+ * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
+ */
+ @Deprecated
+ public List<ServiceInfo> getAccessibilityServiceList() {
+ List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
+ List<ServiceInfo> services = new ArrayList<>();
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityServiceInfo info = infos.get(i);
+ services.add(info.getResolveInfo().serviceInfo);
+ }
+ return Collections.unmodifiableList(services);
+ }
+
+ /**
+ * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ */
+ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
+ List<AccessibilityServiceInfo> services = null;
+ try {
+ services = service.getInstalledAccessibilityServiceList(userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
+ * for a given feedback type.
+ *
+ * @param feedbackTypeFlags The feedback type flags.
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ *
+ * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
+ * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
+ * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
+ * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
+ * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+ * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
+ */
+ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+ int feedbackTypeFlags) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
+ List<AccessibilityServiceInfo> services = null;
+ try {
+ services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Registers an {@link AccessibilityStateChangeListener} for changes in
+ * the global accessibility state of the system. Equivalent to calling
+ * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+ * with a null handler.
+ *
+ * @param listener The listener.
+ * @return Always returns {@code true}.
+ */
+ public boolean addAccessibilityStateChangeListener(
+ @NonNull AccessibilityStateChangeListener listener) {
+ addAccessibilityStateChangeListener(listener, null);
+ return true;
+ }
+
+ /**
+ * Registers an {@link AccessibilityStateChangeListener} for changes in
+ * the global accessibility state of the system. If the listener has already been registered,
+ * the handler used to call it back is updated.
+ *
+ * @param listener The listener.
+ * @param handler The handler on which the listener should be called back, or {@code null}
+ * for a callback on the process's main handler.
+ */
+ public void addAccessibilityStateChangeListener(
+ @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mAccessibilityStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+
+ /**
+ * Unregisters an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if the listener was previously registered.
+ */
+ public boolean removeAccessibilityStateChangeListener(
+ @NonNull AccessibilityStateChangeListener listener) {
+ synchronized (mLock) {
+ int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+ mAccessibilityStateChangeListeners.remove(listener);
+ return (index >= 0);
+ }
+ }
+
+ /**
+ * Registers a {@link TouchExplorationStateChangeListener} for changes in
+ * the global touch exploration state of the system. Equivalent to calling
+ * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+ * with a null handler.
+ *
+ * @param listener The listener.
+ * @return Always returns {@code true}.
+ */
+ public boolean addTouchExplorationStateChangeListener(
+ @NonNull TouchExplorationStateChangeListener listener) {
+ addTouchExplorationStateChangeListener(listener, null);
+ return true;
+ }
+
+ /**
+ * Registers an {@link TouchExplorationStateChangeListener} for changes in
+ * the global touch exploration state of the system. If the listener has already been
+ * registered, the handler used to call it back is updated.
+ *
+ * @param listener The listener.
+ * @param handler The handler on which the listener should be called back, or {@code null}
+ * for a callback on the process's main handler.
+ */
+ public void addTouchExplorationStateChangeListener(
+ @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mTouchExplorationStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+
+ /**
+ * Unregisters a {@link TouchExplorationStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if listener was previously registered.
+ */
+ public boolean removeTouchExplorationStateChangeListener(
+ @NonNull TouchExplorationStateChangeListener listener) {
+ synchronized (mLock) {
+ int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
+ mTouchExplorationStateChangeListeners.remove(listener);
+ return (index >= 0);
+ }
+ }
+
+ /**
+ * Registers a {@link AccessibilityServicesStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @param handler The handler on which the listener should be called back, or {@code null}
+ * for a callback on the process's main handler.
+ * @hide
+ */
+ public void addAccessibilityServicesStateChangeListener(
+ @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mServicesStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+
+ /**
+ * Unregisters a {@link AccessibilityServicesStateChangeListener}.
+ *
+ * @param listener The listener.
+ *
+ * @hide
+ */
+ public void removeAccessibilityServicesStateChangeListener(
+ @NonNull AccessibilityServicesStateChangeListener listener) {
+ // Final CopyOnWriteArrayList - no lock needed.
+ mServicesStateChangeListeners.remove(listener);
+ }
+
+ /**
+ * Registers a {@link AccessibilityRequestPreparer}.
+ */
+ public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ mRequestPreparerLists = new SparseArray<>(1);
+ }
+ int id = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
+ if (requestPreparerList == null) {
+ requestPreparerList = new ArrayList<>(1);
+ mRequestPreparerLists.put(id, requestPreparerList);
+ }
+ requestPreparerList.add(preparer);
+ }
+
+ /**
+ * Unregisters a {@link AccessibilityRequestPreparer}.
+ */
+ public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ return;
+ }
+ int viewId = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
+ if (requestPreparerList != null) {
+ requestPreparerList.remove(preparer);
+ if (requestPreparerList.isEmpty()) {
+ mRequestPreparerLists.remove(viewId);
+ }
+ }
+ }
+
+ /**
+ * Get the preparers that are registered for an accessibility ID
+ *
+ * @param id The ID of interest
+ * @return The list of preparers, or {@code null} if there are none.
+ *
+ * @hide
+ */
+ public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
+ if (mRequestPreparerLists == null) {
+ return null;
+ }
+ return mRequestPreparerLists.get(id);
+ }
+
+ /**
+ * Registers a {@link HighTextContrastChangeListener} for changes in
+ * the global high text contrast state of the system.
+ *
+ * @param listener The listener.
+ *
+ * @hide
+ */
+ public void addHighTextContrastStateChangeListener(
+ @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mHighTextContrastStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+
+ /**
+ * Unregisters a {@link HighTextContrastChangeListener}.
+ *
+ * @param listener The listener.
+ *
+ * @hide
+ */
+ public void removeHighTextContrastStateChangeListener(
+ @NonNull HighTextContrastChangeListener listener) {
+ synchronized (mLock) {
+ mHighTextContrastStateChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Check if the accessibility volume stream is active.
+ *
+ * @return True if accessibility volume is active (i.e. some service has requested it). False
+ * otherwise.
+ * @hide
+ */
+ public boolean isAccessibilityVolumeStreamActive() {
+ List<AccessibilityServiceInfo> serviceInfos =
+ getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ for (int i = 0; i < serviceInfos.size(); i++) {
+ if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Report a fingerprint gesture to accessibility. Only available for the system process.
+ *
+ * @param keyCode The key code of the gesture
+ * @return {@code true} if accessibility consumes the event. {@code false} if not.
+ * @hide
+ */
+ public boolean sendFingerprintGesture(int keyCode) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+ try {
+ return service.sendFingerprintGesture(keyCode);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Sets the current state and notifies listeners, if necessary.
+ *
+ * @param stateFlags The state flags.
+ */
+ private void setStateLocked(int stateFlags) {
+ final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
+ final boolean touchExplorationEnabled =
+ (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
+ final boolean highTextContrastEnabled =
+ (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
+
+ final boolean wasEnabled = mIsEnabled;
+ final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
+ final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+
+ // Ensure listeners get current state from isZzzEnabled() calls.
+ mIsEnabled = enabled;
+ mIsTouchExplorationEnabled = touchExplorationEnabled;
+ mIsHighTextContrastEnabled = highTextContrastEnabled;
+
+ if (wasEnabled != enabled) {
+ notifyAccessibilityStateChanged();
+ }
+
+ if (wasTouchExplorationEnabled != touchExplorationEnabled) {
+ notifyTouchExplorationStateChanged();
+ }
+
+ if (wasHighTextContrastEnabled != highTextContrastEnabled) {
+ notifyHighTextContrastStateChanged();
+ }
+ }
+
+ /**
+ * Find an installed service with the specified {@link ComponentName}.
+ *
+ * @param componentName The name to match to the service.
+ *
+ * @return The info corresponding to the installed service, or {@code null} if no such service
+ * is installed.
+ * @hide
+ */
+ public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
+ ComponentName componentName) {
+ final List<AccessibilityServiceInfo> installedServiceInfos =
+ getInstalledAccessibilityServiceList();
+ if ((installedServiceInfos == null) || (componentName == null)) {
+ return null;
+ }
+ for (int i = 0; i < installedServiceInfos.size(); i++) {
+ if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
+ return installedServiceInfos.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is added.
+ * @param connection The connection.
+ *
+ * @hide
+ */
+ public int addAccessibilityInteractionConnection(IWindow windowToken,
+ IAccessibilityInteractionConnection connection) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return View.NO_ID;
+ }
+ userId = mUserId;
+ }
+ try {
+ return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
+ }
+ return View.NO_ID;
+ }
+
+ /**
+ * Removed an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is removed.
+ *
+ * @hide
+ */
+ public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.removeAccessibilityInteractionConnection(windowToken);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
+ }
+ }
+
+ /**
+ * Perform the accessibility shortcut if the caller has permission.
+ *
+ * @hide
+ */
+ public void performAccessibilityShortcut() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.performAccessibilityShortcut();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
+ }
+ }
+
+ /**
+ * Notifies that the accessibility button in the system's navigation area has been clicked
+ *
+ * @hide
+ */
+ public void notifyAccessibilityButtonClicked() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonClicked();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
+ }
+ }
+
+ /**
+ * Notifies that the visibility of the accessibility button in the system's navigation area
+ * has changed.
+ *
+ * @param shown {@code true} if the accessibility button is visible within the system
+ * navigation area, {@code false} otherwise
+ * @hide
+ */
+ public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonVisibilityChanged(shown);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
+ }
+ }
+
+ /**
+ * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
+ * window. Intended for use by the System UI only.
+ *
+ * @param connection The connection to handle the actions. Set to {@code null} to avoid
+ * affecting the actions.
+ *
+ * @hide
+ */
+ public void setPictureInPictureActionReplacingConnection(
+ @Nullable IAccessibilityInteractionConnection connection) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.setPictureInPictureActionReplacingConnection(connection);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
+ }
+ }
+
+ private IAccessibilityManager getServiceLocked() {
+ if (mService == null) {
+ tryConnectToServiceLocked(null);
+ }
+ return mService;
+ }
+
+ private void tryConnectToServiceLocked(IAccessibilityManager service) {
+ if (service == null) {
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ if (iBinder == null) {
+ return;
+ }
+ service = IAccessibilityManager.Stub.asInterface(iBinder);
+ }
+
+ try {
+ final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
+ setStateLocked(IntPair.first(userStateAndRelevantEvents));
+ mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
+ mService = service;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+ }
+ }
+
+ /**
+ * Notifies the registered {@link AccessibilityStateChangeListener}s.
+ */
+ private void notifyAccessibilityStateChanged() {
+ final boolean isEnabled;
+ final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mAccessibilityStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isEnabled = mIsEnabled;
+ listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final AccessibilityStateChangeListener listener =
+ mAccessibilityStateChangeListeners.keyAt(i);
+ mAccessibilityStateChangeListeners.valueAt(i)
+ .post(() -> listener.onAccessibilityStateChanged(isEnabled));
+ }
+ }
+
+ /**
+ * Notifies the registered {@link TouchExplorationStateChangeListener}s.
+ */
+ private void notifyTouchExplorationStateChanged() {
+ final boolean isTouchExplorationEnabled;
+ final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mTouchExplorationStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isTouchExplorationEnabled = mIsTouchExplorationEnabled;
+ listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final TouchExplorationStateChangeListener listener =
+ mTouchExplorationStateChangeListeners.keyAt(i);
+ mTouchExplorationStateChangeListeners.valueAt(i)
+ .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
+ }
+ }
+
+ /**
+ * Notifies the registered {@link HighTextContrastChangeListener}s.
+ */
+ private void notifyHighTextContrastStateChanged() {
+ final boolean isHighTextContrastEnabled;
+ final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mHighTextContrastStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isHighTextContrastEnabled = mIsHighTextContrastEnabled;
+ listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final HighTextContrastChangeListener listener =
+ mHighTextContrastStateChangeListeners.keyAt(i);
+ mHighTextContrastStateChangeListeners.valueAt(i)
+ .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
+ }
+ }
+
+ /**
+ * Determines if the accessibility button within the system navigation area is supported.
+ *
+ * @return {@code true} if the accessibility button is supported on this device,
+ * {@code false} otherwise
+ */
+ public static boolean isAccessibilityButtonSupported() {
+ final Resources res = Resources.getSystem();
+ return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+ }
+
+ private final class MyCallback implements Handler.Callback {
+ public static final int MSG_SET_STATE = 1;
+
+ @Override
+ public boolean handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_SET_STATE: {
+ // See comment at mClient
+ final int state = message.arg1;
+ synchronized (mLock) {
+ setStateLocked(state);
+ }
+ } break;
+ }
+ return true;
+ }
+ }
+}
diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java
new file mode 100644
index 00000000..9bdd3ffc
--- /dev/null
+++ b/android/view/accessibility/AccessibilityNodeInfo.java
@@ -0,0 +1,4681 @@
+/*
+ * Copyright (C) 2011 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.accessibility;
+
+import static com.android.internal.util.BitUtils.bitAt;
+import static com.android.internal.util.BitUtils.isBitSet;
+
+import static java.util.Collections.EMPTY_LIST;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.AccessibilityURLSpan;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.util.ArraySet;
+import android.util.LongArray;
+import android.util.Pools.SynchronizedPool;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class represents a node of the window content as well as actions that
+ * can be requested from its source. From the point of view of an
+ * {@link android.accessibilityservice.AccessibilityService} a window's content is
+ * presented as a tree of accessibility node infos, which may or may not map one-to-one
+ * to the view hierarchy. In other words, a custom view is free to report itself as
+ * a tree of accessibility node info.
+ * </p>
+ * <p>
+ * Once an accessibility node info is delivered to an accessibility service it is
+ * made immutable and calling a state mutation method generates an error.
+ * </p>
+ * <p>
+ * Please refer to {@link android.accessibilityservice.AccessibilityService} for
+ * details about how to obtain a handle to window content as a tree of accessibility
+ * node info as well as details about the security model.
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about making applications accessible, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @see android.accessibilityservice.AccessibilityService
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ */
+public class AccessibilityNodeInfo implements Parcelable {
+
+ private static final boolean DEBUG = false;
+
+ /** @hide */
+ public static final int UNDEFINED_CONNECTION_ID = -1;
+
+ /** @hide */
+ public static final int UNDEFINED_SELECTION_INDEX = -1;
+
+ /** @hide */
+ public static final int UNDEFINED_ITEM_ID = Integer.MAX_VALUE;
+
+ /** @hide */
+ public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
+
+ /** @hide */
+ public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
+
+ /** @hide */
+ public static final long ROOT_NODE_ID = makeNodeId(ROOT_ITEM_ID,
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
+
+ /** @hide */
+ public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
+
+ /** @hide */
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+
+ // Actions.
+
+ /**
+ * Action that gives input focus to the node.
+ */
+ public static final int ACTION_FOCUS = 0x00000001;
+
+ /**
+ * Action that clears input focus of the node.
+ */
+ public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+
+ /**
+ * Action that selects the node.
+ */
+ public static final int ACTION_SELECT = 0x00000004;
+
+ /**
+ * Action that deselects the node.
+ */
+ public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+
+ /**
+ * Action that clicks on the node info.
+ *
+ * See {@link AccessibilityAction#ACTION_CLICK}
+ */
+ public static final int ACTION_CLICK = 0x00000010;
+
+ /**
+ * Action that long clicks on the node.
+ */
+ public static final int ACTION_LONG_CLICK = 0x00000020;
+
+ /**
+ * Action that gives accessibility focus to the node.
+ */
+ public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040;
+
+ /**
+ * Action that clears accessibility focus of the node.
+ */
+ public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080;
+
+ /**
+ * Action that requests to go to the next entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the previous character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see #setMovementGranularities(int)
+ * @see #getMovementGranularities()
+ *
+ * @see #MOVEMENT_GRANULARITY_CHARACTER
+ * @see #MOVEMENT_GRANULARITY_WORD
+ * @see #MOVEMENT_GRANULARITY_LINE
+ * @see #MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see #MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100;
+
+ /**
+ * Action that requests to go to the previous entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the next character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see #setMovementGranularities(int)
+ * @see #getMovementGranularities()
+ *
+ * @see #MOVEMENT_GRANULARITY_CHARACTER
+ * @see #MOVEMENT_GRANULARITY_WORD
+ * @see #MOVEMENT_GRANULARITY_LINE
+ * @see #MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see #MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200;
+
+ /**
+ * Action to move to the next HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
+
+ /**
+ * Action to move to the previous HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800;
+
+ /**
+ * Action to scroll the node content forward.
+ */
+ public static final int ACTION_SCROLL_FORWARD = 0x00001000;
+
+ /**
+ * Action to scroll the node content backward.
+ */
+ public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
+
+ /**
+ * Action to copy the current selection to the clipboard.
+ */
+ public static final int ACTION_COPY = 0x00004000;
+
+ /**
+ * Action to paste the current clipboard content.
+ */
+ public static final int ACTION_PASTE = 0x00008000;
+
+ /**
+ * Action to cut the current selection and place it to the clipboard.
+ */
+ public static final int ACTION_CUT = 0x00010000;
+
+ /**
+ * Action to set the selection. Performing this action with no arguments
+ * clears the selection.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link #ACTION_ARGUMENT_SELECTION_START_INT},
+ * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+ * info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_SELECTION_START_INT
+ * @see #ACTION_ARGUMENT_SELECTION_END_INT
+ */
+ public static final int ACTION_SET_SELECTION = 0x00020000;
+
+ /**
+ * Action to expand an expandable node.
+ */
+ public static final int ACTION_EXPAND = 0x00040000;
+
+ /**
+ * Action to collapse an expandable node.
+ */
+ public static final int ACTION_COLLAPSE = 0x00080000;
+
+ /**
+ * Action to dismiss a dismissable node.
+ */
+ public static final int ACTION_DISMISS = 0x00100000;
+
+ /**
+ * Action that sets the text of the node. Performing the action without argument, using <code>
+ * null</code> or empty {@link CharSequence} will clear the text. This action will also put the
+ * cursor at the end of text.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+ * "android");
+ * info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
+ * </code></pre></p>
+ */
+ public static final int ACTION_SET_TEXT = 0x00200000;
+
+ /** @hide */
+ public static final int LAST_LEGACY_STANDARD_ACTION = ACTION_SET_TEXT;
+
+ /**
+ * Mask to see if the value is larger than the largest ACTION_ constant
+ */
+ private static final int ACTION_TYPE_MASK = 0xFF000000;
+
+ // Action arguments
+
+ /**
+ * Argument for which movement granularity to be used when traversing the node text.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+ * </ul>
+ * </p>
+ *
+ * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ */
+ public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
+ "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+
+ /**
+ * Argument for which HTML element to get moving to the next/previous HTML element.
+ * <p>
+ * <strong>Type:</strong> String<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_HTML_ELEMENT}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT}</li>
+ * </ul>
+ * </p>
+ *
+ * @see AccessibilityAction#ACTION_NEXT_HTML_ELEMENT
+ * @see AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT
+ */
+ public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
+ "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+
+ /**
+ * Argument for whether when moving at granularity to extend the selection
+ * or to move it otherwise.
+ * <p>
+ * <strong>Type:</strong> boolean<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ */
+ public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN =
+ "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+
+ /**
+ * Argument for specifying the selection start.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_START_INT =
+ "ACTION_ARGUMENT_SELECTION_START_INT";
+
+ /**
+ * Argument for specifying the selection end.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_END_INT =
+ "ACTION_ARGUMENT_SELECTION_END_INT";
+
+ /**
+ * Argument for specifying the text content to set.
+ * <p>
+ * <strong>Type:</strong> CharSequence<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_TEXT}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_TEXT
+ */
+ public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
+ "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+
+ /**
+ * Argument for specifying the collection row to make visible on screen.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+ */
+ public static final String ACTION_ARGUMENT_ROW_INT =
+ "android.view.accessibility.action.ARGUMENT_ROW_INT";
+
+ /**
+ * Argument for specifying the collection column to make visible on screen.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+ */
+ public static final String ACTION_ARGUMENT_COLUMN_INT =
+ "android.view.accessibility.action.ARGUMENT_COLUMN_INT";
+
+ /**
+ * Argument for specifying the progress value to set.
+ * <p>
+ * <strong>Type:</strong> float<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_PROGRESS}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_PROGRESS
+ */
+ public static final String ACTION_ARGUMENT_PROGRESS_VALUE =
+ "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
+
+ /**
+ * Argument for specifying the x coordinate to which to move a window.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_MOVE_WINDOW
+ */
+ public static final String ACTION_ARGUMENT_MOVE_WINDOW_X =
+ "ACTION_ARGUMENT_MOVE_WINDOW_X";
+
+ /**
+ * Argument for specifying the y coordinate to which to move a window.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_MOVE_WINDOW}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_MOVE_WINDOW
+ */
+ public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y =
+ "ACTION_ARGUMENT_MOVE_WINDOW_Y";
+
+ /**
+ * Argument to pass the {@link AccessibilityClickableSpan}.
+ * For use with R.id.accessibilityActionClickOnClickableSpan
+ * @hide
+ */
+ public static final String ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN =
+ "android.view.accessibility.action.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN";
+
+ // Focus types
+
+ /**
+ * The input focus.
+ */
+ public static final int FOCUS_INPUT = 1;
+
+ /**
+ * The accessibility focus.
+ */
+ public static final int FOCUS_ACCESSIBILITY = 2;
+
+ // Movement granularities
+
+ /**
+ * Movement granularity bit for traversing the text of a node by character.
+ */
+ public static final int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by word.
+ */
+ public static final int MOVEMENT_GRANULARITY_WORD = 0x00000002;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by line.
+ */
+ public static final int MOVEMENT_GRANULARITY_LINE = 0x00000004;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by paragraph.
+ */
+ public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008;
+
+ /**
+ * Movement granularity bit for traversing the text of a node by page.
+ */
+ public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010;
+
+ /**
+ * Key used to request and locate extra data for text character location. This key requests that
+ * an array of {@link android.graphics.RectF}s be added to the extras. This request is made with
+ * {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by this request are two
+ * integers: {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and
+ * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index must be valid
+ * inside the CharSequence returned by {@link #getText()}, and the length must be positive.
+ * <p>
+ * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this
+ * string as a key for {@link Bundle#getParcelableArray(String)}. The
+ * {@link android.graphics.RectF} will be null for characters that either do not exist or are
+ * off the screen.
+ *
+ * {@see #refreshWithExtraData(String, Bundle)}
+ */
+ public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY =
+ "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+
+ /**
+ * Integer argument specifying the start index of the requested text location data. Must be
+ * valid inside the CharSequence returned by {@link #getText()}.
+ *
+ * {@see EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY}
+ */
+ public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX =
+ "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+
+ /**
+ * Integer argument specifying the end index of the requested text location data. Must be
+ * positive.
+ *
+ * {@see EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY}
+ */
+ public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH =
+ "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+
+ /** @hide */
+ public static final String EXTRA_DATA_REQUESTED_KEY =
+ "android.view.accessibility.AccessibilityNodeInfo.extra_data_requested";
+
+ // Boolean attributes.
+
+ private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
+
+ private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
+
+ private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
+
+ private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
+
+ private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
+
+ private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
+
+ private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
+
+ private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
+
+ private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
+
+ private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
+
+ private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+
+ private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
+
+ private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000;
+
+ private static final int BOOLEAN_PROPERTY_OPENS_POPUP = 0x00002000;
+
+ private static final int BOOLEAN_PROPERTY_DISMISSABLE = 0x00004000;
+
+ private static final int BOOLEAN_PROPERTY_MULTI_LINE = 0x00008000;
+
+ private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
+
+ private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
+
+ private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
+
+ private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
+
+ /**
+ * Bits that provide the id of a virtual descendant of a view.
+ */
+ private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
+ /**
+ * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a
+ * virtual descendant of a view. Such a descendant does not exist in the view
+ * hierarchy and is only reported via the accessibility APIs.
+ */
+ private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
+
+ private static AtomicInteger sNumInstancesInUse;
+
+ /**
+ * Gets the accessibility view id which identifies a View in the view three.
+ *
+ * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
+ * @return The accessibility view id part of the node id.
+ *
+ * @hide
+ */
+ public static int getAccessibilityViewId(long accessibilityNodeId) {
+ return (int) accessibilityNodeId;
+ }
+
+ /**
+ * Gets the virtual descendant id which identifies an imaginary view in a
+ * containing View.
+ *
+ * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}.
+ * @return The virtual view id part of the node id.
+ *
+ * @hide
+ */
+ public static int getVirtualDescendantId(long accessibilityNodeId) {
+ return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK)
+ >> VIRTUAL_DESCENDANT_ID_SHIFT);
+ }
+
+ /**
+ * Makes a node id by shifting the <code>virtualDescendantId</code>
+ * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking
+ * the bitwise or with the <code>accessibilityViewId</code>.
+ *
+ * @param accessibilityViewId A View accessibility id.
+ * @param virtualDescendantId A virtual descendant id.
+ * @return The node id.
+ *
+ * @hide
+ */
+ public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) {
+ return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
+ }
+
+ // Housekeeping.
+ private static final int MAX_POOL_SIZE = 50;
+ private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+
+ private static final AccessibilityNodeInfo DEFAULT = new AccessibilityNodeInfo();
+
+ private boolean mSealed;
+
+ // Data.
+ private int mWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ private long mSourceNodeId = UNDEFINED_NODE_ID;
+ private long mParentNodeId = UNDEFINED_NODE_ID;
+ private long mLabelForId = UNDEFINED_NODE_ID;
+ private long mLabeledById = UNDEFINED_NODE_ID;
+ private long mTraversalBefore = UNDEFINED_NODE_ID;
+ private long mTraversalAfter = UNDEFINED_NODE_ID;
+
+ private int mBooleanProperties;
+ private final Rect mBoundsInParent = new Rect();
+ private final Rect mBoundsInScreen = new Rect();
+ private int mDrawingOrderInParent;
+
+ private CharSequence mPackageName;
+ private CharSequence mClassName;
+ // Hidden, unparceled value used to hold the original value passed to setText
+ private CharSequence mOriginalText;
+ private CharSequence mText;
+ private CharSequence mHintText;
+ private CharSequence mError;
+ private CharSequence mContentDescription;
+ private String mViewIdResourceName;
+ private ArrayList<String> mExtraDataKeys;
+
+ private LongArray mChildNodeIds;
+ private ArrayList<AccessibilityAction> mActions;
+
+ private int mMaxTextLength = -1;
+ private int mMovementGranularities;
+
+ private int mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
+ private int mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
+ private int mInputType = InputType.TYPE_NULL;
+ private int mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
+
+ private Bundle mExtras;
+
+ private int mConnectionId = UNDEFINED_CONNECTION_ID;
+
+ private RangeInfo mRangeInfo;
+ private CollectionInfo mCollectionInfo;
+ private CollectionItemInfo mCollectionItemInfo;
+
+ /**
+ * Hide constructor from clients.
+ */
+ private AccessibilityNodeInfo() {
+ /* do nothing */
+ }
+
+ /**
+ * Sets the source.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param source The info source.
+ */
+ public void setSource(View source) {
+ setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the source to be a virtual descendant of the given <code>root</code>.
+ * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
+ * is set as the source.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setSource(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED_ITEM_ID;
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Find the view that has the specified focus type. The search starts from
+ * the view represented by this node info.
+ *
+ * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
+ * {@link #FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see #FOCUS_INPUT
+ * @see #FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ enforceSealed();
+ enforceValidFocusType(focus);
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
+ mSourceNodeId, focus);
+ }
+
+ /**
+ * Searches for the nearest view in the specified direction that can take
+ * the input focus.
+ *
+ * @param direction The direction. Can be one of:
+ * {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_UP},
+ * {@link View#FOCUS_LEFT},
+ * {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_FORWARD},
+ * {@link View#FOCUS_BACKWARD}.
+ *
+ * @return The node info for the view that can take accessibility focus.
+ */
+ public AccessibilityNodeInfo focusSearch(int direction) {
+ enforceSealed();
+ enforceValidFocusDirection(direction);
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
+ mSourceNodeId, direction);
+ }
+
+ /**
+ * Gets the id of the window from which the info comes from.
+ *
+ * @return The window id.
+ */
+ public int getWindowId() {
+ return mWindowId;
+ }
+
+ /**
+ * Refreshes this info with the latest state of the view it represents.
+ * <p>
+ * <strong>Note:</strong> If this method returns false this info is obsolete
+ * since it represents a view that is no longer in the view tree and should
+ * be recycled.
+ * </p>
+ *
+ * @param bypassCache Whether to bypass the cache.
+ * @return Whether the refresh succeeded.
+ *
+ * @hide
+ */
+ public boolean refresh(Bundle arguments, boolean bypassCache) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId(
+ mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0, arguments);
+ if (refreshedInfo == null) {
+ return false;
+ }
+ // Hard-to-reproduce bugs seem to be due to some tools recycling a node on another
+ // thread. If that happens, the init will re-seal the node, which then is in a bad state
+ // when it is obtained. Enforce sealing again before we init to fail when a node has been
+ // recycled during a refresh to catch such errors earlier.
+ enforceSealed();
+ init(refreshedInfo);
+ refreshedInfo.recycle();
+ return true;
+ }
+
+ /**
+ * Refreshes this info with the latest state of the view it represents.
+ *
+ * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+ * by this node is no longer in the view tree (and thus this node is obsolete and should be
+ * recycled).
+ */
+ public boolean refresh() {
+ return refresh(null, true);
+ }
+
+ /**
+ * Refreshes this info with the latest state of the view it represents, and request new
+ * data be added by the View.
+ *
+ * @param extraDataKey A bitmask of the extra data requested. Data that must be requested
+ * with this mechanism is generally expensive to retrieve, so should only be
+ * requested when needed. See
+ * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY} and
+ * {@link #getAvailableExtraData()}.
+ * @param args A bundle of arguments for the request. These depend on the particular request.
+ *
+ * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+ * by this node is no longer in the view tree (and thus this node is obsolete and should be
+ * recycled).
+ */
+ public boolean refreshWithExtraData(String extraDataKey, Bundle args) {
+ args.putString(EXTRA_DATA_REQUESTED_KEY, extraDataKey);
+ return refresh(args, true);
+ }
+
+ /**
+ * Returns the array containing the IDs of this node's children.
+ *
+ * @hide
+ */
+ public LongArray getChildNodeIds() {
+ return mChildNodeIds;
+ }
+
+ /**
+ * Returns the id of the child at the specified index.
+ *
+ * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt;=
+ * getChildCount()
+ * @hide
+ */
+ public long getChildId(int index) {
+ if (mChildNodeIds == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mChildNodeIds.get(index);
+ }
+
+ /**
+ * Gets the number of children.
+ *
+ * @return The child count.
+ */
+ public int getChildCount() {
+ return mChildNodeIds == null ? 0 : mChildNodeIds.size();
+ }
+
+ /**
+ * Get the child at given index.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @param index The child index.
+ * @return The child node.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ *
+ */
+ public AccessibilityNodeInfo getChild(int index) {
+ enforceSealed();
+ if (mChildNodeIds == null) {
+ return null;
+ }
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ final long childId = mChildNodeIds.get(index);
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
+ childId, false, FLAG_PREFETCH_DESCENDANTS, null);
+ }
+
+ /**
+ * Adds a child.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param child The child.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addChild(View child) {
+ addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, true);
+ }
+
+ /**
+ * Unchecked version of {@link #addChild(View)} that does not verify
+ * uniqueness. For framework use only.
+ *
+ * @hide
+ */
+ public void addChildUnchecked(View child) {
+ addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, false);
+ }
+
+ /**
+ * Removes a child. If the child was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param child The child.
+ * @return true if the child was present
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public boolean removeChild(View child) {
+ return removeChild(child, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Adds a virtual child which is a descendant of the given <code>root</code>.
+ * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
+ * is added as a child.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual child.
+ */
+ public void addChild(View root, int virtualDescendantId) {
+ addChildInternal(root, virtualDescendantId, true);
+ }
+
+ private void addChildInternal(View root, int virtualDescendantId, boolean checked) {
+ enforceNotSealed();
+ if (mChildNodeIds == null) {
+ mChildNodeIds = new LongArray();
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ // If we're checking uniqueness and the ID already exists, abort.
+ if (checked && mChildNodeIds.indexOf(childNodeId) >= 0) {
+ return;
+ }
+ mChildNodeIds.add(childNodeId);
+ }
+
+ /**
+ * Removes a virtual child which is a descendant of the given
+ * <code>root</code>. If the child was not previously added to the node,
+ * calling this method has no effect.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual child.
+ * @return true if the child was present
+ * @see #addChild(View, int)
+ */
+ public boolean removeChild(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ return false;
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ final int index = childIds.indexOf(childNodeId);
+ if (index < 0) {
+ return false;
+ }
+ childIds.remove(index);
+ return true;
+ }
+
+ /**
+ * Gets the actions that can be performed on the node.
+ */
+ public List<AccessibilityAction> getActionList() {
+ return CollectionUtils.emptyIfNull(mActions);
+ }
+
+ /**
+ * Gets the actions that can be performed on the node.
+ *
+ * @return The bit mask of with actions.
+ *
+ * @see AccessibilityNodeInfo#ACTION_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_SELECT
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
+ * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS
+ * @see AccessibilityNodeInfo#ACTION_CLICK
+ * @see AccessibilityNodeInfo#ACTION_LONG_CLICK
+ * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT
+ * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
+ * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
+ * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
+ *
+ * @deprecated Use {@link #getActionList()}.
+ */
+ @Deprecated
+ public int getActions() {
+ int returnValue = 0;
+
+ if (mActions == null) {
+ return returnValue;
+ }
+
+ final int actionSize = mActions.size();
+ for (int i = 0; i < actionSize; i++) {
+ int actionId = mActions.get(i).getId();
+ if (actionId <= LAST_LEGACY_STANDARD_ACTION) {
+ returnValue |= actionId;
+ }
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * To add a standard action use the static constants on {@link AccessibilityAction}.
+ * To add a custom action create a new {@link AccessibilityAction} by passing in a
+ * resource id from your application as the action id and an optional label that
+ * describes the action. To override one of the standard actions use as the action
+ * id of a standard action id such as {@link #ACTION_CLICK} and an optional label that
+ * describes the action.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void addAction(AccessibilityAction action) {
+ enforceNotSealed();
+
+ addActionUnchecked(action);
+ }
+
+ private void addActionUnchecked(AccessibilityAction action) {
+ if (action == null) {
+ return;
+ }
+
+ if (mActions == null) {
+ mActions = new ArrayList<>();
+ }
+
+ mActions.remove(action);
+ mActions.add(action);
+ }
+
+ /**
+ * Adds an action that can be performed on the node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @throws IllegalArgumentException If the argument is not one of the standard actions.
+ *
+ * @deprecated This has been deprecated for {@link #addAction(AccessibilityAction)}
+ */
+ @Deprecated
+ public void addAction(int action) {
+ enforceNotSealed();
+
+ if ((action & ACTION_TYPE_MASK) != 0) {
+ throw new IllegalArgumentException("Action is not a combination of the standard " +
+ "actions: " + action);
+ }
+
+ addStandardActions(action);
+ }
+
+ /**
+ * Removes an action that can be performed on the node. If the action was
+ * not already added to the node, calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action to be removed.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #removeAction(AccessibilityAction)}
+ */
+ @Deprecated
+ public void removeAction(int action) {
+ enforceNotSealed();
+
+ removeAction(getActionSingleton(action));
+ }
+
+ /**
+ * Removes an action that can be performed on the node. If the action was
+ * not already added to the node, calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action to be removed.
+ * @return The action removed from the list of actions.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public boolean removeAction(AccessibilityAction action) {
+ enforceNotSealed();
+
+ if (mActions == null || action == null) {
+ return false;
+ }
+
+ return mActions.remove(action);
+ }
+
+ /**
+ * Removes all actions.
+ *
+ * @hide
+ */
+ public void removeAllActions() {
+ if (mActions != null) {
+ mActions.clear();
+ }
+ }
+
+ /**
+ * Gets the node before which this one is visited during traversal. A screen-reader
+ * must visit the content of this node before the content of the one it precedes.
+ *
+ * @return The succeeding node if such or <code>null</code>.
+ *
+ * @see #setTraversalBefore(android.view.View)
+ * @see #setTraversalBefore(android.view.View, int)
+ */
+ public AccessibilityNodeInfo getTraversalBefore() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mTraversalBefore);
+ }
+
+ /**
+ * Sets the view before whose node this one should be visited during traversal. A
+ * screen-reader must visit the content of this node before the content of the one
+ * it precedes.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param view The view providing the preceding node.
+ *
+ * @see #getTraversalBefore()
+ */
+ public void setTraversalBefore(View view) {
+ setTraversalBefore(view, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the node before which this one is visited during traversal. A screen-reader
+ * must visit the content of this node before the content of the one it precedes.
+ * The successor is a virtual descendant of the given <code>root</code>. If
+ * <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root is set
+ * as the successor.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setTraversalBefore(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mTraversalBefore = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one.
+ *
+ * @return The succeeding node if such or <code>null</code>.
+ *
+ * @see #setTraversalAfter(android.view.View)
+ * @see #setTraversalAfter(android.view.View, int)
+ */
+ public AccessibilityNodeInfo getTraversalAfter() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mTraversalAfter);
+ }
+
+ /**
+ * Sets the view whose node is visited after this one in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param view The previous view.
+ *
+ * @see #getTraversalAfter()
+ */
+ public void setTraversalAfter(View view) {
+ setTraversalAfter(view, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the node after which this one is visited in accessibility traversal.
+ * A screen-reader must visit the content of the other node before the content
+ * of this one. If <code>virtualDescendantId</code> equals to {@link View#NO_ID}
+ * the root is set as the predecessor.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setTraversalAfter(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mTraversalAfter = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Get the extra data available for this node.
+ * <p>
+ * Some data that is useful for some accessibility services is expensive to compute, and would
+ * place undue overhead on apps to compute all the time. That data can be requested with
+ * {@link #refreshWithExtraData(String, Bundle)}.
+ *
+ * @return An unmodifiable list of keys corresponding to extra data that can be requested.
+ * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+ */
+ public List<String> getAvailableExtraData() {
+ if (mExtraDataKeys != null) {
+ return Collections.unmodifiableList(mExtraDataKeys);
+ } else {
+ return EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Set the extra data available for this node.
+ * <p>
+ * <strong>Note:</strong> When a {@code View} passes in a non-empty list, it promises that
+ * it will populate the node's extras with corresponding pieces of information in
+ * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)}.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ *
+ * @param extraDataKeys A list of types of extra data that are available.
+ * @see #getAvailableExtraData()
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAvailableExtraData(List<String> extraDataKeys) {
+ enforceNotSealed();
+ mExtraDataKeys = new ArrayList<>(extraDataKeys);
+ }
+
+ /**
+ * Sets the maximum text length, or -1 for no limit.
+ * <p>
+ * Typically used to indicate that an editable text field has a limit on
+ * the number of characters entered.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ *
+ * @param max The maximum text length.
+ * @see #getMaxTextLength()
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setMaxTextLength(int max) {
+ enforceNotSealed();
+ mMaxTextLength = max;
+ }
+
+ /**
+ * Returns the maximum text length for this node.
+ *
+ * @return The maximum text length, or -1 for no limit.
+ * @see #setMaxTextLength(int)
+ */
+ public int getMaxTextLength() {
+ return mMaxTextLength;
+ }
+
+ /**
+ * Sets the movement granularities for traversing the text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param granularities The bit mask with granularities.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setMovementGranularities(int granularities) {
+ enforceNotSealed();
+ mMovementGranularities = granularities;
+ }
+
+ /**
+ * Gets the movement granularities for traversing the text of this node.
+ *
+ * @return The bit mask with granularities.
+ */
+ public int getMovementGranularities() {
+ return mMovementGranularities;
+ }
+
+ /**
+ * Performs an action on the node.
+ * <p>
+ * <strong>Note:</strong> An action can be performed only if the request is made
+ * from an {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param action The action to perform.
+ * @return True if the action was performed.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ */
+ public boolean performAction(int action) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
+ action, null);
+ }
+
+ /**
+ * Performs an action on the node.
+ * <p>
+ * <strong>Note:</strong> An action can be performed only if the request is made
+ * from an {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param action The action to perform.
+ * @param arguments A bundle with additional arguments.
+ * @return True if the action was performed.
+ *
+ * @throws IllegalStateException If called outside of an AccessibilityService.
+ */
+ public boolean performAction(int action, Bundle arguments) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
+ action, arguments);
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by text. The match is case
+ * insensitive containment. The search is relative to this info i.e.
+ * this info is the root of the traversed tree.
+ *
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @param text The searched text.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
+ text);
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by the fully qualified view id's resource
+ * name where a fully qualified id is of the from "package:id/id_resource_name".
+ * For example, if the target application's package is "foo.bar" and the id
+ * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
+ *
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
+ * the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId,
+ viewId);
+ }
+
+ /**
+ * Gets the window to which this node belongs.
+ *
+ * @return The window.
+ *
+ * @see android.accessibilityservice.AccessibilityService#getWindows()
+ */
+ public AccessibilityWindowInfo getWindow() {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.getWindow(mConnectionId, mWindowId);
+ }
+
+ /**
+ * Gets the parent.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The parent.
+ */
+ public AccessibilityNodeInfo getParent() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mParentNodeId);
+ }
+
+ /**
+ * @return The parent node id.
+ *
+ * @hide
+ */
+ public long getParentNodeId() {
+ return mParentNodeId;
+ }
+
+ /**
+ * Sets the parent.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param parent The parent.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setParent(View parent) {
+ setParent(parent, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the parent to be a virtual descendant of the given <code>root</code>.
+ * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
+ * is set as the parent.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setParent(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node bounds in parent coordinates.
+ *
+ * @param outBounds The output node bounds.
+ */
+ public void getBoundsInParent(Rect outBounds) {
+ outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
+ mBoundsInParent.right, mBoundsInParent.bottom);
+ }
+
+ /**
+ * Sets the node bounds in parent coordinates.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param bounds The node bounds.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBoundsInParent(Rect bounds) {
+ enforceNotSealed();
+ mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Gets the node bounds in screen coordinates.
+ *
+ * @param outBounds The output node bounds.
+ */
+ public void getBoundsInScreen(Rect outBounds) {
+ outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
+ mBoundsInScreen.right, mBoundsInScreen.bottom);
+ }
+
+ /**
+ * Returns the actual rect containing the node bounds in screen coordinates.
+ *
+ * @hide Not safe to expose outside the framework.
+ */
+ public Rect getBoundsInScreen() {
+ return mBoundsInScreen;
+ }
+
+ /**
+ * Sets the node bounds in screen coordinates.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param bounds The node bounds.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBoundsInScreen(Rect bounds) {
+ enforceNotSealed();
+ mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Gets whether this node is checkable.
+ *
+ * @return True if the node is checkable.
+ */
+ public boolean isCheckable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE);
+ }
+
+ /**
+ * Sets whether this node is checkable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param checkable True if the node is checkable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setCheckable(boolean checkable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE, checkable);
+ }
+
+ /**
+ * Gets whether this node is checked.
+ *
+ * @return True if the node is checked.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets whether this node is checked.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param checked True if the node is checked.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setChecked(boolean checked) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked);
+ }
+
+ /**
+ * Gets whether this node is focusable.
+ *
+ * @return True if the node is focusable.
+ */
+ public boolean isFocusable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
+ }
+
+ /**
+ * Sets whether this node is focusable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param focusable True if the node is focusable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocusable(boolean focusable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
+ }
+
+ /**
+ * Gets whether this node is focused.
+ *
+ * @return True if the node is focused.
+ */
+ public boolean isFocused() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is focused.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param focused True if the node is focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFocused(boolean focused) {
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets whether this node is visible to the user.
+ *
+ * @return Whether the node is visible to the user.
+ */
+ public boolean isVisibleToUser() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER);
+ }
+
+ /**
+ * Sets whether this node is visible to the user.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param visibleToUser Whether the node is visible to the user.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setVisibleToUser(boolean visibleToUser) {
+ setBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER, visibleToUser);
+ }
+
+ /**
+ * Gets whether this node is accessibility focused.
+ *
+ * @return True if the node is accessibility focused.
+ */
+ public boolean isAccessibilityFocused() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is accessibility focused.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param focused True if the node is accessibility focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAccessibilityFocused(boolean focused) {
+ setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets whether this node is selected.
+ *
+ * @return True if the node is selected.
+ */
+ public boolean isSelected() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_SELECTED);
+ }
+
+ /**
+ * Sets whether this node is selected.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param selected True if the node is selected.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setSelected(boolean selected) {
+ setBooleanProperty(BOOLEAN_PROPERTY_SELECTED, selected);
+ }
+
+ /**
+ * Gets whether this node is clickable.
+ *
+ * @return True if the node is clickable.
+ */
+ public boolean isClickable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is clickable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param clickable True if the node is clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClickable(boolean clickable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
+ }
+
+ /**
+ * Gets whether this node is long clickable.
+ *
+ * @return True if the node is long clickable.
+ */
+ public boolean isLongClickable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is long clickable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param longClickable True if the node is long clickable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setLongClickable(boolean longClickable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
+ }
+
+ /**
+ * Gets whether this node is enabled.
+ *
+ * @return True if the node is enabled.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets whether this node is enabled.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param enabled True if the node is enabled.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEnabled(boolean enabled) {
+ setBooleanProperty(BOOLEAN_PROPERTY_ENABLED, enabled);
+ }
+
+ /**
+ * Gets whether this node is a password.
+ *
+ * @return True if the node is a password.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets whether this node is a password.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param password True if the node is a password.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPassword(boolean password) {
+ setBooleanProperty(BOOLEAN_PROPERTY_PASSWORD, password);
+ }
+
+ /**
+ * Gets if the node is scrollable.
+ *
+ * @return True if the node is scrollable, false otherwise.
+ */
+ public boolean isScrollable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
+ }
+
+ /**
+ * Sets if the node is scrollable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param scrollable True if the node is scrollable, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setScrollable(boolean scrollable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
+ }
+
+ /**
+ * Gets if the node is editable.
+ *
+ * @return True if the node is editable, false otherwise.
+ */
+ public boolean isEditable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_EDITABLE);
+ }
+
+ /**
+ * Sets whether this node is editable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param editable True if the node is editable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEditable(boolean editable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_EDITABLE, editable);
+ }
+
+ /**
+ * Get the drawing order of the view corresponding it this node.
+ * <p>
+ * Drawing order is determined only within the node's parent, so this index is only relative
+ * to its siblings.
+ * <p>
+ * In some cases, the drawing order is essentially simultaneous, so it is possible for two
+ * siblings to return the same value. It is also possible that values will be skipped.
+ *
+ * @return The drawing position of the view corresponding to this node relative to its siblings.
+ */
+ public int getDrawingOrder() {
+ return mDrawingOrderInParent;
+ }
+
+ /**
+ * Set the drawing order of the view corresponding it this node.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param drawingOrderInParent
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setDrawingOrder(int drawingOrderInParent) {
+ enforceNotSealed();
+ mDrawingOrderInParent = drawingOrderInParent;
+ }
+
+ /**
+ * Gets the collection info if the node is a collection. A collection
+ * child is always a collection item.
+ *
+ * @return The collection info.
+ */
+ public CollectionInfo getCollectionInfo() {
+ return mCollectionInfo;
+ }
+
+ /**
+ * Sets the collection info if the node is a collection. A collection
+ * child is always a collection item.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param collectionInfo The collection info.
+ */
+ public void setCollectionInfo(CollectionInfo collectionInfo) {
+ enforceNotSealed();
+ mCollectionInfo = collectionInfo;
+ }
+
+ /**
+ * Gets the collection item info if the node is a collection item. A collection
+ * item is always a child of a collection.
+ *
+ * @return The collection item info.
+ */
+ public CollectionItemInfo getCollectionItemInfo() {
+ return mCollectionItemInfo;
+ }
+
+ /**
+ * Sets the collection item info if the node is a collection item. A collection
+ * item is always a child of a collection.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ */
+ public void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) {
+ enforceNotSealed();
+ mCollectionItemInfo = collectionItemInfo;
+ }
+
+ /**
+ * Gets the range info if this node is a range.
+ *
+ * @return The range.
+ */
+ public RangeInfo getRangeInfo() {
+ return mRangeInfo;
+ }
+
+ /**
+ * Sets the range info if this node is a range.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param rangeInfo The range info.
+ */
+ public void setRangeInfo(RangeInfo rangeInfo) {
+ enforceNotSealed();
+ mRangeInfo = rangeInfo;
+ }
+
+ /**
+ * Gets if the content of this node is invalid. For example,
+ * a date is not well-formed.
+ *
+ * @return If the node content is invalid.
+ */
+ public boolean isContentInvalid() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID);
+ }
+
+ /**
+ * Sets if the content of this node is invalid. For example,
+ * a date is not well-formed.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param contentInvalid If the node content is invalid.
+ */
+ public void setContentInvalid(boolean contentInvalid) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CONTENT_INVALID, contentInvalid);
+ }
+
+ /**
+ * Gets whether this node is context clickable.
+ *
+ * @return True if the node is context clickable.
+ */
+ public boolean isContextClickable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE);
+ }
+
+ /**
+ * Sets whether this node is context clickable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}. This class is made immutable
+ * before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param contextClickable True if the node is context clickable.
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContextClickable(boolean contextClickable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE, contextClickable);
+ }
+
+ /**
+ * Gets the node's live region mode.
+ * <p>
+ * A live region is a node that contains information that is important for
+ * the user and when it changes the user should be notified. For example,
+ * in a login screen with a TextView that displays an "incorrect password"
+ * notification, that view should be marked as a live region with mode
+ * {@link View#ACCESSIBILITY_LIVE_REGION_POLITE}.
+ * <p>
+ * It is the responsibility of the accessibility service to monitor
+ * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events indicating
+ * changes to live region nodes and their children.
+ *
+ * @return The live region mode, or
+ * {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a
+ * live region.
+ * @see android.view.View#getAccessibilityLiveRegion()
+ */
+ public int getLiveRegion() {
+ return mLiveRegion;
+ }
+
+ /**
+ * Sets the node's live region mode.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}. This class is
+ * made immutable before being delivered to an AccessibilityService.
+ *
+ * @param mode The live region mode, or
+ * {@link View#ACCESSIBILITY_LIVE_REGION_NONE} if the view is not a
+ * live region.
+ * @see android.view.View#setAccessibilityLiveRegion(int)
+ */
+ public void setLiveRegion(int mode) {
+ enforceNotSealed();
+ mLiveRegion = mode;
+ }
+
+ /**
+ * Gets if the node is a multi line editable text.
+ *
+ * @return True if the node is multi line.
+ */
+ public boolean isMultiLine() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE);
+ }
+
+ /**
+ * Sets if the node is a multi line editable text.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param multiLine True if the node is multi line.
+ */
+ public void setMultiLine(boolean multiLine) {
+ setBooleanProperty(BOOLEAN_PROPERTY_MULTI_LINE, multiLine);
+ }
+
+ /**
+ * Gets if this node opens a popup or a dialog.
+ *
+ * @return If the the node opens a popup.
+ */
+ public boolean canOpenPopup() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP);
+ }
+
+ /**
+ * Sets if this node opens a popup or a dialog.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param opensPopup If the the node opens a popup.
+ */
+ public void setCanOpenPopup(boolean opensPopup) {
+ enforceNotSealed();
+ setBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP, opensPopup);
+ }
+
+ /**
+ * Gets if the node can be dismissed.
+ *
+ * @return If the node can be dismissed.
+ */
+ public boolean isDismissable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE);
+ }
+
+ /**
+ * Sets if the node can be dismissed.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param dismissable If the node can be dismissed.
+ */
+ public void setDismissable(boolean dismissable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable);
+ }
+
+ /**
+ * Returns whether the node originates from a view considered important for accessibility.
+ *
+ * @return {@code true} if the node originates from a view considered important for
+ * accessibility, {@code false} otherwise
+ *
+ * @see View#isImportantForAccessibility()
+ */
+ public boolean isImportantForAccessibility() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE);
+ }
+
+ /**
+ * Sets whether the node is considered important for accessibility.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param important {@code true} if the node is considered important for accessibility,
+ * {@code false} otherwise
+ */
+ public void setImportantForAccessibility(boolean important) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE, important);
+ }
+
+ /**
+ * Returns whether the node's text represents a hint for the user to enter text. It should only
+ * be {@code true} if the node has editable text.
+ *
+ * @return {@code true} if the text in the node represents a hint to the user, {@code false}
+ * otherwise.
+ */
+ public boolean isShowingHintText() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IS_SHOWING_HINT);
+ }
+
+ /**
+ * Sets whether the node's text represents a hint for the user to enter text. It should only
+ * be {@code true} if the node has editable text.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param showingHintText {@code true} if the text in the node represents a hint to the user,
+ * {@code false} otherwise.
+ */
+ public void setShowingHintText(boolean showingHintText) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IS_SHOWING_HINT, showingHintText);
+ }
+
+ /**
+ * Gets the package this node comes from.
+ *
+ * @return The package name.
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sets the package this node comes from.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param packageName The package name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPackageName(CharSequence packageName) {
+ enforceNotSealed();
+ mPackageName = packageName;
+ }
+
+ /**
+ * Gets the class this node comes from.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class this node comes from.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param className The class name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClassName(CharSequence className) {
+ enforceNotSealed();
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of this node.
+ * <p>
+ * <strong>Note:</strong> If the text contains {@link ClickableSpan}s or {@link URLSpan}s,
+ * these spans will have been replaced with ones whose {@link ClickableSpan#onClick(View)}
+ * can be called from an {@link AccessibilityService}. When called from a service, the
+ * {@link View} argument is ignored and the corresponding span will be found on the view that
+ * this {@code AccessibilityNodeInfo} represents and called with that view as its argument.
+ * <p>
+ * This treatment of {@link ClickableSpan}s means that the text returned from this method may
+ * different slightly one passed to {@link #setText(CharSequence)}, although they will be
+ * equivalent according to {@link TextUtils#equals(CharSequence, CharSequence)}. The
+ * {@link ClickableSpan#onClick(View)} of any spans, however, will generally not work outside
+ * of an accessibility service.
+ * </p>
+ *
+ * @return The text.
+ */
+ public CharSequence getText() {
+ // Attach this node to any spans that need it
+ if (mText instanceof Spanned) {
+ Spanned spanned = (Spanned) mText;
+ AccessibilityClickableSpan[] clickableSpans =
+ spanned.getSpans(0, mText.length(), AccessibilityClickableSpan.class);
+ for (int i = 0; i < clickableSpans.length; i++) {
+ clickableSpans[i].copyConnectionDataFrom(this);
+ }
+ AccessibilityURLSpan[] urlSpans =
+ spanned.getSpans(0, mText.length(), AccessibilityURLSpan.class);
+ for (int i = 0; i < urlSpans.length; i++) {
+ urlSpans[i].copyConnectionDataFrom(this);
+ }
+ }
+ return mText;
+ }
+
+ /**
+ * Get the text passed to setText before any changes to the spans.
+ * @hide
+ */
+ public CharSequence getOriginalText() {
+ return mOriginalText;
+ }
+
+ /**
+ * Sets the text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param text The text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setText(CharSequence text) {
+ enforceNotSealed();
+ mOriginalText = text;
+ // Replace any ClickableSpans in mText with placeholders
+ if (text instanceof Spanned) {
+ ClickableSpan[] spans =
+ ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+ if (spans.length > 0) {
+ Spannable spannable = new SpannableStringBuilder(text);
+ for (int i = 0; i < spans.length; i++) {
+ ClickableSpan span = spans[i];
+ if ((span instanceof AccessibilityClickableSpan)
+ || (span instanceof AccessibilityURLSpan)) {
+ // We've already done enough
+ break;
+ }
+ int spanToReplaceStart = spannable.getSpanStart(span);
+ int spanToReplaceEnd = spannable.getSpanEnd(span);
+ int spanToReplaceFlags = spannable.getSpanFlags(span);
+ spannable.removeSpan(span);
+ ClickableSpan replacementSpan = (span instanceof URLSpan)
+ ? new AccessibilityURLSpan((URLSpan) span)
+ : new AccessibilityClickableSpan(span.getId());
+ spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
+ spanToReplaceFlags);
+ }
+ mText = spannable;
+ return;
+ }
+ }
+ mText = (text == null) ? null : text.subSequence(0, text.length());
+ }
+
+ /**
+ * Gets the hint text of this node. Only applies to nodes where text can be entered.
+ *
+ * @return The hint text.
+ */
+ public CharSequence getHintText() {
+ return mHintText;
+ }
+
+ /**
+ * Sets the hint text of this node. Only applies to nodes where text can be entered.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param hintText The hint text for this mode.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setHintText(CharSequence hintText) {
+ enforceNotSealed();
+ mHintText = (hintText == null) ? null : hintText.subSequence(0, hintText.length());
+ }
+
+ /**
+ * Sets the error text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param error The error text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setError(CharSequence error) {
+ enforceNotSealed();
+ mError = (error == null) ? null : error.subSequence(0, error.length());
+ }
+
+ /**
+ * Gets the error text of this node.
+ *
+ * @return The error text.
+ */
+ public CharSequence getError() {
+ return mError;
+ }
+
+ /**
+ * Gets the content description of this node.
+ *
+ * @return The content description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the content description of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param contentDescription The content description.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ enforceNotSealed();
+ mContentDescription = (contentDescription == null) ? null
+ : contentDescription.subSequence(0, contentDescription.length());
+ }
+
+ /**
+ * Sets the view for which the view represented by this info serves as a
+ * label for accessibility purposes.
+ *
+ * @param labeled The view for which this info serves as a label.
+ */
+ public void setLabelFor(View labeled) {
+ setLabelFor(labeled, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the view for which the view represented by this info serves as a
+ * label for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the labeled.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root whose virtual descendant serves as a label.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setLabelFor(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node info for which the view represented by this info serves as
+ * a label for accessibility purposes.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The labeled info.
+ */
+ public AccessibilityNodeInfo getLabelFor() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mLabelForId);
+ }
+
+ /**
+ * Sets the view which serves as the label of the view represented by
+ * this info for accessibility purposes.
+ *
+ * @param label The view that labels this node's source.
+ */
+ public void setLabeledBy(View label) {
+ setLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the view which serves as the label of the view represented by
+ * this info for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the label.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root whose virtual descendant labels this node's source.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setLabeledBy(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node info which serves as the label of the view represented by
+ * this info for accessibility purposes.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The label.
+ */
+ public AccessibilityNodeInfo getLabeledBy() {
+ enforceSealed();
+ return getNodeForAccessibilityId(mLabeledById);
+ }
+
+ /**
+ * Sets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param viewIdResName The id resource name.
+ */
+ public void setViewIdResourceName(String viewIdResName) {
+ enforceNotSealed();
+ mViewIdResourceName = viewIdResName;
+ }
+
+ /**
+ * Gets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the source view id of an {@link AccessibilityNodeInfo} the
+ * client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+
+ * @return The id resource name.
+ */
+ public String getViewIdResourceName() {
+ return mViewIdResourceName;
+ }
+
+ /**
+ * Gets the text selection start or the cursor position.
+ * <p>
+ * If no text is selected, both this method and
+ * {@link AccessibilityNodeInfo#getTextSelectionEnd()} return the same value:
+ * the current location of the cursor.
+ * </p>
+ *
+ * @return The text selection start, the cursor location if there is no selection, or -1 if
+ * there is no text selection and no cursor.
+ */
+ public int getTextSelectionStart() {
+ return mTextSelectionStart;
+ }
+
+ /**
+ * Gets the text selection end if text is selected.
+ * <p>
+ * If no text is selected, both this method and
+ * {@link AccessibilityNodeInfo#getTextSelectionStart()} return the same value:
+ * the current location of the cursor.
+ * </p>
+ *
+ * @return The text selection end, the cursor location if there is no selection, or -1 if
+ * there is no text selection and no cursor.
+ */
+ public int getTextSelectionEnd() {
+ return mTextSelectionEnd;
+ }
+
+ /**
+ * Sets the text selection start and end.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param start The text selection start.
+ * @param end The text selection end.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTextSelection(int start, int end) {
+ enforceNotSealed();
+ mTextSelectionStart = start;
+ mTextSelectionEnd = end;
+ }
+
+ /**
+ * Gets the input type of the source as defined by {@link InputType}.
+ *
+ * @return The input type.
+ */
+ public int getInputType() {
+ return mInputType;
+ }
+
+ /**
+ * Sets the input type of the source as defined by {@link InputType}.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an
+ * AccessibilityService.
+ * </p>
+ *
+ * @param inputType The input type.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setInputType(int inputType) {
+ enforceNotSealed();
+ mInputType = inputType;
+ }
+
+ /**
+ * Gets an optional bundle with extra data. The bundle
+ * is lazily created and never <code>null</code>.
+ * <p>
+ * <strong>Note:</strong> It is recommended to use the package
+ * name of your application as a prefix for the keys to avoid
+ * collisions which may confuse an accessibility service if the
+ * same key has different meaning when emitted from different
+ * applications.
+ * </p>
+ *
+ * @return The bundle.
+ */
+ public Bundle getExtras() {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ return mExtras;
+ }
+
+ /**
+ * Check if a node has an extras bundle
+ * @hide
+ */
+ public boolean hasExtras() {
+ return mExtras != null;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) != 0;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ enforceNotSealed();
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Sets the unique id of the IAccessibilityServiceConnection over which
+ * this instance can send requests to the system.
+ *
+ * @param connectionId The connection id.
+ *
+ * @hide
+ */
+ public void setConnectionId(int connectionId) {
+ enforceNotSealed();
+ mConnectionId = connectionId;
+ }
+
+ /**
+ * Get the connection ID.
+ *
+ * @return The connection id
+ *
+ * @hide
+ */
+ public int getConnectionId() {
+ return mConnectionId;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Sets the id of the source node.
+ *
+ * @param sourceId The id.
+ * @param windowId The window id.
+ *
+ * @hide
+ */
+ public void setSourceNodeId(long sourceId, int windowId) {
+ enforceNotSealed();
+ mSourceNodeId = sourceId;
+ mWindowId = windowId;
+ }
+
+ /**
+ * Gets the id of the source node.
+ *
+ * @return The id.
+ *
+ * @hide
+ */
+ public long getSourceNodeId() {
+ return mSourceNodeId;
+ }
+
+ /**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ public void setSealed(boolean sealed) {
+ mSealed = sealed;
+ }
+
+ /**
+ * Gets if this instance is sealed.
+ *
+ * @return Whether is sealed.
+ *
+ * @hide
+ */
+ public boolean isSealed() {
+ return mSealed;
+ }
+
+ /**
+ * Enforces that this instance is sealed.
+ *
+ * @throws IllegalStateException If this instance is not sealed.
+ *
+ * @hide
+ */
+ protected void enforceSealed() {
+ if (!isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a not sealed instance.");
+ }
+ }
+
+ private void enforceValidFocusDirection(int direction) {
+ switch (direction) {
+ case View.FOCUS_DOWN:
+ case View.FOCUS_UP:
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_BACKWARD:
+ return;
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ }
+
+ private void enforceValidFocusType(int focusType) {
+ switch (focusType) {
+ case FOCUS_INPUT:
+ case FOCUS_ACCESSIBILITY:
+ return;
+ default:
+ throw new IllegalArgumentException("Unknown focus type: " + focusType);
+ }
+ }
+
+ /**
+ * Enforces that this instance is not sealed.
+ *
+ * @throws IllegalStateException If this instance is sealed.
+ *
+ * @hide
+ */
+ protected void enforceNotSealed() {
+ if (isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a sealed instance.");
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one
+ * and sets the source.
+ *
+ * @param source The source view.
+ * @return An instance.
+ *
+ * @see #setSource(View)
+ */
+ public static AccessibilityNodeInfo obtain(View source) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setSource(source);
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one
+ * and sets the source.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ * @return An instance.
+ *
+ * @see #setSource(View, int)
+ */
+ public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setSource(root, virtualDescendantId);
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available otherwise a new one.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityNodeInfo obtain() {
+ AccessibilityNodeInfo info = sPool.acquire();
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.incrementAndGet();
+ }
+ return (info != null) ? info : new AccessibilityNodeInfo();
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * create. The returned instance is initialized from the given
+ * <code>info</code>.
+ *
+ * @param info The other info.
+ * @return An instance.
+ */
+ public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
+ AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
+ infoClone.init(info);
+ return infoClone;
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <strong>Note:</strong> You must not touch the object after calling this function.
+ *
+ * @throws IllegalStateException If the info is already recycled.
+ */
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.decrementAndGet();
+ }
+ }
+
+ /**
+ * Specify a counter that will be incremented on obtain() and decremented on recycle()
+ *
+ * @hide
+ */
+ @TestApi
+ public static void setNumInstancesInUseCounter(AtomicInteger counter) {
+ sNumInstancesInUse = counter;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <strong>Note:</strong> After the instance is written to a parcel it
+ * is recycled. You must not touch the object after calling this function.
+ * </p>
+ */
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ // Write bit set of indices of fields with values differing from default
+ long nonDefaultFields = 0;
+ int fieldIndex = 0; // index of the current field
+ if (isSealed() != DEFAULT.isSealed()) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mSourceNodeId != DEFAULT.mSourceNodeId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mWindowId != DEFAULT.mWindowId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mParentNodeId != DEFAULT.mParentNodeId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLabelForId != DEFAULT.mLabelForId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mChildNodeIds, DEFAULT.mChildNodeIds)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mBoundsInParent, DEFAULT.mBoundsInParent)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mBoundsInScreen, DEFAULT.mBoundsInScreen)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mActions, DEFAULT.mActions)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mMaxTextLength != DEFAULT.mMaxTextLength) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mMovementGranularities != DEFAULT.mMovementGranularities) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mBooleanProperties != DEFAULT.mBooleanProperties) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mPackageName, DEFAULT.mPackageName)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mClassName, DEFAULT.mClassName)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mText, DEFAULT.mText)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mHintText, DEFAULT.mHintText)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mError, DEFAULT.mError)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mContentDescription, DEFAULT.mContentDescription)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mTextSelectionStart != DEFAULT.mTextSelectionStart) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mTextSelectionEnd != DEFAULT.mTextSelectionEnd) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mInputType != DEFAULT.mInputType) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLiveRegion != DEFAULT.mLiveRegion) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mDrawingOrderInParent != DEFAULT.mDrawingOrderInParent) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mExtraDataKeys, DEFAULT.mExtraDataKeys)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mExtras, DEFAULT.mExtras)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mRangeInfo, DEFAULT.mRangeInfo)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mCollectionInfo, DEFAULT.mCollectionInfo)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mCollectionItemInfo, DEFAULT.mCollectionItemInfo)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ int totalFields = fieldIndex;
+ parcel.writeLong(nonDefaultFields);
+
+ fieldIndex = 0;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(isSealed() ? 1 : 0);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mSourceNodeId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mWindowId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int childIdsSize = childIds.size();
+ parcel.writeInt(childIdsSize);
+ for (int i = 0; i < childIdsSize; i++) {
+ parcel.writeLong(childIds.get(i));
+ }
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mBoundsInParent.top);
+ parcel.writeInt(mBoundsInParent.bottom);
+ parcel.writeInt(mBoundsInParent.left);
+ parcel.writeInt(mBoundsInParent.right);
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mBoundsInScreen.top);
+ parcel.writeInt(mBoundsInScreen.bottom);
+ parcel.writeInt(mBoundsInScreen.left);
+ parcel.writeInt(mBoundsInScreen.right);
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ if (mActions != null && !mActions.isEmpty()) {
+ final int actionCount = mActions.size();
+
+ int nonStandardActionCount = 0;
+ int defaultStandardActions = 0;
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (isDefaultStandardAction(action)) {
+ defaultStandardActions |= action.mSerializationFlag;
+ } else {
+ nonStandardActionCount++;
+ }
+ }
+ parcel.writeInt(defaultStandardActions);
+
+ parcel.writeInt(nonStandardActionCount);
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (!isDefaultStandardAction(action)) {
+ parcel.writeInt(action.getId());
+ parcel.writeCharSequence(action.getLabel());
+ }
+ }
+ } else {
+ parcel.writeInt(0);
+ parcel.writeInt(0);
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMaxTextLength);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMovementGranularities);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mBooleanProperties);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPackageName);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mClassName);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mText);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mHintText);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mError);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeCharSequence(mContentDescription);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionEnd);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mInputType);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mLiveRegion);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mDrawingOrderInParent);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeStringList(mExtraDataKeys);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeBundle(mExtras);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mRangeInfo.getType());
+ parcel.writeFloat(mRangeInfo.getMin());
+ parcel.writeFloat(mRangeInfo.getMax());
+ parcel.writeFloat(mRangeInfo.getCurrent());
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mCollectionInfo.getRowCount());
+ parcel.writeInt(mCollectionInfo.getColumnCount());
+ parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
+ parcel.writeInt(mCollectionInfo.getSelectionMode());
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mCollectionItemInfo.getRowIndex());
+ parcel.writeInt(mCollectionItemInfo.getRowSpan());
+ parcel.writeInt(mCollectionItemInfo.getColumnIndex());
+ parcel.writeInt(mCollectionItemInfo.getColumnSpan());
+ parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
+ parcel.writeInt(mCollectionItemInfo.isSelected() ? 1 : 0);
+ }
+
+ if (DEBUG) {
+ fieldIndex--;
+ if (totalFields != fieldIndex) {
+ throw new IllegalStateException("Number of fields mismatch: " + totalFields
+ + " vs " + fieldIndex);
+ }
+ }
+
+ // Since instances of this class are fetched via synchronous i.e. blocking
+ // calls in IPCs we always recycle as soon as the instance is marshaled.
+ recycle();
+ }
+
+ /**
+ * Initializes this instance from another one.
+ *
+ * @param other The other instance.
+ */
+ private void init(AccessibilityNodeInfo other) {
+ mSealed = other.mSealed;
+ mSourceNodeId = other.mSourceNodeId;
+ mParentNodeId = other.mParentNodeId;
+ mLabelForId = other.mLabelForId;
+ mLabeledById = other.mLabeledById;
+ mTraversalBefore = other.mTraversalBefore;
+ mTraversalAfter = other.mTraversalAfter;
+ mWindowId = other.mWindowId;
+ mConnectionId = other.mConnectionId;
+ mBoundsInParent.set(other.mBoundsInParent);
+ mBoundsInScreen.set(other.mBoundsInScreen);
+ mPackageName = other.mPackageName;
+ mClassName = other.mClassName;
+ mText = other.mText;
+ mHintText = other.mHintText;
+ mError = other.mError;
+ mContentDescription = other.mContentDescription;
+ mViewIdResourceName = other.mViewIdResourceName;
+
+ if (mActions != null) mActions.clear();
+ final ArrayList<AccessibilityAction> otherActions = other.mActions;
+ if (otherActions != null && otherActions.size() > 0) {
+ if (mActions == null) {
+ mActions = new ArrayList(otherActions);
+ } else {
+ mActions.addAll(other.mActions);
+ }
+ }
+
+ mBooleanProperties = other.mBooleanProperties;
+ mMaxTextLength = other.mMaxTextLength;
+ mMovementGranularities = other.mMovementGranularities;
+
+
+ if (mChildNodeIds != null) mChildNodeIds.clear();
+ final LongArray otherChildNodeIds = other.mChildNodeIds;
+ if (otherChildNodeIds != null && otherChildNodeIds.size() > 0) {
+ if (mChildNodeIds == null) {
+ mChildNodeIds = otherChildNodeIds.clone();
+ } else {
+ mChildNodeIds.addAll(otherChildNodeIds);
+ }
+ }
+
+ mTextSelectionStart = other.mTextSelectionStart;
+ mTextSelectionEnd = other.mTextSelectionEnd;
+ mInputType = other.mInputType;
+ mLiveRegion = other.mLiveRegion;
+ mDrawingOrderInParent = other.mDrawingOrderInParent;
+
+ mExtraDataKeys = other.mExtraDataKeys;
+
+ mExtras = other.mExtras != null ? new Bundle(other.mExtras) : null;
+
+ if (mRangeInfo != null) mRangeInfo.recycle();
+ mRangeInfo = (other.mRangeInfo != null)
+ ? RangeInfo.obtain(other.mRangeInfo) : null;
+ if (mCollectionInfo != null) mCollectionInfo.recycle();
+ mCollectionInfo = (other.mCollectionInfo != null)
+ ? CollectionInfo.obtain(other.mCollectionInfo) : null;
+ if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
+ mCollectionItemInfo = (other.mCollectionItemInfo != null)
+ ? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null;
+ }
+
+ /**
+ * Creates a new instance from a {@link Parcel}.
+ *
+ * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
+ */
+ private void initFromParcel(Parcel parcel) {
+ // Bit mask of non-default-valued field indices
+ long nonDefaultFields = parcel.readLong();
+ int fieldIndex = 0;
+ final boolean sealed = isBitSet(nonDefaultFields, fieldIndex++)
+ ? (parcel.readInt() == 1)
+ : DEFAULT.mSealed;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mSourceNodeId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mWindowId = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int childrenSize = parcel.readInt();
+ if (childrenSize <= 0) {
+ mChildNodeIds = null;
+ } else {
+ mChildNodeIds = new LongArray(childrenSize);
+ for (int i = 0; i < childrenSize; i++) {
+ final long childId = parcel.readLong();
+ mChildNodeIds.add(childId);
+ }
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mBoundsInParent.top = parcel.readInt();
+ mBoundsInParent.bottom = parcel.readInt();
+ mBoundsInParent.left = parcel.readInt();
+ mBoundsInParent.right = parcel.readInt();
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mBoundsInScreen.top = parcel.readInt();
+ mBoundsInScreen.bottom = parcel.readInt();
+ mBoundsInScreen.left = parcel.readInt();
+ mBoundsInScreen.right = parcel.readInt();
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int standardActions = parcel.readInt();
+ addStandardActions(standardActions);
+ final int nonStandardActionCount = parcel.readInt();
+ for (int i = 0; i < nonStandardActionCount; i++) {
+ final AccessibilityAction action = new AccessibilityAction(
+ parcel.readInt(), parcel.readCharSequence());
+ addActionUnchecked(action);
+ }
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mMaxTextLength = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mMovementGranularities = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mBooleanProperties = parcel.readInt();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mPackageName = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mClassName = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mText = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mHintText = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mError = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mContentDescription = parcel.readCharSequence();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionEnd = parcel.readInt();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mInputType = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLiveRegion = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mDrawingOrderInParent = parcel.readInt();
+
+ mExtraDataKeys = isBitSet(nonDefaultFields, fieldIndex++)
+ ? parcel.createStringArrayList()
+ : null;
+
+ mExtras = isBitSet(nonDefaultFields, fieldIndex++)
+ ? parcel.readBundle()
+ : null;
+
+ if (mRangeInfo != null) mRangeInfo.recycle();
+ mRangeInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? RangeInfo.obtain(
+ parcel.readInt(),
+ parcel.readFloat(),
+ parcel.readFloat(),
+ parcel.readFloat())
+ : null;
+
+ if (mCollectionInfo != null) mCollectionInfo.recycle();
+ mCollectionInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? CollectionInfo.obtain(
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt() == 1,
+ parcel.readInt())
+ : null;
+
+ if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
+ mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? CollectionItemInfo.obtain(
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt() == 1,
+ parcel.readInt() == 1)
+ : null;
+
+ mSealed = sealed;
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ private void clear() {
+ init(DEFAULT);
+ }
+
+ private static boolean isDefaultStandardAction(AccessibilityAction action) {
+ return action.mSerializationFlag != -1 && TextUtils.isEmpty(action.getLabel());
+ }
+
+ private static AccessibilityAction getActionSingleton(int actionId) {
+ final int actions = AccessibilityAction.sStandardActions.size();
+ for (int i = 0; i < actions; i++) {
+ AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
+ if (actionId == currentAction.getId()) {
+ return currentAction;
+ }
+ }
+
+ return null;
+ }
+
+ private static AccessibilityAction getActionSingletonBySerializationFlag(int flag) {
+ final int actions = AccessibilityAction.sStandardActions.size();
+ for (int i = 0; i < actions; i++) {
+ AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
+ if (flag == currentAction.mSerializationFlag) {
+ return currentAction;
+ }
+ }
+
+ return null;
+ }
+
+ private void addStandardActions(int serializationIdMask) {
+ int remainingIds = serializationIdMask;
+ while (remainingIds > 0) {
+ final int id = 1 << Integer.numberOfTrailingZeros(remainingIds);
+ remainingIds &= ~id;
+ AccessibilityAction action = getActionSingletonBySerializationFlag(id);
+ addAction(action);
+ }
+ }
+
+ /**
+ * Gets the human readable action symbolic name.
+ *
+ * @param action The action.
+ * @return The symbolic name.
+ */
+ private static String getActionSymbolicName(int action) {
+ switch (action) {
+ case ACTION_FOCUS:
+ return "ACTION_FOCUS";
+ case ACTION_CLEAR_FOCUS:
+ return "ACTION_CLEAR_FOCUS";
+ case ACTION_SELECT:
+ return "ACTION_SELECT";
+ case ACTION_CLEAR_SELECTION:
+ return "ACTION_CLEAR_SELECTION";
+ case ACTION_CLICK:
+ return "ACTION_CLICK";
+ case ACTION_LONG_CLICK:
+ return "ACTION_LONG_CLICK";
+ case ACTION_ACCESSIBILITY_FOCUS:
+ return "ACTION_ACCESSIBILITY_FOCUS";
+ case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ return "ACTION_CLEAR_ACCESSIBILITY_FOCUS";
+ case ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY";
+ case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+ return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY";
+ case ACTION_NEXT_HTML_ELEMENT:
+ return "ACTION_NEXT_HTML_ELEMENT";
+ case ACTION_PREVIOUS_HTML_ELEMENT:
+ return "ACTION_PREVIOUS_HTML_ELEMENT";
+ case ACTION_SCROLL_FORWARD:
+ return "ACTION_SCROLL_FORWARD";
+ case ACTION_SCROLL_BACKWARD:
+ return "ACTION_SCROLL_BACKWARD";
+ case ACTION_CUT:
+ return "ACTION_CUT";
+ case ACTION_COPY:
+ return "ACTION_COPY";
+ case ACTION_PASTE:
+ return "ACTION_PASTE";
+ case ACTION_SET_SELECTION:
+ return "ACTION_SET_SELECTION";
+ case ACTION_EXPAND:
+ return "ACTION_EXPAND";
+ case ACTION_COLLAPSE:
+ return "ACTION_COLLAPSE";
+ case ACTION_DISMISS:
+ return "ACTION_DISMISS";
+ case ACTION_SET_TEXT:
+ return "ACTION_SET_TEXT";
+ case R.id.accessibilityActionShowOnScreen:
+ return "ACTION_SHOW_ON_SCREEN";
+ case R.id.accessibilityActionScrollToPosition:
+ return "ACTION_SCROLL_TO_POSITION";
+ case R.id.accessibilityActionScrollUp:
+ return "ACTION_SCROLL_UP";
+ case R.id.accessibilityActionScrollLeft:
+ return "ACTION_SCROLL_LEFT";
+ case R.id.accessibilityActionScrollDown:
+ return "ACTION_SCROLL_DOWN";
+ case R.id.accessibilityActionScrollRight:
+ return "ACTION_SCROLL_RIGHT";
+ case R.id.accessibilityActionSetProgress:
+ return "ACTION_SET_PROGRESS";
+ case R.id.accessibilityActionContextClick:
+ return "ACTION_CONTEXT_CLICK";
+ default:
+ return "ACTION_UNKNOWN";
+ }
+ }
+
+ /**
+ * Gets the human readable movement granularity symbolic name.
+ *
+ * @param granularity The granularity.
+ * @return The symbolic name.
+ */
+ private static String getMovementGranularitySymbolicName(int granularity) {
+ switch (granularity) {
+ case MOVEMENT_GRANULARITY_CHARACTER:
+ return "MOVEMENT_GRANULARITY_CHARACTER";
+ case MOVEMENT_GRANULARITY_WORD:
+ return "MOVEMENT_GRANULARITY_WORD";
+ case MOVEMENT_GRANULARITY_LINE:
+ return "MOVEMENT_GRANULARITY_LINE";
+ case MOVEMENT_GRANULARITY_PARAGRAPH:
+ return "MOVEMENT_GRANULARITY_PARAGRAPH";
+ case MOVEMENT_GRANULARITY_PAGE:
+ return "MOVEMENT_GRANULARITY_PAGE";
+ default:
+ throw new IllegalArgumentException("Unknown movement granularity: " + granularity);
+ }
+ }
+
+ private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
+ return ((mWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+ && (getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID)
+ && (mConnectionId != UNDEFINED_CONNECTION_ID));
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null) {
+ return false;
+ }
+ if (getClass() != object.getClass()) {
+ return false;
+ }
+ AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
+ if (mSourceNodeId != other.mSourceNodeId) {
+ return false;
+ }
+ if (mWindowId != other.mWindowId) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getAccessibilityViewId(mSourceNodeId);
+ result = prime * result + getVirtualDescendantId(mSourceNodeId);
+ result = prime * result + mWindowId;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(super.toString());
+
+ if (DEBUG) {
+ builder.append("; sourceNodeId: " + mSourceNodeId);
+ builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
+ builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
+ builder.append("; mParentNodeId: " + mParentNodeId);
+ builder.append("; traversalBefore: ").append(mTraversalBefore);
+ builder.append("; traversalAfter: ").append(mTraversalAfter);
+
+ int granularities = mMovementGranularities;
+ builder.append("; MovementGranularities: [");
+ while (granularities != 0) {
+ final int granularity = 1 << Integer.numberOfTrailingZeros(granularities);
+ granularities &= ~granularity;
+ builder.append(getMovementGranularitySymbolicName(granularity));
+ if (granularities != 0) {
+ builder.append(", ");
+ }
+ }
+ builder.append("]");
+
+ builder.append("; childAccessibilityIds: [");
+ final LongArray childIds = mChildNodeIds;
+ if (childIds != null) {
+ for (int i = 0, count = childIds.size(); i < count; i++) {
+ builder.append(childIds.get(i));
+ if (i < count - 1) {
+ builder.append(", ");
+ }
+ }
+ }
+ builder.append("]");
+ }
+
+ builder.append("; boundsInParent: " + mBoundsInParent);
+ builder.append("; boundsInScreen: " + mBoundsInScreen);
+
+ builder.append("; packageName: ").append(mPackageName);
+ builder.append("; className: ").append(mClassName);
+ builder.append("; text: ").append(mText);
+ builder.append("; error: ").append(mError);
+ builder.append("; maxTextLength: ").append(mMaxTextLength);
+ builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; viewIdResName: ").append(mViewIdResourceName);
+
+ builder.append("; checkable: ").append(isCheckable());
+ builder.append("; checked: ").append(isChecked());
+ builder.append("; focusable: ").append(isFocusable());
+ builder.append("; focused: ").append(isFocused());
+ builder.append("; selected: ").append(isSelected());
+ builder.append("; clickable: ").append(isClickable());
+ builder.append("; longClickable: ").append(isLongClickable());
+ builder.append("; contextClickable: ").append(isContextClickable());
+ builder.append("; enabled: ").append(isEnabled());
+ builder.append("; password: ").append(isPassword());
+ builder.append("; scrollable: ").append(isScrollable());
+ builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
+ builder.append("; actions: ").append(mActions);
+
+ return builder.toString();
+ }
+
+ private AccessibilityNodeInfo getNodeForAccessibilityId(long accessibilityId) {
+ if (!canPerformRequestOverConnection(accessibilityId)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mWindowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+ }
+
+ /**
+ * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}.
+ * Each action has a unique id that is mandatory and optional data.
+ * <p>
+ * There are three categories of actions:
+ * <ul>
+ * <li><strong>Standard actions</strong> - These are actions that are reported and
+ * handled by the standard UI widgets in the platform. For each standard action
+ * there is a static constant defined in this class, e.g. {@link #ACTION_FOCUS}.
+ * These actions will have {@code null} labels.
+ * </li>
+ * <li><strong>Custom actions action</strong> - These are actions that are reported
+ * and handled by custom widgets. i.e. ones that are not part of the UI toolkit. For
+ * example, an application may define a custom action for clearing the user history.
+ * </li>
+ * <li><strong>Overriden standard actions</strong> - These are actions that override
+ * standard actions to customize them. For example, an app may add a label to the
+ * standard {@link #ACTION_CLICK} action to announce that this action clears browsing history.
+ * </ul>
+ * </p>
+ * <p>
+ * Actions are typically added to an {@link AccessibilityNodeInfo} by using
+ * {@link AccessibilityNodeInfo#addAction(AccessibilityAction)} within
+ * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} and are performed
+ * within {@link View#performAccessibilityAction(int, Bundle)}.
+ * </p>
+ * <p class="note">
+ * <strong>Note:</strong> Views which support these actions should invoke
+ * {@link View#setImportantForAccessibility(int)} with
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an {@link AccessibilityService}
+ * can discover the set of supported actions.
+ * </p>
+ */
+ public static final class AccessibilityAction {
+
+ /** @hide */
+ public static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>();
+
+ /**
+ * Action that gives input focus to the node.
+ */
+ public static final AccessibilityAction ACTION_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS);
+
+ /**
+ * Action that clears input focus of the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
+
+ /**
+ * Action that selects the node.
+ */
+ public static final AccessibilityAction ACTION_SELECT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SELECT);
+
+ /**
+ * Action that deselects the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_SELECTION =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+
+ /**
+ * Action that clicks on the node info.
+ */
+ public static final AccessibilityAction ACTION_CLICK =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+ /**
+ * Action that long clicks on the node.
+ */
+ public static final AccessibilityAction ACTION_LONG_CLICK =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+
+ /**
+ * Action that gives accessibility focus to the node.
+ */
+ public static final AccessibilityAction ACTION_ACCESSIBILITY_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+
+ /**
+ * Action that clears accessibility focus of the node.
+ */
+ public static final AccessibilityAction ACTION_CLEAR_ACCESSIBILITY_FOCUS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+
+ /**
+ * Action that requests to go to the next entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the previous character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityAction.ACTION_NEXT_AT_MOVEMENT_GRANULARITY.getId(),
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see AccessibilityNodeInfo#setMovementGranularities(int)
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * @see AccessibilityNodeInfo#getMovementGranularities()
+ * AccessibilityNodeInfo.getMovementGranularities()
+ *
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+
+ /**
+ * Action that requests to go to the previous entity in this node's text
+ * at a given movement granularity. For example, move to the next character,
+ * word, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the next character and do not extend selection.
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
+ * info.performAction(AccessibilityAction.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY.getId(),
+ * arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
+ * @see AccessibilityNodeInfo#setMovementGranularities(int)
+ * AccessibilityNodeInfo.setMovementGranularities(int)
+ * @see AccessibilityNodeInfo#getMovementGranularities()
+ * AccessibilityNodeInfo.getMovementGranularities()
+ *
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_CHARACTER
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_WORD
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_LINE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PARAGRAPH
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ * @see AccessibilityNodeInfo#MOVEMENT_GRANULARITY_PAGE
+ * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+ */
+ public static final AccessibilityAction ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY =
+ new AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+
+ /**
+ * Action to move to the next HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityAction.ACTION_NEXT_HTML_ELEMENT.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final AccessibilityAction ACTION_NEXT_HTML_ELEMENT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+
+ /**
+ * Action to move to the previous HTML element of a given type. For example, move
+ * to the BUTTON, INPUT, TABLE, etc.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_HTML_ELEMENT_STRING
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
+ * info.performAction(AccessibilityAction.ACTION_PREVIOUS_HTML_ELEMENT.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ */
+ public static final AccessibilityAction ACTION_PREVIOUS_HTML_ELEMENT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+
+ /**
+ * Action to scroll the node content forward.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_FORWARD =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+
+ /**
+ * Action to scroll the node content backward.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_BACKWARD =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+
+ /**
+ * Action to copy the current selection to the clipboard.
+ */
+ public static final AccessibilityAction ACTION_COPY =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_COPY);
+
+ /**
+ * Action to paste the current clipboard content.
+ */
+ public static final AccessibilityAction ACTION_PASTE =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_PASTE);
+
+ /**
+ * Action to cut the current selection and place it to the clipboard.
+ */
+ public static final AccessibilityAction ACTION_CUT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CUT);
+
+ /**
+ * Action to set the selection. Performing this action with no arguments
+ * clears the selection.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT},
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+ * info.performAction(AccessibilityAction.ACTION_SET_SELECTION.getId(), arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT
+ */
+ public static final AccessibilityAction ACTION_SET_SELECTION =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+
+ /**
+ * Action to expand an expandable node.
+ */
+ public static final AccessibilityAction ACTION_EXPAND =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_EXPAND);
+
+ /**
+ * Action to collapse an expandable node.
+ */
+ public static final AccessibilityAction ACTION_COLLAPSE =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
+
+ /**
+ * Action to dismiss a dismissable node.
+ */
+ public static final AccessibilityAction ACTION_DISMISS =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_DISMISS);
+
+ /**
+ * Action that sets the text of the node. Performing the action without argument,
+ * using <code> null</code> or empty {@link CharSequence} will clear the text. This
+ * action will also put the cursor at the end of text.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+ * "android");
+ * info.performAction(AccessibilityAction.ACTION_SET_TEXT.getId(), arguments);
+ * </code></pre></p>
+ */
+ public static final AccessibilityAction ACTION_SET_TEXT =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_SET_TEXT);
+
+ /**
+ * Action that requests the node make its bounding rectangle visible
+ * on the screen, scrolling if necessary just enough.
+ *
+ * @see View#requestRectangleOnScreen(Rect)
+ */
+ public static final AccessibilityAction ACTION_SHOW_ON_SCREEN =
+ new AccessibilityAction(R.id.accessibilityActionShowOnScreen);
+
+ /**
+ * Action that scrolls the node to make the specified collection
+ * position visible on screen.
+ * <p>
+ * <strong>Arguments:</strong>
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT}</li>
+ * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT}</li>
+ * <ul>
+ *
+ * @see AccessibilityNodeInfo#getCollectionInfo()
+ */
+ public static final AccessibilityAction ACTION_SCROLL_TO_POSITION =
+ new AccessibilityAction(R.id.accessibilityActionScrollToPosition);
+
+ /**
+ * Action to scroll the node content up.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_UP =
+ new AccessibilityAction(R.id.accessibilityActionScrollUp);
+
+ /**
+ * Action to scroll the node content left.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_LEFT =
+ new AccessibilityAction(R.id.accessibilityActionScrollLeft);
+
+ /**
+ * Action to scroll the node content down.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_DOWN =
+ new AccessibilityAction(R.id.accessibilityActionScrollDown);
+
+ /**
+ * Action to scroll the node content right.
+ */
+ public static final AccessibilityAction ACTION_SCROLL_RIGHT =
+ new AccessibilityAction(R.id.accessibilityActionScrollRight);
+
+ /**
+ * Action that context clicks the node.
+ */
+ public static final AccessibilityAction ACTION_CONTEXT_CLICK =
+ new AccessibilityAction(R.id.accessibilityActionContextClick);
+
+ /**
+ * Action that sets progress between {@link RangeInfo#getMin() RangeInfo.getMin()} and
+ * {@link RangeInfo#getMax() RangeInfo.getMax()}. It should use the same value type as
+ * {@link RangeInfo#getType() RangeInfo.getType()}
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_PROGRESS_VALUE}
+ *
+ * @see RangeInfo
+ */
+ public static final AccessibilityAction ACTION_SET_PROGRESS =
+ new AccessibilityAction(R.id.accessibilityActionSetProgress);
+
+ /**
+ * Action to move a window to a new location.
+ * <p>
+ * <strong>Arguments:</strong>
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_X}
+ * {@link AccessibilityNodeInfo#ACTION_ARGUMENT_MOVE_WINDOW_Y}
+ */
+ public static final AccessibilityAction ACTION_MOVE_WINDOW =
+ new AccessibilityAction(R.id.accessibilityActionMoveWindow);
+
+ private final int mActionId;
+ private final CharSequence mLabel;
+
+ /** @hide */
+ public int mSerializationFlag = -1;
+
+ /**
+ * Creates a new AccessibilityAction. For adding a standard action without a specific label,
+ * use the static constants.
+ *
+ * You can also override the description for one the standard actions. Below is an example
+ * how to override the standard click action by adding a custom label:
+ * <pre>
+ * AccessibilityAction action = new AccessibilityAction(
+ * AccessibilityAction.ACTION_CLICK.getId(), getLocalizedLabel());
+ * node.addAction(action);
+ * </pre>
+ *
+ * @param actionId The id for this action. This should either be one of the
+ * standard actions or a specific action for your app. In that case it is
+ * required to use a resource identifier.
+ * @param label The label for the new AccessibilityAction.
+ */
+ public AccessibilityAction(int actionId, @Nullable CharSequence label) {
+ if ((actionId & ACTION_TYPE_MASK) == 0 && Integer.bitCount(actionId) != 1) {
+ throw new IllegalArgumentException("Invalid standard action id");
+ }
+
+ mActionId = actionId;
+ mLabel = label;
+ }
+
+ /**
+ * Constructor for a {@link #sStandardActions standard} action
+ */
+ private AccessibilityAction(int standardActionId) {
+ this(standardActionId, null);
+
+ mSerializationFlag = (int) bitAt(sStandardActions.size());
+ sStandardActions.add(this);
+ }
+
+ /**
+ * Gets the id for this action.
+ *
+ * @return The action id.
+ */
+ public int getId() {
+ return mActionId;
+ }
+
+ /**
+ * Gets the label for this action. Its purpose is to describe the
+ * action to user.
+ *
+ * @return The label.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public int hashCode() {
+ return mActionId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+
+ if (other == this) {
+ return true;
+ }
+
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+
+ return mActionId == ((AccessibilityAction)other).mActionId;
+ }
+
+ @Override
+ public String toString() {
+ return "AccessibilityAction: " + getActionSymbolicName(mActionId) + " - " + mLabel;
+ }
+ }
+
+ /**
+ * Class with information if a node is a range. Use
+ * {@link RangeInfo#obtain(int, float, float, float)} to get an instance. Recycling is
+ * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+ */
+ public static final class RangeInfo {
+ private static final int MAX_POOL_SIZE = 10;
+
+ /** Range type: integer. */
+ public static final int RANGE_TYPE_INT = 0;
+ /** Range type: float. */
+ public static final int RANGE_TYPE_FLOAT = 1;
+ /** Range type: percent with values from zero to one.*/
+ public static final int RANGE_TYPE_PERCENT = 2;
+
+ private static final SynchronizedPool<RangeInfo> sPool =
+ new SynchronizedPool<AccessibilityNodeInfo.RangeInfo>(MAX_POOL_SIZE);
+
+ private int mType;
+ private float mMin;
+ private float mMax;
+ private float mCurrent;
+
+ /**
+ * Obtains a pooled instance that is a clone of another one.
+ *
+ * @param other The instance to clone.
+ *
+ * @hide
+ */
+ public static RangeInfo obtain(RangeInfo other) {
+ return obtain(other.mType, other.mMin, other.mMax, other.mCurrent);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param type The type of the range.
+ * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+ * minimum.
+ * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+ * maximum.
+ * @param current The current value.
+ */
+ public static RangeInfo obtain(int type, float min, float max, float current) {
+ RangeInfo info = sPool.acquire();
+ if (info == null) {
+ return new RangeInfo(type, min, max, current);
+ }
+
+ info.mType = type;
+ info.mMin = min;
+ info.mMax = max;
+ info.mCurrent = current;
+ return info;
+ }
+
+ /**
+ * Creates a new range.
+ *
+ * @param type The type of the range.
+ * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no
+ * minimum.
+ * @param max The maximum value. Use {@code Float.POSITIVE_INFINITY} if the range has no
+ * maximum.
+ * @param current The current value.
+ */
+ private RangeInfo(int type, float min, float max, float current) {
+ mType = type;
+ mMin = min;
+ mMax = max;
+ mCurrent = current;
+ }
+
+ /**
+ * Gets the range type.
+ *
+ * @return The range type.
+ *
+ * @see #RANGE_TYPE_INT
+ * @see #RANGE_TYPE_FLOAT
+ * @see #RANGE_TYPE_PERCENT
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets the minimum value.
+ *
+ * @return The minimum value, or {@code Float.NEGATIVE_INFINITY} if no minimum exists.
+ */
+ public float getMin() {
+ return mMin;
+ }
+
+ /**
+ * Gets the maximum value.
+ *
+ * @return The maximum value, or {@code Float.POSITIVE_INFINITY} if no maximum exists.
+ */
+ public float getMax() {
+ return mMax;
+ }
+
+ /**
+ * Gets the current value.
+ *
+ * @return The current value.
+ */
+ public float getCurrent() {
+ return mCurrent;
+ }
+
+ /**
+ * Recycles this instance.
+ */
+ void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ private void clear() {
+ mType = 0;
+ mMin = 0;
+ mMax = 0;
+ mCurrent = 0;
+ }
+ }
+
+ /**
+ * Class with information if a node is a collection. Use
+ * {@link CollectionInfo#obtain(int, int, boolean)} to get an instance. Recycling is
+ * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+ * <p>
+ * A collection of items has rows and columns and may be hierarchical.
+ * For example, a horizontal list is a collection with one column, as
+ * many rows as the list items, and is not hierarchical; A table is a
+ * collection with several rows, several columns, and is not hierarchical;
+ * A vertical tree is a hierarchical collection with one column and
+ * as many rows as the first level children.
+ * </p>
+ */
+ public static final class CollectionInfo {
+ /** Selection mode where items are not selectable. */
+ public static final int SELECTION_MODE_NONE = 0;
+
+ /** Selection mode where a single item may be selected. */
+ public static final int SELECTION_MODE_SINGLE = 1;
+
+ /** Selection mode where multiple items may be selected. */
+ public static final int SELECTION_MODE_MULTIPLE = 2;
+
+ private static final int MAX_POOL_SIZE = 20;
+
+ private static final SynchronizedPool<CollectionInfo> sPool =
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+
+ private int mRowCount;
+ private int mColumnCount;
+ private boolean mHierarchical;
+ private int mSelectionMode;
+
+ /**
+ * Obtains a pooled instance that is a clone of another one.
+ *
+ * @param other The instance to clone.
+ * @hide
+ */
+ public static CollectionInfo obtain(CollectionInfo other) {
+ return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical,
+ other.mSelectionMode);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ */
+ public static CollectionInfo obtain(int rowCount, int columnCount,
+ boolean hierarchical) {
+ return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public static CollectionInfo obtain(int rowCount, int columnCount,
+ boolean hierarchical, int selectionMode) {
+ final CollectionInfo info = sPool.acquire();
+ if (info == null) {
+ return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode);
+ }
+
+ info.mRowCount = rowCount;
+ info.mColumnCount = columnCount;
+ info.mHierarchical = hierarchical;
+ info.mSelectionMode = selectionMode;
+ return info;
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode.
+ */
+ private CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
+ int selectionMode) {
+ mRowCount = rowCount;
+ mColumnCount = columnCount;
+ mHierarchical = hierarchical;
+ mSelectionMode = selectionMode;
+ }
+
+ /**
+ * Gets the number of rows.
+ *
+ * @return The row count.
+ */
+ public int getRowCount() {
+ return mRowCount;
+ }
+
+ /**
+ * Gets the number of columns.
+ *
+ * @return The column count.
+ */
+ public int getColumnCount() {
+ return mColumnCount;
+ }
+
+ /**
+ * Gets if the collection is a hierarchically ordered.
+ *
+ * @return Whether the collection is hierarchical.
+ */
+ public boolean isHierarchical() {
+ return mHierarchical;
+ }
+
+ /**
+ * Gets the collection's selection mode.
+ *
+ * @return The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public int getSelectionMode() {
+ return mSelectionMode;
+ }
+
+ /**
+ * Recycles this instance.
+ */
+ void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ private void clear() {
+ mRowCount = 0;
+ mColumnCount = 0;
+ mHierarchical = false;
+ mSelectionMode = SELECTION_MODE_NONE;
+ }
+ }
+
+ /**
+ * Class with information if a node is a collection item. Use
+ * {@link CollectionItemInfo#obtain(int, int, int, int, boolean)}
+ * to get an instance. Recycling is handled by the {@link AccessibilityNodeInfo} to which this
+ * object is attached.
+ * <p>
+ * A collection item is contained in a collection, it starts at
+ * a given row and column in the collection, and spans one or
+ * more rows and columns. For example, a header of two related
+ * table columns starts at the first row and the first column,
+ * spans one row and two columns.
+ * </p>
+ */
+ public static final class CollectionItemInfo {
+ private static final int MAX_POOL_SIZE = 20;
+
+ private static final SynchronizedPool<CollectionItemInfo> sPool =
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+
+ /**
+ * Obtains a pooled instance that is a clone of another one.
+ *
+ * @param other The instance to clone.
+ * @hide
+ */
+ public static CollectionItemInfo obtain(CollectionItemInfo other) {
+ return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex,
+ other.mColumnSpan, other.mHeading, other.mSelected);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ */
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading) {
+ return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ * @param selected Whether the item is selected.
+ */
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading, boolean selected) {
+ final CollectionItemInfo info = sPool.acquire();
+ if (info == null) {
+ return new CollectionItemInfo(
+ rowIndex, rowSpan, columnIndex, columnSpan, heading, selected);
+ }
+
+ info.mRowIndex = rowIndex;
+ info.mRowSpan = rowSpan;
+ info.mColumnIndex = columnIndex;
+ info.mColumnSpan = columnSpan;
+ info.mHeading = heading;
+ info.mSelected = selected;
+ return info;
+ }
+
+ private boolean mHeading;
+ private int mColumnIndex;
+ private int mRowIndex;
+ private int mColumnSpan;
+ private int mRowSpan;
+ private boolean mSelected;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ */
+ private CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
+ boolean heading, boolean selected) {
+ mRowIndex = rowIndex;
+ mRowSpan = rowSpan;
+ mColumnIndex = columnIndex;
+ mColumnSpan = columnSpan;
+ mHeading = heading;
+ mSelected = selected;
+ }
+
+ /**
+ * Gets the column index at which the item is located.
+ *
+ * @return The column index.
+ */
+ public int getColumnIndex() {
+ return mColumnIndex;
+ }
+
+ /**
+ * Gets the row index at which the item is located.
+ *
+ * @return The row index.
+ */
+ public int getRowIndex() {
+ return mRowIndex;
+ }
+
+ /**
+ * Gets the number of columns the item spans.
+ *
+ * @return The column span.
+ */
+ public int getColumnSpan() {
+ return mColumnSpan;
+ }
+
+ /**
+ * Gets the number of rows the item spans.
+ *
+ * @return The row span.
+ */
+ public int getRowSpan() {
+ return mRowSpan;
+ }
+
+ /**
+ * Gets if the collection item is a heading. For example, section
+ * heading, table header, etc.
+ *
+ * @return If the item is a heading.
+ */
+ public boolean isHeading() {
+ return mHeading;
+ }
+
+ /**
+ * Gets if the collection item is selected.
+ *
+ * @return If the item is selected.
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ /**
+ * Recycles this instance.
+ */
+ void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ private void clear() {
+ mColumnIndex = 0;
+ mColumnSpan = 0;
+ mRowIndex = 0;
+ mRowSpan = 0;
+ mHeading = false;
+ mSelected = false;
+ }
+ }
+
+ /**
+ * @see android.os.Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
+ new Parcelable.Creator<AccessibilityNodeInfo>() {
+ @Override
+ public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ @Override
+ public AccessibilityNodeInfo[] newArray(int size) {
+ return new AccessibilityNodeInfo[size];
+ }
+ };
+}
diff --git a/android/view/accessibility/AccessibilityNodeProvider.java b/android/view/accessibility/AccessibilityNodeProvider.java
new file mode 100644
index 00000000..73733a04
--- /dev/null
+++ b/android/view/accessibility/AccessibilityNodeProvider.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 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.accessibility;
+
+import android.accessibilityservice.AccessibilityService;
+import android.os.Bundle;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * This class is the contract a client should implement to enable support of a
+ * virtual view hierarchy rooted at a given view for accessibility purposes. A virtual
+ * view hierarchy is a tree of imaginary Views that is reported as a part of the view
+ * hierarchy when an {@link AccessibilityService} explores the window content.
+ * Since the virtual View tree does not exist this class is responsible for
+ * managing the {@link AccessibilityNodeInfo}s describing that tree to accessibility
+ * services.
+ * </p>
+ * <p>
+ * The main use case of these APIs is to enable a custom view that draws complex content,
+ * for example a monthly calendar grid, to be presented as a tree of logical nodes,
+ * for example month days each containing events, thus conveying its logical structure.
+ * <p>
+ * <p>
+ * A typical use case is to override {@link View#getAccessibilityNodeProvider()} of the
+ * View that is a root of a virtual View hierarchy to return an instance of this class.
+ * In such a case this instance is responsible for managing {@link AccessibilityNodeInfo}s
+ * describing the virtual sub-tree rooted at the View including the one representing the
+ * View itself. Similarly the returned instance is responsible for performing accessibility
+ * actions on any virtual view or the root view itself. For example:
+ * </p>
+ * <pre>
+ * getAccessibilityNodeProvider(
+ * if (mAccessibilityNodeProvider == null) {
+ * mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
+ * public boolean performAction(int action, int virtualDescendantId) {
+ * // Implementation.
+ * return false;
+ * }
+ *
+ * public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text,
+ * int virtualDescendantId) {
+ * // Implementation.
+ * return null;
+ * }
+ *
+ * public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
+ * // Implementation.
+ * return null;
+ * }
+ * });
+ * return mAccessibilityNodeProvider;
+ * </pre>
+ */
+public abstract class AccessibilityNodeProvider {
+
+ /**
+ * The virtual id for the hosting View.
+ */
+ public static final int HOST_VIEW_ID = -1;
+
+ /**
+ * Returns an {@link AccessibilityNodeInfo} representing a virtual view,
+ * such as a descendant of the host View, with the given <code>virtualViewId</code>
+ * or the host View itself if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * The implementer is responsible for obtaining an accessibility node info from the
+ * pool of reusable instances and setting the desired properties of the node info
+ * before returning it.
+ * </p>
+ *
+ * @param virtualViewId A client defined virtual view id.
+ * @return A populated {@link AccessibilityNodeInfo} for a virtual descendant or the
+ * host View.
+ *
+ * @see View#createAccessibilityNodeInfo()
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ return null;
+ }
+
+ /**
+ * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
+ * additional data.
+ * <p>
+ * This method only needs to be implemented if a virtual view offers to provide additional
+ * data.
+ * </p>
+ *
+ * @param virtualViewId The virtual view id used to create the node
+ * @param info The info to which to add the extra data
+ * @param extraDataKey A key specifying the type of extra data to add to the info. The
+ * extra data should be added to the {@link Bundle} returned by
+ * the info's {@link AccessibilityNodeInfo#getExtras} method.
+ * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+ *
+ * @see AccessibilityNodeInfo#setExtraAvailableData
+ */
+ public void addExtraDataToAccessibilityNodeInfo(
+ int virtualViewId, AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+ }
+
+ /**
+ * Performs an accessibility action on a virtual view, such as a descendant of the
+ * host View, with the given <code>virtualViewId</code> or the host View itself
+ * if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
+ *
+ * @param virtualViewId A client defined virtual view id.
+ * @param action The action to perform.
+ * @param arguments Optional action arguments.
+ * @return True if the action was performed.
+ *
+ * @see View#performAccessibilityAction(int, Bundle)
+ * @see #createAccessibilityNodeInfo(int)
+ * @see AccessibilityNodeInfo
+ */
+ public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ return false;
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo}s by text. The match is case insensitive
+ * containment. The search is relative to the virtual view, i.e. a descendant of the
+ * host View, with the given <code>virtualViewId</code> or the host View itself
+ * <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
+ *
+ * @param virtualViewId A client defined virtual view id which defined
+ * the root of the tree in which to perform the search.
+ * @param text The searched text.
+ * @return A list of node info.
+ *
+ * @see #createAccessibilityNodeInfo(int)
+ * @see AccessibilityNodeInfo
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text,
+ int virtualViewId) {
+ return null;
+ }
+
+ /**
+ * Find the virtual view, such as a descendant of the host View, that has the
+ * specified focus type.
+ *
+ * @param focus The focus to find. One of
+ * {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ * @see AccessibilityNodeInfo#FOCUS_INPUT
+ * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ return null;
+ }
+}
diff --git a/android/view/accessibility/AccessibilityRecord.java b/android/view/accessibility/AccessibilityRecord.java
new file mode 100644
index 00000000..fa505c97
--- /dev/null
+++ b/android/view/accessibility/AccessibilityRecord.java
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2011 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.accessibility;
+
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a record in an {@link AccessibilityEvent} and contains information
+ * about state change of its source {@link android.view.View}. When a view fires
+ * an accessibility event it requests from its parent to dispatch the
+ * constructed event. The parent may optionally append a record for itself
+ * for providing more context to
+ * {@link android.accessibilityservice.AccessibilityService}s. Hence,
+ * accessibility services can facilitate additional accessibility records
+ * to enhance feedback.
+ * </p>
+ * <p>
+ * Once the accessibility event containing a record is dispatched the record is
+ * made immutable and calling a state mutation method generates an error.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Not all properties are applicable to all accessibility
+ * event types. For detailed information please refer to {@link AccessibilityEvent}.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating and processing AccessibilityRecords, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ * @see android.accessibilityservice.AccessibilityService
+ * @see AccessibilityNodeInfo
+ */
+public class AccessibilityRecord {
+
+ private static final int UNDEFINED = -1;
+
+ private static final int PROPERTY_CHECKED = 0x00000001;
+ private static final int PROPERTY_ENABLED = 0x00000002;
+ private static final int PROPERTY_PASSWORD = 0x00000004;
+ private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+ private static final int PROPERTY_SCROLLABLE = 0x00000100;
+ private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
+
+ private static final int GET_SOURCE_PREFETCH_FLAGS =
+ AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
+ | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
+
+ // Housekeeping
+ private static final int MAX_POOL_SIZE = 10;
+ private static final Object sPoolLock = new Object();
+ private static AccessibilityRecord sPool;
+ private static int sPoolSize;
+ private AccessibilityRecord mNext;
+ private boolean mIsInPool;
+
+ boolean mSealed;
+ int mBooleanProperties = 0;
+ int mCurrentItemIndex = UNDEFINED;
+ int mItemCount = UNDEFINED;
+ int mFromIndex = UNDEFINED;
+ int mToIndex = UNDEFINED;
+ int mScrollX = UNDEFINED;
+ int mScrollY = UNDEFINED;
+
+ int mScrollDeltaX = UNDEFINED;
+ int mScrollDeltaY = UNDEFINED;
+ int mMaxScrollX = UNDEFINED;
+ int mMaxScrollY = UNDEFINED;
+
+ int mAddedCount= UNDEFINED;
+ int mRemovedCount = UNDEFINED;
+ long mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
+ int mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+
+ CharSequence mClassName;
+ CharSequence mContentDescription;
+ CharSequence mBeforeText;
+ Parcelable mParcelableData;
+
+ final List<CharSequence> mText = new ArrayList<CharSequence>();
+
+ int mConnectionId = UNDEFINED;
+
+ /*
+ * Hide constructor.
+ */
+ AccessibilityRecord() {
+ }
+
+ /**
+ * Sets the event source.
+ *
+ * @param source The source.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setSource(View source) {
+ setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Sets the source to be a virtual descendant of the given <code>root</code>.
+ * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
+ * is set as the source.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report them selves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setSource(@Nullable View root, int virtualDescendantId) {
+ enforceNotSealed();
+ boolean important = true;
+ int rootViewId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ if (root != null) {
+ important = root.isImportantForAccessibility();
+ rootViewId = root.getAccessibilityViewId();
+ mSourceWindowId = root.getAccessibilityWindowId();
+ }
+ setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
+ mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
+ }
+
+ /**
+ * Set the source node ID directly
+ *
+ * @param sourceNodeId The source node Id
+ * @hide
+ */
+ public void setSourceNodeId(long sourceNodeId) {
+ mSourceNodeId = sourceNodeId;
+ }
+
+ /**
+ * Gets the {@link AccessibilityNodeInfo} of the event source.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the received info
+ * by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ * @return The info of the source.
+ */
+ public AccessibilityNodeInfo getSource() {
+ enforceSealed();
+ if ((mConnectionId == UNDEFINED)
+ || (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+ || (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId)
+ == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
+ mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null);
+ }
+
+ /**
+ * Sets the window id.
+ *
+ * @param windowId The window id.
+ *
+ * @hide
+ */
+ public void setWindowId(int windowId) {
+ mSourceWindowId = windowId;
+ }
+
+ /**
+ * Gets the id of the window from which the event comes from.
+ *
+ * @return The window id.
+ */
+ public int getWindowId() {
+ return mSourceWindowId;
+ }
+
+ /**
+ * Gets if the source is checked.
+ *
+ * @return True if the view is checked, false otherwise.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets if the source is checked.
+ *
+ * @param isChecked True if the view is checked, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setChecked(boolean isChecked) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_CHECKED, isChecked);
+ }
+
+ /**
+ * Gets if the source is enabled.
+ *
+ * @return True if the view is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets if the source is enabled.
+ *
+ * @param isEnabled True if the view is enabled, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEnabled(boolean isEnabled) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_ENABLED, isEnabled);
+ }
+
+ /**
+ * Gets if the source is a password field.
+ *
+ * @return True if the view is a password field, false otherwise.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets if the source is a password field.
+ *
+ * @param isPassword True if the view is a password field, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setPassword(boolean isPassword) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_PASSWORD, isPassword);
+ }
+
+ /**
+ * Gets if the source is taking the entire screen.
+ *
+ * @return True if the source is full screen, false otherwise.
+ */
+ public boolean isFullScreen() {
+ return getBooleanProperty(PROPERTY_FULL_SCREEN);
+ }
+
+ /**
+ * Sets if the source is taking the entire screen.
+ *
+ * @param isFullScreen True if the source is full screen, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFullScreen(boolean isFullScreen) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+ }
+
+ /**
+ * Gets if the source is scrollable.
+ *
+ * @return True if the source is scrollable, false otherwise.
+ */
+ public boolean isScrollable() {
+ return getBooleanProperty(PROPERTY_SCROLLABLE);
+ }
+
+ /**
+ * Sets if the source is scrollable.
+ *
+ * @param scrollable True if the source is scrollable, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setScrollable(boolean scrollable) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
+ }
+
+ /**
+ * Gets if the source is important for accessibility.
+ *
+ * <strong>Note:</strong> Used only internally to determine whether
+ * to deliver the event to a given accessibility service since some
+ * services may want to regard all views for accessibility while others
+ * may want to regard only the important views for accessibility.
+ *
+ * @return True if the source is important for accessibility,
+ * false otherwise.
+ *
+ * @hide
+ */
+ public boolean isImportantForAccessibility() {
+ return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY);
+ }
+
+ /**
+ * Sets if the source is important for accessibility.
+ *
+ * @param importantForAccessibility True if the source is important for accessibility,
+ * false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @hide
+ */
+ public void setImportantForAccessibility(boolean importantForAccessibility) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, importantForAccessibility);
+ }
+
+ /**
+ * Gets the number of items that can be visited.
+ *
+ * @return The number of items.
+ */
+ public int getItemCount() {
+ return mItemCount;
+ }
+
+ /**
+ * Sets the number of items that can be visited.
+ *
+ * @param itemCount The number of items.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setItemCount(int itemCount) {
+ enforceNotSealed();
+ mItemCount = itemCount;
+ }
+
+ /**
+ * Gets the index of the source in the list of items the can be visited.
+ *
+ * @return The current item index.
+ */
+ public int getCurrentItemIndex() {
+ return mCurrentItemIndex;
+ }
+
+ /**
+ * Sets the index of the source in the list of items that can be visited.
+ *
+ * @param currentItemIndex The current item index.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setCurrentItemIndex(int currentItemIndex) {
+ enforceNotSealed();
+ mCurrentItemIndex = currentItemIndex;
+ }
+
+ /**
+ * Gets the index of the first character of the changed sequence,
+ * or the beginning of a text selection or the index of the first
+ * visible item when scrolling.
+ *
+ * @return The index of the first character or selection
+ * start or the first visible item.
+ */
+ public int getFromIndex() {
+ return mFromIndex;
+ }
+
+ /**
+ * Sets the index of the first character of the changed sequence
+ * or the beginning of a text selection or the index of the first
+ * visible item when scrolling.
+ *
+ * @param fromIndex The index of the first character or selection
+ * start or the first visible item.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setFromIndex(int fromIndex) {
+ enforceNotSealed();
+ mFromIndex = fromIndex;
+ }
+
+ /**
+ * Gets the index of text selection end or the index of the last
+ * visible item when scrolling.
+ *
+ * @return The index of selection end or last item index.
+ */
+ public int getToIndex() {
+ return mToIndex;
+ }
+
+ /**
+ * Sets the index of text selection end or the index of the last
+ * visible item when scrolling.
+ *
+ * @param toIndex The index of selection end or last item index.
+ */
+ public void setToIndex(int toIndex) {
+ enforceNotSealed();
+ mToIndex = toIndex;
+ }
+
+ /**
+ * Gets the scroll offset of the source left edge in pixels.
+ *
+ * @return The scroll.
+ */
+ public int getScrollX() {
+ return mScrollX;
+ }
+
+ /**
+ * Sets the scroll offset of the source left edge in pixels.
+ *
+ * @param scrollX The scroll.
+ */
+ public void setScrollX(int scrollX) {
+ enforceNotSealed();
+ mScrollX = scrollX;
+ }
+
+ /**
+ * Gets the scroll offset of the source top edge in pixels.
+ *
+ * @return The scroll.
+ */
+ public int getScrollY() {
+ return mScrollY;
+ }
+
+ /**
+ * Sets the scroll offset of the source top edge in pixels.
+ *
+ * @param scrollY The scroll.
+ */
+ public void setScrollY(int scrollY) {
+ enforceNotSealed();
+ mScrollY = scrollY;
+ }
+
+ /**
+ * Gets the difference in pixels between the horizontal position before the scroll and the
+ * current horizontal position
+ *
+ * @return the scroll delta x
+ */
+ public int getScrollDeltaX() {
+ return mScrollDeltaX;
+ }
+
+ /**
+ * Sets the difference in pixels between the horizontal position before the scroll and the
+ * current horizontal position
+ *
+ * @param scrollDeltaX the scroll delta x
+ */
+ public void setScrollDeltaX(int scrollDeltaX) {
+ enforceNotSealed();
+ mScrollDeltaX = scrollDeltaX;
+ }
+
+ /**
+ * Gets the difference in pixels between the vertical position before the scroll and the
+ * current vertical position
+ *
+ * @return the scroll delta y
+ */
+ public int getScrollDeltaY() {
+ return mScrollDeltaY;
+ }
+
+ /**
+ * Sets the difference in pixels between the vertical position before the scroll and the
+ * current vertical position
+ *
+ * @param scrollDeltaY the scroll delta y
+ */
+ public void setScrollDeltaY(int scrollDeltaY) {
+ enforceNotSealed();
+ mScrollDeltaY = scrollDeltaY;
+ }
+
+ /**
+ * Gets the max scroll offset of the source left edge in pixels.
+ *
+ * @return The max scroll.
+ */
+ public int getMaxScrollX() {
+ return mMaxScrollX;
+ }
+
+ /**
+ * Sets the max scroll offset of the source left edge in pixels.
+ *
+ * @param maxScrollX The max scroll.
+ */
+ public void setMaxScrollX(int maxScrollX) {
+ enforceNotSealed();
+ mMaxScrollX = maxScrollX;
+ }
+
+ /**
+ * Gets the max scroll offset of the source top edge in pixels.
+ *
+ * @return The max scroll.
+ */
+ public int getMaxScrollY() {
+ return mMaxScrollY;
+ }
+
+ /**
+ * Sets the max scroll offset of the source top edge in pixels.
+ *
+ * @param maxScrollY The max scroll.
+ */
+ public void setMaxScrollY(int maxScrollY) {
+ enforceNotSealed();
+ mMaxScrollY = maxScrollY;
+ }
+
+ /**
+ * Gets the number of added characters.
+ *
+ * @return The number of added characters.
+ */
+ public int getAddedCount() {
+ return mAddedCount;
+ }
+
+ /**
+ * Sets the number of added characters.
+ *
+ * @param addedCount The number of added characters.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAddedCount(int addedCount) {
+ enforceNotSealed();
+ mAddedCount = addedCount;
+ }
+
+ /**
+ * Gets the number of removed characters.
+ *
+ * @return The number of removed characters.
+ */
+ public int getRemovedCount() {
+ return mRemovedCount;
+ }
+
+ /**
+ * Sets the number of removed characters.
+ *
+ * @param removedCount The number of removed characters.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setRemovedCount(int removedCount) {
+ enforceNotSealed();
+ mRemovedCount = removedCount;
+ }
+
+ /**
+ * Gets the class name of the source.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class name of the source.
+ *
+ * @param className The lass name.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setClassName(CharSequence className) {
+ enforceNotSealed();
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of the event. The index in the list represents the priority
+ * of the text. Specifically, the lower the index the higher the priority.
+ *
+ * @return The text.
+ */
+ public List<CharSequence> getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @return The text before the change.
+ */
+ public CharSequence getBeforeText() {
+ return mBeforeText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @param beforeText The text before the change.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setBeforeText(CharSequence beforeText) {
+ enforceNotSealed();
+ mBeforeText = (beforeText == null) ? null
+ : beforeText.subSequence(0, beforeText.length());
+ }
+
+ /**
+ * Gets the description of the source.
+ *
+ * @return The description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the description of the source.
+ *
+ * @param contentDescription The description.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ enforceNotSealed();
+ mContentDescription = (contentDescription == null) ? null
+ : contentDescription.subSequence(0, contentDescription.length());
+ }
+
+ /**
+ * Gets the {@link Parcelable} data.
+ *
+ * @return The parcelable data.
+ */
+ public Parcelable getParcelableData() {
+ return mParcelableData;
+ }
+
+ /**
+ * Sets the {@link Parcelable} data of the event.
+ *
+ * @param parcelableData The parcelable data.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setParcelableData(Parcelable parcelableData) {
+ enforceNotSealed();
+ mParcelableData = parcelableData;
+ }
+
+ /**
+ * Gets the id of the source node.
+ *
+ * @return The id.
+ *
+ * @hide
+ */
+ public long getSourceNodeId() {
+ return mSourceNodeId;
+ }
+
+ /**
+ * Sets the unique id of the IAccessibilityServiceConnection over which
+ * this instance can send requests to the system.
+ *
+ * @param connectionId The connection id.
+ *
+ * @hide
+ */
+ public void setConnectionId(int connectionId) {
+ enforceNotSealed();
+ mConnectionId = connectionId;
+ }
+
+ /**
+ * Sets if this instance is sealed.
+ *
+ * @param sealed Whether is sealed.
+ *
+ * @hide
+ */
+ public void setSealed(boolean sealed) {
+ mSealed = sealed;
+ }
+
+ /**
+ * Gets if this instance is sealed.
+ *
+ * @return Whether is sealed.
+ */
+ boolean isSealed() {
+ return mSealed;
+ }
+
+ /**
+ * Enforces that this instance is sealed.
+ *
+ * @throws IllegalStateException If this instance is not sealed.
+ */
+ void enforceSealed() {
+ if (!isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a not sealed instance.");
+ }
+ }
+
+ /**
+ * Enforces that this instance is not sealed.
+ *
+ * @throws IllegalStateException If this instance is sealed.
+ */
+ void enforceNotSealed() {
+ if (isSealed()) {
+ throw new IllegalStateException("Cannot perform this "
+ + "action on a sealed instance.");
+ }
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) == property;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated. The instance is initialized with data from the
+ * given record.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityRecord obtain(AccessibilityRecord record) {
+ AccessibilityRecord clone = AccessibilityRecord.obtain();
+ clone.init(record);
+ return clone;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityRecord obtain() {
+ synchronized (sPoolLock) {
+ if (sPool != null) {
+ AccessibilityRecord record = sPool;
+ sPool = sPool.mNext;
+ sPoolSize--;
+ record.mNext = null;
+ record.mIsInPool = false;
+ return record;
+ }
+ return new AccessibilityRecord();
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <strong>Note:</strong> You must not touch the object after calling this function.
+ *
+ * @throws IllegalStateException If the record is already recycled.
+ */
+ public void recycle() {
+ if (mIsInPool) {
+ throw new IllegalStateException("Record already recycled!");
+ }
+ clear();
+ synchronized (sPoolLock) {
+ if (sPoolSize <= MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ mIsInPool = true;
+ sPoolSize++;
+ }
+ }
+ }
+
+ /**
+ * Initialize this record from another one.
+ *
+ * @param record The to initialize from.
+ */
+ void init(AccessibilityRecord record) {
+ mSealed = record.mSealed;
+ mBooleanProperties = record.mBooleanProperties;
+ mCurrentItemIndex = record.mCurrentItemIndex;
+ mItemCount = record.mItemCount;
+ mFromIndex = record.mFromIndex;
+ mToIndex = record.mToIndex;
+ mScrollX = record.mScrollX;
+ mScrollY = record.mScrollY;
+ mMaxScrollX = record.mMaxScrollX;
+ mMaxScrollY = record.mMaxScrollY;
+ mAddedCount = record.mAddedCount;
+ mRemovedCount = record.mRemovedCount;
+ mClassName = record.mClassName;
+ mContentDescription = record.mContentDescription;
+ mBeforeText = record.mBeforeText;
+ mParcelableData = record.mParcelableData;
+ mText.addAll(record.mText);
+ mSourceWindowId = record.mSourceWindowId;
+ mSourceNodeId = record.mSourceNodeId;
+ mConnectionId = record.mConnectionId;
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ void clear() {
+ mSealed = false;
+ mBooleanProperties = 0;
+ mCurrentItemIndex = UNDEFINED;
+ mItemCount = UNDEFINED;
+ mFromIndex = UNDEFINED;
+ mToIndex = UNDEFINED;
+ mScrollX = UNDEFINED;
+ mScrollY = UNDEFINED;
+ mMaxScrollX = UNDEFINED;
+ mMaxScrollY = UNDEFINED;
+ mAddedCount = UNDEFINED;
+ mRemovedCount = UNDEFINED;
+ mClassName = null;
+ mContentDescription = null;
+ mBeforeText = null;
+ mParcelableData = null;
+ mText.clear();
+ mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ mConnectionId = UNDEFINED;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(" [ ClassName: " + mClassName);
+ builder.append("; Text: " + mText);
+ builder.append("; ContentDescription: " + mContentDescription);
+ builder.append("; ItemCount: " + mItemCount);
+ builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
+ builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
+ builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
+ builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
+ builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
+ builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE));
+ builder.append("; BeforeText: " + mBeforeText);
+ builder.append("; FromIndex: " + mFromIndex);
+ builder.append("; ToIndex: " + mToIndex);
+ builder.append("; ScrollX: " + mScrollX);
+ builder.append("; ScrollY: " + mScrollY);
+ builder.append("; MaxScrollX: " + mMaxScrollX);
+ builder.append("; MaxScrollY: " + mMaxScrollY);
+ builder.append("; AddedCount: " + mAddedCount);
+ builder.append("; RemovedCount: " + mRemovedCount);
+ builder.append("; ParcelableData: " + mParcelableData);
+ builder.append(" ]");
+ return builder.toString();
+ }
+}
diff --git a/android/view/accessibility/AccessibilityRequestPreparer.java b/android/view/accessibility/AccessibilityRequestPreparer.java
new file mode 100644
index 00000000..889feb98
--- /dev/null
+++ b/android/view/accessibility/AccessibilityRequestPreparer.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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.accessibility;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+/**
+ * Object responsible to ensuring that a {@link View} is prepared to meet a synchronous request for
+ * accessibility data.
+ * <p>
+ * Because accessibility requests arrive to {@link View}s synchronously on the UI thread, a View
+ * that requires information from other processes can struggle to meet those requests. Registering
+ * an instance of this class with {@link AccessibilityManager} allows a View to be notified when
+ * a request is about to be made, and to asynchronously inform the accessibility system when it is
+ * ready to meet the request.
+ * <p>
+ * <strong>Note:</strong> This class should only be needed in exceptional situations where a
+ * {@link View} cannot otherwise synchronously meet the request for accessibility data.
+ */
+public abstract class AccessibilityRequestPreparer {
+ public static final int REQUEST_TYPE_EXTRA_DATA = 0x00000001;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ REQUEST_TYPE_EXTRA_DATA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestTypes {}
+
+ private final WeakReference<View> mViewRef;
+ private final int mRequestTypes;
+
+ /**
+ * @param view The view whose requests need preparation. It must be attached to a
+ * window. This object will retain a weak reference to this view, and will unregister itself
+ * from AccessibilityManager if the view is detached from a window. It will not re-register
+ * itself.
+ * @param requestTypes The types of requests that require preparation. Different types may
+ * be ORed together.
+ *
+ * @throws IllegalStateException if the view is not attached to a window.
+ */
+ public AccessibilityRequestPreparer(View view, @RequestTypes int requestTypes) {
+ if (!view.isAttachedToWindow()) {
+ throw new IllegalStateException("View must be attached to a window");
+ }
+ mViewRef = new WeakReference<>(view);
+ mRequestTypes = requestTypes;
+ view.addOnAttachStateChangeListener(new ViewAttachStateListener());
+ }
+
+ /**
+ * Callback to allow preparation for filling extra data. Only called back if
+ * REQUEST_TYPE_EXTRA_DATA is requested.
+ *
+ * @param virtualViewId The ID of a virtual child node, if the {@link View} for this preparer
+ * supports virtual descendents, or {@link AccessibilityNodeProvider#HOST_VIEW_ID}
+ * if the request is for the view itself.
+ * @param extraDataKey The extra data key for the request
+ * @param args The arguments for the request
+ * @param preparationFinishedMessage A message that must be sent to its target when preparations
+ * are complete.
+ *
+ * @see View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)
+ * @see AccessibilityDelegate#addExtraDataToAccessibilityNodeInfo(View, AccessibilityNodeInfo,
+ * String, Bundle)
+ * @see AccessibilityNodeProvider#addExtraDataToAccessibilityNodeInfo(
+ * int, AccessibilityNodeInfo, String, Bundle)
+ */
+ public abstract void onPrepareExtraData(int virtualViewId, String extraDataKey,
+ Bundle args, Message preparationFinishedMessage);
+
+ /**
+ * Get the view this object was created with.
+ *
+ * @return The view this object was created with, or {@code null} if the weak reference held
+ * to the view is no longer valid.
+ */
+ public @Nullable View getView() {
+ return mViewRef.get();
+ }
+
+ private class ViewAttachStateListener implements View.OnAttachStateChangeListener {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ Context context = v.getContext();
+ if (context != null) {
+ context.getSystemService(AccessibilityManager.class)
+ .removeAccessibilityRequestPreparer(AccessibilityRequestPreparer.this);
+ }
+ v.removeOnAttachStateChangeListener(this);
+ }
+ }
+}
diff --git a/android/view/accessibility/AccessibilityWindowInfo.java b/android/view/accessibility/AccessibilityWindowInfo.java
new file mode 100644
index 00000000..f11767de
--- /dev/null
+++ b/android/view/accessibility/AccessibilityWindowInfo.java
@@ -0,0 +1,742 @@
+/*
+ * Copyright (C) 2014 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.accessibility;
+
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.LongArray;
+import android.util.Pools.SynchronizedPool;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class represents a state snapshot of a window for accessibility
+ * purposes. The screen content contains one or more windows where some
+ * windows can be descendants of other windows, which is the windows are
+ * hierarchically ordered. Note that there is no root window. Hence, the
+ * screen content can be seen as a collection of window trees.
+ */
+public final class AccessibilityWindowInfo implements Parcelable {
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Window type: This is an application window. Such a window shows UI for
+ * interacting with an application.
+ */
+ public static final int TYPE_APPLICATION = 1;
+
+ /**
+ * Window type: This is an input method window. Such a window shows UI for
+ * inputting text such as keyboard, suggestions, etc.
+ */
+ public static final int TYPE_INPUT_METHOD = 2;
+
+ /**
+ * Window type: This is an system window. Such a window shows UI for
+ * interacting with the system.
+ */
+ public static final int TYPE_SYSTEM = 3;
+
+ /**
+ * Window type: Windows that are overlaid <em>only</em> by an {@link
+ * android.accessibilityservice.AccessibilityService} for interception of
+ * user interactions without changing the windows an accessibility service
+ * can introspect. In particular, an accessibility service can introspect
+ * only windows that a sighted user can interact with which they can touch
+ * these windows or can type into these windows. For example, if there
+ * is a full screen accessibility overlay that is touchable, the windows
+ * below it will be introspectable by an accessibility service regardless
+ * they are covered by a touchable window.
+ */
+ public static final int TYPE_ACCESSIBILITY_OVERLAY = 4;
+
+ /**
+ * Window type: A system window used to divide the screen in split-screen mode.
+ * This type of window is present only in split-screen mode.
+ */
+ public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5;
+
+ /* Special values for window IDs */
+ /** @hide */
+ public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE;
+ /** @hide */
+ public static final int UNDEFINED_WINDOW_ID = -1;
+ /** @hide */
+ public static final int ANY_WINDOW_ID = -2;
+ /** @hide */
+ public static final int PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID = -3;
+
+ private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
+ private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
+ private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
+
+ // Housekeeping.
+ private static final int MAX_POOL_SIZE = 10;
+ private static final SynchronizedPool<AccessibilityWindowInfo> sPool =
+ new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE);
+ private static AtomicInteger sNumInstancesInUse;
+
+ // Data.
+ private int mType = UNDEFINED_WINDOW_ID;
+ private int mLayer = UNDEFINED_WINDOW_ID;
+ private int mBooleanProperties;
+ private int mId = UNDEFINED_WINDOW_ID;
+ private int mParentId = UNDEFINED_WINDOW_ID;
+ private final Rect mBoundsInScreen = new Rect();
+ private LongArray mChildIds;
+ private CharSequence mTitle;
+ private int mAnchorId = UNDEFINED_WINDOW_ID;
+ private boolean mInPictureInPicture;
+
+ private int mConnectionId = UNDEFINED_WINDOW_ID;
+
+ private AccessibilityWindowInfo() {
+ /* do nothing - hide constructor */
+ }
+
+ /**
+ * Gets the title of the window.
+ *
+ * @return The title of the window, or {@code null} if none is available.
+ */
+ @Nullable
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Sets the title of the window.
+ *
+ * @param title The title.
+ *
+ * @hide
+ */
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ }
+
+ /**
+ * Gets the type of the window.
+ *
+ * @return The type.
+ *
+ * @see #TYPE_APPLICATION
+ * @see #TYPE_INPUT_METHOD
+ * @see #TYPE_SYSTEM
+ * @see #TYPE_ACCESSIBILITY_OVERLAY
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Sets the type of the window.
+ *
+ * @param type The type
+ *
+ * @hide
+ */
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * Gets the layer which determines the Z-order of the window. Windows
+ * with greater layer appear on top of windows with lesser layer.
+ *
+ * @return The window layer.
+ */
+ public int getLayer() {
+ return mLayer;
+ }
+
+ /**
+ * Sets the layer which determines the Z-order of the window. Windows
+ * with greater layer appear on top of windows with lesser layer.
+ *
+ * @param layer The window layer.
+ *
+ * @hide
+ */
+ public void setLayer(int layer) {
+ mLayer = layer;
+ }
+
+ /**
+ * Gets the root node in the window's hierarchy.
+ *
+ * @return The root node.
+ */
+ public AccessibilityNodeInfo getRoot() {
+ if (mConnectionId == UNDEFINED_WINDOW_ID) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mId, AccessibilityNodeInfo.ROOT_NODE_ID,
+ true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
+ }
+
+ /**
+ * Sets the anchor node's ID.
+ *
+ * @param anchorId The anchor's accessibility id in its window.
+ *
+ * @hide
+ */
+ public void setAnchorId(int anchorId) {
+ mAnchorId = anchorId;
+ }
+
+ /**
+ * Gets the node that anchors this window to another.
+ *
+ * @return The anchor node, or {@code null} if none exists.
+ */
+ public AccessibilityNodeInfo getAnchor() {
+ if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID)
+ || (mParentId == UNDEFINED_WINDOW_ID)) {
+ return null;
+ }
+
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mParentId, mAnchorId, true, 0, null);
+ }
+
+ /** @hide */
+ public void setPictureInPicture(boolean pictureInPicture) {
+ mInPictureInPicture = pictureInPicture;
+ }
+
+ /**
+ * Check if the window is in picture-in-picture mode.
+ *
+ * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
+ * @removed
+ */
+ public boolean inPictureInPicture() {
+ return isInPictureInPictureMode();
+ }
+
+ /**
+ * Check if the window is in picture-in-picture mode.
+ *
+ * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
+ */
+ public boolean isInPictureInPictureMode() {
+ return mInPictureInPicture;
+ }
+
+ /**
+ * Gets the parent window.
+ *
+ * @return The parent window, or {@code null} if none exists.
+ */
+ public AccessibilityWindowInfo getParent() {
+ if (mConnectionId == UNDEFINED_WINDOW_ID || mParentId == UNDEFINED_WINDOW_ID) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.getWindow(mConnectionId, mParentId);
+ }
+
+ /**
+ * Sets the parent window id.
+ *
+ * @param parentId The parent id.
+ *
+ * @hide
+ */
+ public void setParentId(int parentId) {
+ mParentId = parentId;
+ }
+
+ /**
+ * Gets the unique window id.
+ *
+ * @return windowId The window id.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Sets the unique window id.
+ *
+ * @param id The window id.
+ *
+ * @hide
+ */
+ public void setId(int id) {
+ mId = id;
+ }
+
+ /**
+ * Sets the unique id of the IAccessibilityServiceConnection over which
+ * this instance can send requests to the system.
+ *
+ * @param connectionId The connection id.
+ *
+ * @hide
+ */
+ public void setConnectionId(int connectionId) {
+ mConnectionId = connectionId;
+ }
+
+ /**
+ * Gets the bounds of this window in the screen.
+ *
+ * @param outBounds The out window bounds.
+ */
+ public void getBoundsInScreen(Rect outBounds) {
+ outBounds.set(mBoundsInScreen);
+ }
+
+ /**
+ * Sets the bounds of this window in the screen.
+ *
+ * @param bounds The out window bounds.
+ *
+ * @hide
+ */
+ public void setBoundsInScreen(Rect bounds) {
+ mBoundsInScreen.set(bounds);
+ }
+
+ /**
+ * Gets if this window is active. An active window is the one
+ * the user is currently touching or the window has input focus
+ * and the user is not touching any window.
+ *
+ * @return Whether this is the active window.
+ */
+ public boolean isActive() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE);
+ }
+
+ /**
+ * Sets if this window is active, which is this is the window
+ * the user is currently touching or the window has input focus
+ * and the user is not touching any window.
+ *
+ * @param active Whether this is the active window.
+ *
+ * @hide
+ */
+ public void setActive(boolean active) {
+ setBooleanProperty(BOOLEAN_PROPERTY_ACTIVE, active);
+ }
+
+ /**
+ * Gets if this window has input focus.
+ *
+ * @return Whether has input focus.
+ */
+ public boolean isFocused() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
+ }
+
+ /**
+ * Sets if this window has input focus.
+ *
+ * @param focused Whether has input focus.
+ *
+ * @hide
+ */
+ public void setFocused(boolean focused) {
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets if this window has accessibility focus.
+ *
+ * @return Whether has accessibility focus.
+ */
+ public boolean isAccessibilityFocused() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
+ }
+
+ /**
+ * Sets if this window has accessibility focus.
+ *
+ * @param focused Whether has accessibility focus.
+ *
+ * @hide
+ */
+ public void setAccessibilityFocused(boolean focused) {
+ setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets the number of child windows.
+ *
+ * @return The child count.
+ */
+ public int getChildCount() {
+ return (mChildIds != null) ? mChildIds.size() : 0;
+ }
+
+ /**
+ * Gets the child window at a given index.
+ *
+ * @param index The index.
+ * @return The child.
+ */
+ public AccessibilityWindowInfo getChild(int index) {
+ if (mChildIds == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (mConnectionId == UNDEFINED_WINDOW_ID) {
+ return null;
+ }
+ final int childId = (int) mChildIds.get(index);
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.getWindow(mConnectionId, childId);
+ }
+
+ /**
+ * Adds a child window.
+ *
+ * @param childId The child window id.
+ *
+ * @hide
+ */
+ public void addChild(int childId) {
+ if (mChildIds == null) {
+ mChildIds = new LongArray();
+ }
+ mChildIds.add(childId);
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * created.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityWindowInfo obtain() {
+ AccessibilityWindowInfo info = sPool.acquire();
+ if (info == null) {
+ info = new AccessibilityWindowInfo();
+ }
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.incrementAndGet();
+ }
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * created. The returned instance is initialized from the given
+ * <code>info</code>.
+ *
+ * @param info The other info.
+ * @return An instance.
+ */
+ public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) {
+ AccessibilityWindowInfo infoClone = obtain();
+
+ infoClone.mType = info.mType;
+ infoClone.mLayer = info.mLayer;
+ infoClone.mBooleanProperties = info.mBooleanProperties;
+ infoClone.mId = info.mId;
+ infoClone.mParentId = info.mParentId;
+ infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
+ infoClone.mTitle = info.mTitle;
+ infoClone.mAnchorId = info.mAnchorId;
+ infoClone.mInPictureInPicture = info.mInPictureInPicture;
+
+ if (info.mChildIds != null && info.mChildIds.size() > 0) {
+ if (infoClone.mChildIds == null) {
+ infoClone.mChildIds = info.mChildIds.clone();
+ } else {
+ infoClone.mChildIds.addAll(info.mChildIds);
+ }
+ }
+
+ infoClone.mConnectionId = info.mConnectionId;
+
+ return infoClone;
+ }
+
+ /**
+ * Specify a counter that will be incremented on obtain() and decremented on recycle()
+ *
+ * @hide
+ */
+ @TestApi
+ public static void setNumInstancesInUseCounter(AtomicInteger counter) {
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse = counter;
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <strong>Note:</strong> You must not touch the object after calling this function.
+ * </p>
+ *
+ * @throws IllegalStateException If the info is already recycled.
+ */
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.decrementAndGet();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+ parcel.writeInt(mLayer);
+ parcel.writeInt(mBooleanProperties);
+ parcel.writeInt(mId);
+ parcel.writeInt(mParentId);
+ mBoundsInScreen.writeToParcel(parcel, flags);
+ parcel.writeCharSequence(mTitle);
+ parcel.writeInt(mAnchorId);
+ parcel.writeInt(mInPictureInPicture ? 1 : 0);
+
+ final LongArray childIds = mChildIds;
+ if (childIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int childCount = childIds.size();
+ parcel.writeInt(childCount);
+ for (int i = 0; i < childCount; i++) {
+ parcel.writeInt((int) childIds.get(i));
+ }
+ }
+
+ parcel.writeInt(mConnectionId);
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ mType = parcel.readInt();
+ mLayer = parcel.readInt();
+ mBooleanProperties = parcel.readInt();
+ mId = parcel.readInt();
+ mParentId = parcel.readInt();
+ mBoundsInScreen.readFromParcel(parcel);
+ mTitle = parcel.readCharSequence();
+ mAnchorId = parcel.readInt();
+ mInPictureInPicture = parcel.readInt() == 1;
+
+ final int childCount = parcel.readInt();
+ if (childCount > 0) {
+ if (mChildIds == null) {
+ mChildIds = new LongArray(childCount);
+ }
+ for (int i = 0; i < childCount; i++) {
+ final int childId = parcel.readInt();
+ mChildIds.add(childId);
+ }
+ }
+
+ mConnectionId = parcel.readInt();
+ }
+
+ @Override
+ public int hashCode() {
+ return mId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AccessibilityWindowInfo other = (AccessibilityWindowInfo) obj;
+ return (mId == other.mId);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("AccessibilityWindowInfo[");
+ builder.append("title=").append(mTitle);
+ builder.append("id=").append(mId);
+ builder.append(", type=").append(typeToString(mType));
+ builder.append(", layer=").append(mLayer);
+ builder.append(", bounds=").append(mBoundsInScreen);
+ builder.append(", focused=").append(isFocused());
+ builder.append(", active=").append(isActive());
+ builder.append(", pictureInPicture=").append(inPictureInPicture());
+ if (DEBUG) {
+ builder.append(", parent=").append(mParentId);
+ builder.append(", children=[");
+ if (mChildIds != null) {
+ final int childCount = mChildIds.size();
+ for (int i = 0; i < childCount; i++) {
+ builder.append(mChildIds.get(i));
+ if (i < childCount - 1) {
+ builder.append(',');
+ }
+ }
+ } else {
+ builder.append("null");
+ }
+ builder.append(']');
+ } else {
+ builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
+ builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID);
+ builder.append(", hasChildren=").append(mChildIds != null
+ && mChildIds.size() > 0);
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+
+ /**
+ * Clears the internal state.
+ */
+ private void clear() {
+ mType = UNDEFINED_WINDOW_ID;
+ mLayer = UNDEFINED_WINDOW_ID;
+ mBooleanProperties = 0;
+ mId = UNDEFINED_WINDOW_ID;
+ mParentId = UNDEFINED_WINDOW_ID;
+ mBoundsInScreen.setEmpty();
+ if (mChildIds != null) {
+ mChildIds.clear();
+ }
+ mConnectionId = UNDEFINED_WINDOW_ID;
+ mAnchorId = UNDEFINED_WINDOW_ID;
+ mInPictureInPicture = false;
+ mTitle = null;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) != 0;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ private static String typeToString(int type) {
+ switch (type) {
+ case TYPE_APPLICATION: {
+ return "TYPE_APPLICATION";
+ }
+ case TYPE_INPUT_METHOD: {
+ return "TYPE_INPUT_METHOD";
+ }
+ case TYPE_SYSTEM: {
+ return "TYPE_SYSTEM";
+ }
+ case TYPE_ACCESSIBILITY_OVERLAY: {
+ return "TYPE_ACCESSIBILITY_OVERLAY";
+ }
+ case TYPE_SPLIT_SCREEN_DIVIDER: {
+ return "TYPE_SPLIT_SCREEN_DIVIDER";
+ }
+ default:
+ return "<UNKNOWN>";
+ }
+ }
+
+ /**
+ * Checks whether this window changed. The argument should be
+ * another state of the same window, which is have the same id
+ * and type as they never change.
+ *
+ * @param other The new state.
+ * @return Whether something changed.
+ *
+ * @hide
+ */
+ public boolean changed(AccessibilityWindowInfo other) {
+ if (other.mId != mId) {
+ throw new IllegalArgumentException("Not same window.");
+ }
+ if (other.mType != mType) {
+ throw new IllegalArgumentException("Not same type.");
+ }
+ if (!mBoundsInScreen.equals(other.mBoundsInScreen)) {
+ return true;
+ }
+ if (mLayer != other.mLayer) {
+ return true;
+ }
+ if (mBooleanProperties != other.mBooleanProperties) {
+ return true;
+ }
+ if (mParentId != other.mParentId) {
+ return true;
+ }
+ if (mChildIds == null) {
+ if (other.mChildIds != null) {
+ return true;
+ }
+ } else if (!mChildIds.equals(other.mChildIds)) {
+ return true;
+ }
+ return false;
+ }
+
+ public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
+ new Creator<AccessibilityWindowInfo>() {
+ @Override
+ public AccessibilityWindowInfo createFromParcel(Parcel parcel) {
+ AccessibilityWindowInfo info = obtain();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ @Override
+ public AccessibilityWindowInfo[] newArray(int size) {
+ return new AccessibilityWindowInfo[size];
+ }
+ };
+}
diff --git a/android/view/accessibility/CaptioningManager.java b/android/view/accessibility/CaptioningManager.java
new file mode 100644
index 00000000..d6455e72
--- /dev/null
+++ b/android/view/accessibility/CaptioningManager.java
@@ -0,0 +1,555 @@
+/*
+ * 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.view.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * Contains methods for accessing and monitoring preferred video captioning state and visual
+ * properties.
+ */
+@SystemService(Context.CAPTIONING_SERVICE)
+public class CaptioningManager {
+ /** Default captioning enabled value. */
+ private static final int DEFAULT_ENABLED = 0;
+
+ /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */
+ private static final int DEFAULT_PRESET = 0;
+
+ /** Default scaling value for caption fonts. */
+ private static final float DEFAULT_FONT_SCALE = 1;
+
+ private final ArrayList<CaptioningChangeListener> mListeners = new ArrayList<>();
+ private final ContentResolver mContentResolver;
+ private final ContentObserver mContentObserver;
+
+ /**
+ * Creates a new captioning manager for the specified context.
+ *
+ * @hide
+ */
+ public CaptioningManager(Context context) {
+ mContentResolver = context.getContentResolver();
+
+ final Handler handler = new Handler(context.getMainLooper());
+ mContentObserver = new MyContentObserver(handler);
+ }
+
+ /**
+ * @return the user's preferred captioning enabled state
+ */
+ public final boolean isEnabled() {
+ return Secure.getInt(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1;
+ }
+
+ /**
+ * @return the raw locale string for the user's preferred captioning
+ * language
+ * @hide
+ */
+ @Nullable
+ public final String getRawLocale() {
+ return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+ }
+
+ /**
+ * @return the locale for the user's preferred captioning language, or null
+ * if not specified
+ */
+ @Nullable
+ public final Locale getLocale() {
+ final String rawLocale = getRawLocale();
+ if (!TextUtils.isEmpty(rawLocale)) {
+ final String[] splitLocale = rawLocale.split("_");
+ switch (splitLocale.length) {
+ case 3:
+ return new Locale(splitLocale[0], splitLocale[1], splitLocale[2]);
+ case 2:
+ return new Locale(splitLocale[0], splitLocale[1]);
+ case 1:
+ return new Locale(splitLocale[0]);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return the user's preferred font scaling factor for video captions, or 1 if not
+ * specified
+ */
+ public final float getFontScale() {
+ return Secure.getFloat(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, DEFAULT_FONT_SCALE);
+ }
+
+ /**
+ * @return the raw preset number, or the first preset if not specified
+ * @hide
+ */
+ public int getRawUserStyle() {
+ return Secure.getInt(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET);
+ }
+
+ /**
+ * @return the user's preferred visual properties for captions as a
+ * {@link CaptionStyle}, or the default style if not specified
+ */
+ @NonNull
+ public CaptionStyle getUserStyle() {
+ final int preset = getRawUserStyle();
+ if (preset == CaptionStyle.PRESET_CUSTOM) {
+ return CaptionStyle.getCustomStyle(mContentResolver);
+ }
+
+ return CaptionStyle.PRESETS[preset];
+ }
+
+ /**
+ * Adds a listener for changes in the user's preferred captioning enabled
+ * state and visual properties.
+ *
+ * @param listener the listener to add
+ */
+ public void addCaptioningChangeListener(@NonNull CaptioningChangeListener listener) {
+ synchronized (mListeners) {
+ if (mListeners.isEmpty()) {
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET);
+ }
+
+ mListeners.add(listener);
+ }
+ }
+
+ private void registerObserver(String key) {
+ mContentResolver.registerContentObserver(Secure.getUriFor(key), false, mContentObserver);
+ }
+
+ /**
+ * Removes a listener previously added using
+ * {@link #addCaptioningChangeListener}.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeCaptioningChangeListener(@NonNull CaptioningChangeListener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+
+ if (mListeners.isEmpty()) {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+ }
+ }
+
+ private void notifyEnabledChanged() {
+ final boolean enabled = isEnabled();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onEnabledChanged(enabled);
+ }
+ }
+ }
+
+ private void notifyUserStyleChanged() {
+ final CaptionStyle userStyle = getUserStyle();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onUserStyleChanged(userStyle);
+ }
+ }
+ }
+
+ private void notifyLocaleChanged() {
+ final Locale locale = getLocale();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onLocaleChanged(locale);
+ }
+ }
+ }
+
+ private void notifyFontScaleChanged() {
+ final float fontScale = getFontScale();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onFontScaleChanged(fontScale);
+ }
+ }
+ }
+
+ private class MyContentObserver extends ContentObserver {
+ private final Handler mHandler;
+
+ public MyContentObserver(Handler handler) {
+ super(handler);
+
+ mHandler = handler;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ final String uriPath = uri.getPath();
+ final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
+ if (Secure.ACCESSIBILITY_CAPTIONING_ENABLED.equals(name)) {
+ notifyEnabledChanged();
+ } else if (Secure.ACCESSIBILITY_CAPTIONING_LOCALE.equals(name)) {
+ notifyLocaleChanged();
+ } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) {
+ notifyFontScaleChanged();
+ } else {
+ // We only need a single callback when multiple style properties
+ // change in rapid succession.
+ mHandler.removeCallbacks(mStyleChangedRunnable);
+ mHandler.post(mStyleChangedRunnable);
+ }
+ }
+ };
+
+ /**
+ * Runnable posted when user style properties change. This is used to
+ * prevent unnecessary change notifications when multiple properties change
+ * in rapid succession.
+ */
+ private final Runnable mStyleChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyUserStyleChanged();
+ }
+ };
+
+ /**
+ * Specifies visual properties for video captions, including foreground and
+ * background colors, edge properties, and typeface.
+ */
+ public static final class CaptionStyle {
+ /**
+ * Packed value for a color of 'none' and a cached opacity of 100%.
+ *
+ * @hide
+ */
+ private static final int COLOR_NONE_OPAQUE = 0x000000FF;
+
+ /**
+ * Packed value for a color of 'default' and opacity of 100%.
+ *
+ * @hide
+ */
+ public static final int COLOR_UNSPECIFIED = 0x00FFFFFF;
+
+ private static final CaptionStyle WHITE_ON_BLACK;
+ private static final CaptionStyle BLACK_ON_WHITE;
+ private static final CaptionStyle YELLOW_ON_BLACK;
+ private static final CaptionStyle YELLOW_ON_BLUE;
+ private static final CaptionStyle DEFAULT_CUSTOM;
+ private static final CaptionStyle UNSPECIFIED;
+
+ /** The default caption style used to fill in unspecified values. @hide */
+ public static final CaptionStyle DEFAULT;
+
+ /** @hide */
+ public static final CaptionStyle[] PRESETS;
+
+ /** @hide */
+ public static final int PRESET_CUSTOM = -1;
+
+ /** Unspecified edge type value. */
+ public static final int EDGE_TYPE_UNSPECIFIED = -1;
+
+ /** Edge type value specifying no character edges. */
+ public static final int EDGE_TYPE_NONE = 0;
+
+ /** Edge type value specifying uniformly outlined character edges. */
+ public static final int EDGE_TYPE_OUTLINE = 1;
+
+ /** Edge type value specifying drop-shadowed character edges. */
+ public static final int EDGE_TYPE_DROP_SHADOW = 2;
+
+ /** Edge type value specifying raised bevel character edges. */
+ public static final int EDGE_TYPE_RAISED = 3;
+
+ /** Edge type value specifying depressed bevel character edges. */
+ public static final int EDGE_TYPE_DEPRESSED = 4;
+
+ /** The preferred foreground color for video captions. */
+ public final int foregroundColor;
+
+ /** The preferred background color for video captions. */
+ public final int backgroundColor;
+
+ /**
+ * The preferred edge type for video captions, one of:
+ * <ul>
+ * <li>{@link #EDGE_TYPE_UNSPECIFIED}
+ * <li>{@link #EDGE_TYPE_NONE}
+ * <li>{@link #EDGE_TYPE_OUTLINE}
+ * <li>{@link #EDGE_TYPE_DROP_SHADOW}
+ * <li>{@link #EDGE_TYPE_RAISED}
+ * <li>{@link #EDGE_TYPE_DEPRESSED}
+ * </ul>
+ */
+ public final int edgeType;
+
+ /**
+ * The preferred edge color for video captions, if using an edge type
+ * other than {@link #EDGE_TYPE_NONE}.
+ */
+ public final int edgeColor;
+
+ /** The preferred window color for video captions. */
+ public final int windowColor;
+
+ /**
+ * @hide
+ */
+ public final String mRawTypeface;
+
+ private final boolean mHasForegroundColor;
+ private final boolean mHasBackgroundColor;
+ private final boolean mHasEdgeType;
+ private final boolean mHasEdgeColor;
+ private final boolean mHasWindowColor;
+
+ /** Lazily-created typeface based on the raw typeface string. */
+ private Typeface mParsedTypeface;
+
+ private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor,
+ int windowColor, String rawTypeface) {
+ mHasForegroundColor = hasColor(foregroundColor);
+ mHasBackgroundColor = hasColor(backgroundColor);
+ mHasEdgeType = edgeType != EDGE_TYPE_UNSPECIFIED;
+ mHasEdgeColor = hasColor(edgeColor);
+ mHasWindowColor = hasColor(windowColor);
+
+ // Always use valid colors, even when no override is specified, to
+ // ensure backwards compatibility with apps targeting KitKat MR2.
+ this.foregroundColor = mHasForegroundColor ? foregroundColor : Color.WHITE;
+ this.backgroundColor = mHasBackgroundColor ? backgroundColor : Color.BLACK;
+ this.edgeType = mHasEdgeType ? edgeType : EDGE_TYPE_NONE;
+ this.edgeColor = mHasEdgeColor ? edgeColor : Color.BLACK;
+ this.windowColor = mHasWindowColor ? windowColor : COLOR_NONE_OPAQUE;
+
+ mRawTypeface = rawTypeface;
+ }
+
+ /**
+ * Returns whether a packed color indicates a non-default value.
+ *
+ * @param packedColor the packed color value
+ * @return {@code true} if a non-default value is specified
+ * @hide
+ */
+ public static boolean hasColor(int packedColor) {
+ // Matches the color packing code from Settings. "Default" packed
+ // colors are indicated by zero alpha and non-zero red/blue. The
+ // cached alpha value used by Settings is stored in green.
+ return (packedColor >>> 24) != 0 || (packedColor & 0xFFFF00) == 0;
+ }
+
+ /**
+ * Applies a caption style, overriding any properties that are specified
+ * in the overlay caption.
+ *
+ * @param overlay The style to apply
+ * @return A caption style with the overlay style applied
+ * @hide
+ */
+ @NonNull
+ public CaptionStyle applyStyle(@NonNull CaptionStyle overlay) {
+ final int newForegroundColor = overlay.hasForegroundColor() ?
+ overlay.foregroundColor : foregroundColor;
+ final int newBackgroundColor = overlay.hasBackgroundColor() ?
+ overlay.backgroundColor : backgroundColor;
+ final int newEdgeType = overlay.hasEdgeType() ?
+ overlay.edgeType : edgeType;
+ final int newEdgeColor = overlay.hasEdgeColor() ?
+ overlay.edgeColor : edgeColor;
+ final int newWindowColor = overlay.hasWindowColor() ?
+ overlay.windowColor : windowColor;
+ final String newRawTypeface = overlay.mRawTypeface != null ?
+ overlay.mRawTypeface : mRawTypeface;
+ return new CaptionStyle(newForegroundColor, newBackgroundColor, newEdgeType,
+ newEdgeColor, newWindowColor, newRawTypeface);
+ }
+
+ /**
+ * @return {@code true} if the user has specified a background color
+ * that should override the application default, {@code false}
+ * otherwise
+ */
+ public boolean hasBackgroundColor() {
+ return mHasBackgroundColor;
+ }
+
+ /**
+ * @return {@code true} if the user has specified a foreground color
+ * that should override the application default, {@code false}
+ * otherwise
+ */
+ public boolean hasForegroundColor() {
+ return mHasForegroundColor;
+ }
+
+ /**
+ * @return {@code true} if the user has specified an edge type that
+ * should override the application default, {@code false}
+ * otherwise
+ */
+ public boolean hasEdgeType() {
+ return mHasEdgeType;
+ }
+
+ /**
+ * @return {@code true} if the user has specified an edge color that
+ * should override the application default, {@code false}
+ * otherwise
+ */
+ public boolean hasEdgeColor() {
+ return mHasEdgeColor;
+ }
+
+ /**
+ * @return {@code true} if the user has specified a window color that
+ * should override the application default, {@code false}
+ * otherwise
+ */
+ public boolean hasWindowColor() {
+ return mHasWindowColor;
+ }
+
+ /**
+ * @return the preferred {@link Typeface} for video captions, or null if
+ * not specified
+ */
+ @Nullable
+ public Typeface getTypeface() {
+ if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
+ mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL);
+ }
+ return mParsedTypeface;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static CaptionStyle getCustomStyle(ContentResolver cr) {
+ final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
+ final int foregroundColor = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor);
+ final int backgroundColor = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor);
+ final int edgeType = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
+ final int edgeColor = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
+ final int windowColor = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor);
+
+ String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+ if (rawTypeface == null) {
+ rawTypeface = defStyle.mRawTypeface;
+ }
+
+ return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor,
+ windowColor, rawTypeface);
+ }
+
+ static {
+ WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
+ BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
+ YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
+ YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
+ UNSPECIFIED = new CaptionStyle(COLOR_UNSPECIFIED, COLOR_UNSPECIFIED,
+ EDGE_TYPE_UNSPECIFIED, COLOR_UNSPECIFIED, COLOR_UNSPECIFIED, null);
+
+ // The ordering of these cannot change since we store the index
+ // directly in preferences.
+ PRESETS = new CaptionStyle[] {
+ WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE, UNSPECIFIED
+ };
+
+ DEFAULT_CUSTOM = WHITE_ON_BLACK;
+ DEFAULT = WHITE_ON_BLACK;
+ }
+ }
+
+ /**
+ * Listener for changes in captioning properties, including enabled state
+ * and user style preferences.
+ */
+ public static abstract class CaptioningChangeListener {
+ /**
+ * Called when the captioning enabled state changes.
+ *
+ * @param enabled the user's new preferred captioning enabled state
+ */
+ public void onEnabledChanged(boolean enabled) {}
+
+ /**
+ * Called when the captioning user style changes.
+ *
+ * @param userStyle the user's new preferred style
+ * @see CaptioningManager#getUserStyle()
+ */
+ public void onUserStyleChanged(@NonNull CaptionStyle userStyle) {}
+
+ /**
+ * Called when the captioning locale changes.
+ *
+ * @param locale the preferred captioning locale, or {@code null} if not specified
+ * @see CaptioningManager#getLocale()
+ */
+ public void onLocaleChanged(@Nullable Locale locale) {}
+
+ /**
+ * Called when the captioning font scaling factor changes.
+ *
+ * @param fontScale the preferred font scaling factor
+ * @see CaptioningManager#getFontScale()
+ */
+ public void onFontScaleChanged(float fontScale) {}
+ }
+}
diff --git a/android/view/animation/AccelerateDecelerateInterpolator.java b/android/view/animation/AccelerateDecelerateInterpolator.java
new file mode 100644
index 00000000..21d5a5b8
--- /dev/null
+++ b/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 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.animation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the rate of change starts and ends slowly but
+ * accelerates through the middle.
+ */
+@HasNativeInterpolator
+public class AccelerateDecelerateInterpolator extends BaseInterpolator
+ implements NativeInterpolatorFactory {
+ public AccelerateDecelerateInterpolator() {
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
+ }
+
+ public float getInterpolation(float input) {
+ return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
+ }
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
+ }
+}
diff --git a/android/view/animation/AccelerateInterpolator.java b/android/view/animation/AccelerateInterpolator.java
new file mode 100644
index 00000000..6c8d7b19
--- /dev/null
+++ b/android/view/animation/AccelerateInterpolator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the rate of change starts out slowly and
+ * and then accelerates.
+ *
+ */
+@HasNativeInterpolator
+public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+ private final float mFactor;
+ private final double mDoubleFactor;
+
+ public AccelerateInterpolator() {
+ mFactor = 1.0f;
+ mDoubleFactor = 2.0;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param factor Degree to which the animation should be eased. Seting
+ * factor to 1.0f produces a y=x^2 parabola. Increasing factor above
+ * 1.0f exaggerates the ease-in effect (i.e., it starts even
+ * slower and ends evens faster)
+ */
+ public AccelerateInterpolator(float factor) {
+ mFactor = factor;
+ mDoubleFactor = 2 * mFactor;
+ }
+
+ public AccelerateInterpolator(Context context, AttributeSet attrs) {
+ this(context.getResources(), context.getTheme(), attrs);
+ }
+
+ /** @hide */
+ public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
+ }
+
+ mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
+ mDoubleFactor = 2 * mFactor;
+ setChangingConfiguration(a.getChangingConfigurations());
+ a.recycle();
+ }
+
+ public float getInterpolation(float input) {
+ if (mFactor == 1.0f) {
+ return input * input;
+ } else {
+ return (float)Math.pow(input, mDoubleFactor);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
+ }
+}
diff --git a/android/view/animation/AlphaAnimation.java b/android/view/animation/AlphaAnimation.java
new file mode 100644
index 00000000..c4d9afcc
--- /dev/null
+++ b/android/view/animation/AlphaAnimation.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the alpha level of an object.
+ * Useful for fading things in and out. This animation ends up
+ * changing the alpha property of a {@link Transformation}
+ *
+ */
+public class AlphaAnimation extends Animation {
+ private float mFromAlpha;
+ private float mToAlpha;
+
+ /**
+ * Constructor used when an AlphaAnimation is loaded from a resource.
+ *
+ * @param context Application context to use
+ * @param attrs Attribute set from which to read values
+ */
+ public AlphaAnimation(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AlphaAnimation);
+
+ mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f);
+ mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f);
+
+ a.recycle();
+ }
+
+ /**
+ * Constructor to use when building an AlphaAnimation from code
+ *
+ * @param fromAlpha Starting alpha value for the animation, where 1.0 means
+ * fully opaque and 0.0 means fully transparent.
+ * @param toAlpha Ending alpha value for the animation.
+ */
+ public AlphaAnimation(float fromAlpha, float toAlpha) {
+ mFromAlpha = fromAlpha;
+ mToAlpha = toAlpha;
+ }
+
+ /**
+ * Changes the alpha property of the supplied {@link Transformation}
+ */
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ final float alpha = mFromAlpha;
+ t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
+ }
+
+ @Override
+ public boolean willChangeTransformationMatrix() {
+ return false;
+ }
+
+ @Override
+ public boolean willChangeBounds() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean hasAlpha() {
+ return true;
+ }
+}
diff --git a/android/view/animation/Animation.java b/android/view/animation/Animation.java
new file mode 100644
index 00000000..474db128
--- /dev/null
+++ b/android/view/animation/Animation.java
@@ -0,0 +1,1169 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.annotation.AnimRes;
+import android.annotation.ColorInt;
+import android.annotation.InterpolatorRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+import dalvik.system.CloseGuard;
+
+/**
+ * Abstraction for an Animation that can be applied to Views, Surfaces, or
+ * other objects. See the {@link android.view.animation animation package
+ * description file}.
+ */
+public abstract class Animation implements Cloneable {
+ /**
+ * Repeat the animation indefinitely.
+ */
+ public static final int INFINITE = -1;
+
+ /**
+ * When the animation reaches the end and the repeat count is INFINTE_REPEAT
+ * or a positive value, the animation restarts from the beginning.
+ */
+ public static final int RESTART = 1;
+
+ /**
+ * When the animation reaches the end and the repeat count is INFINTE_REPEAT
+ * or a positive value, the animation plays backward (and then forward again).
+ */
+ public static final int REVERSE = 2;
+
+ /**
+ * Can be used as the start time to indicate the start time should be the current
+ * time when {@link #getTransformation(long, Transformation)} is invoked for the
+ * first animation frame. This can is useful for short animations.
+ */
+ public static final int START_ON_FIRST_FRAME = -1;
+
+ /**
+ * The specified dimension is an absolute number of pixels.
+ */
+ public static final int ABSOLUTE = 0;
+
+ /**
+ * The specified dimension holds a float and should be multiplied by the
+ * height or width of the object being animated.
+ */
+ public static final int RELATIVE_TO_SELF = 1;
+
+ /**
+ * The specified dimension holds a float and should be multiplied by the
+ * height or width of the parent of the object being animated.
+ */
+ public static final int RELATIVE_TO_PARENT = 2;
+
+ /**
+ * Requests that the content being animated be kept in its current Z
+ * order.
+ */
+ public static final int ZORDER_NORMAL = 0;
+
+ /**
+ * Requests that the content being animated be forced on top of all other
+ * content for the duration of the animation.
+ */
+ public static final int ZORDER_TOP = 1;
+
+ /**
+ * Requests that the content being animated be forced under all other
+ * content for the duration of the animation.
+ */
+ public static final int ZORDER_BOTTOM = -1;
+
+ // Use a preload holder to isolate static initialization into inner class, which allows
+ // Animation and its subclasses to be compile-time initialized.
+ private static class NoImagePreloadHolder {
+ public static final boolean USE_CLOSEGUARD
+ = SystemProperties.getBoolean("log.closeguard.Animation", false);
+ }
+
+ /**
+ * Set by {@link #getTransformation(long, Transformation)} when the animation ends.
+ */
+ boolean mEnded = false;
+
+ /**
+ * Set by {@link #getTransformation(long, Transformation)} when the animation starts.
+ */
+ boolean mStarted = false;
+
+ /**
+ * Set by {@link #getTransformation(long, Transformation)} when the animation repeats
+ * in REVERSE mode.
+ */
+ boolean mCycleFlip = false;
+
+ /**
+ * This value must be set to true by {@link #initialize(int, int, int, int)}. It
+ * indicates the animation was successfully initialized and can be played.
+ */
+ boolean mInitialized = false;
+
+ /**
+ * Indicates whether the animation transformation should be applied before the
+ * animation starts. The value of this variable is only relevant if mFillEnabled is true;
+ * otherwise it is assumed to be true.
+ */
+ boolean mFillBefore = true;
+
+ /**
+ * Indicates whether the animation transformation should be applied after the
+ * animation ends.
+ */
+ boolean mFillAfter = false;
+
+ /**
+ * Indicates whether fillBefore should be taken into account.
+ */
+ boolean mFillEnabled = false;
+
+ /**
+ * The time in milliseconds at which the animation must start;
+ */
+ long mStartTime = -1;
+
+ /**
+ * The delay in milliseconds after which the animation must start. When the
+ * start offset is > 0, the start time of the animation is startTime + startOffset.
+ */
+ long mStartOffset;
+
+ /**
+ * The duration of one animation cycle in milliseconds.
+ */
+ long mDuration;
+
+ /**
+ * The number of times the animation must repeat. By default, an animation repeats
+ * indefinitely.
+ */
+ int mRepeatCount = 0;
+
+ /**
+ * Indicates how many times the animation was repeated.
+ */
+ int mRepeated = 0;
+
+ /**
+ * The behavior of the animation when it repeats. The repeat mode is either
+ * {@link #RESTART} or {@link #REVERSE}.
+ *
+ */
+ int mRepeatMode = RESTART;
+
+ /**
+ * The interpolator used by the animation to smooth the movement.
+ */
+ Interpolator mInterpolator;
+
+ /**
+ * The animation listener to be notified when the animation starts, ends or repeats.
+ */
+ AnimationListener mListener;
+
+ /**
+ * Desired Z order mode during animation.
+ */
+ private int mZAdjustment;
+
+ /**
+ * Desired background color behind animation.
+ */
+ private int mBackgroundColor;
+
+ /**
+ * scalefactor to apply to pivot points, etc. during animation. Subclasses retrieve the
+ * value via getScaleFactor().
+ */
+ private float mScaleFactor = 1f;
+
+ /**
+ * Don't animate the wallpaper.
+ */
+ private boolean mDetachWallpaper = false;
+
+ private boolean mMore = true;
+ private boolean mOneMoreTime = true;
+
+ RectF mPreviousRegion = new RectF();
+ RectF mRegion = new RectF();
+ Transformation mTransformation = new Transformation();
+ Transformation mPreviousTransformation = new Transformation();
+
+ private final CloseGuard guard = CloseGuard.get();
+
+ private Handler mListenerHandler;
+ private Runnable mOnStart;
+ private Runnable mOnRepeat;
+ private Runnable mOnEnd;
+
+ /**
+ * Creates a new animation with a duration of 0ms, the default interpolator, with
+ * fillBefore set to true and fillAfter set to false
+ */
+ public Animation() {
+ ensureInterpolator();
+ }
+
+ /**
+ * Creates a new animation whose parameters come from the specified context and
+ * attributes set.
+ *
+ * @param context the application environment
+ * @param attrs the set of attributes holding the animation parameters
+ */
+ public Animation(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animation);
+
+ setDuration((long) a.getInt(com.android.internal.R.styleable.Animation_duration, 0));
+ setStartOffset((long) a.getInt(com.android.internal.R.styleable.Animation_startOffset, 0));
+
+ setFillEnabled(a.getBoolean(com.android.internal.R.styleable.Animation_fillEnabled, mFillEnabled));
+ setFillBefore(a.getBoolean(com.android.internal.R.styleable.Animation_fillBefore, mFillBefore));
+ setFillAfter(a.getBoolean(com.android.internal.R.styleable.Animation_fillAfter, mFillAfter));
+
+ setRepeatCount(a.getInt(com.android.internal.R.styleable.Animation_repeatCount, mRepeatCount));
+ setRepeatMode(a.getInt(com.android.internal.R.styleable.Animation_repeatMode, RESTART));
+
+ setZAdjustment(a.getInt(com.android.internal.R.styleable.Animation_zAdjustment, ZORDER_NORMAL));
+
+ setBackgroundColor(a.getInt(com.android.internal.R.styleable.Animation_background, 0));
+
+ setDetachWallpaper(a.getBoolean(com.android.internal.R.styleable.Animation_detachWallpaper, false));
+
+ final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0);
+
+ a.recycle();
+
+ if (resID > 0) {
+ setInterpolator(context, resID);
+ }
+
+ ensureInterpolator();
+ }
+
+ @Override
+ protected Animation clone() throws CloneNotSupportedException {
+ final Animation animation = (Animation) super.clone();
+ animation.mPreviousRegion = new RectF();
+ animation.mRegion = new RectF();
+ animation.mTransformation = new Transformation();
+ animation.mPreviousTransformation = new Transformation();
+ return animation;
+ }
+
+ /**
+ * Reset the initialization state of this animation.
+ *
+ * @see #initialize(int, int, int, int)
+ */
+ public void reset() {
+ mPreviousRegion.setEmpty();
+ mPreviousTransformation.clear();
+ mInitialized = false;
+ mCycleFlip = false;
+ mRepeated = 0;
+ mMore = true;
+ mOneMoreTime = true;
+ mListenerHandler = null;
+ }
+
+ /**
+ * Cancel the animation. Cancelling an animation invokes the animation
+ * listener, if set, to notify the end of the animation.
+ *
+ * If you cancel an animation manually, you must call {@link #reset()}
+ * before starting the animation again.
+ *
+ * @see #reset()
+ * @see #start()
+ * @see #startNow()
+ */
+ public void cancel() {
+ if (mStarted && !mEnded) {
+ fireAnimationEnd();
+ mEnded = true;
+ guard.close();
+ }
+ // Make sure we move the animation to the end
+ mStartTime = Long.MIN_VALUE;
+ mMore = mOneMoreTime = false;
+ }
+
+ /**
+ * @hide
+ */
+ public void detach() {
+ if (mStarted && !mEnded) {
+ mEnded = true;
+ guard.close();
+ fireAnimationEnd();
+ }
+ }
+
+ /**
+ * Whether or not the animation has been initialized.
+ *
+ * @return Has this animation been initialized.
+ * @see #initialize(int, int, int, int)
+ */
+ public boolean isInitialized() {
+ return mInitialized;
+ }
+
+ /**
+ * Initialize this animation with the dimensions of the object being
+ * animated as well as the objects parents. (This is to support animation
+ * sizes being specified relative to these dimensions.)
+ *
+ * <p>Objects that interpret Animations should call this method when
+ * the sizes of the object being animated and its parent are known, and
+ * before calling {@link #getTransformation}.
+ *
+ *
+ * @param width Width of the object being animated
+ * @param height Height of the object being animated
+ * @param parentWidth Width of the animated object's parent
+ * @param parentHeight Height of the animated object's parent
+ */
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ reset();
+ mInitialized = true;
+ }
+
+ /**
+ * Sets the handler used to invoke listeners.
+ *
+ * @hide
+ */
+ public void setListenerHandler(Handler handler) {
+ if (mListenerHandler == null) {
+ mOnStart = new Runnable() {
+ public void run() {
+ if (mListener != null) {
+ mListener.onAnimationStart(Animation.this);
+ }
+ }
+ };
+ mOnRepeat = new Runnable() {
+ public void run() {
+ if (mListener != null) {
+ mListener.onAnimationRepeat(Animation.this);
+ }
+ }
+ };
+ mOnEnd = new Runnable() {
+ public void run() {
+ if (mListener != null) {
+ mListener.onAnimationEnd(Animation.this);
+ }
+ }
+ };
+ }
+ mListenerHandler = handler;
+ }
+
+ /**
+ * Sets the acceleration curve for this animation. The interpolator is loaded as
+ * a resource from the specified context.
+ *
+ * @param context The application environment
+ * @param resID The resource identifier of the interpolator to load
+ * @attr ref android.R.styleable#Animation_interpolator
+ */
+ public void setInterpolator(Context context, @AnimRes @InterpolatorRes int resID) {
+ setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+ }
+
+ /**
+ * Sets the acceleration curve for this animation. Defaults to a linear
+ * interpolation.
+ *
+ * @param i The interpolator which defines the acceleration curve
+ * @attr ref android.R.styleable#Animation_interpolator
+ */
+ public void setInterpolator(Interpolator i) {
+ mInterpolator = i;
+ }
+
+ /**
+ * When this animation should start relative to the start time. This is most
+ * useful when composing complex animations using an {@link AnimationSet }
+ * where some of the animations components start at different times.
+ *
+ * @param startOffset When this Animation should start, in milliseconds from
+ * the start time of the root AnimationSet.
+ * @attr ref android.R.styleable#Animation_startOffset
+ */
+ public void setStartOffset(long startOffset) {
+ mStartOffset = startOffset;
+ }
+
+ /**
+ * How long this animation should last. The duration cannot be negative.
+ *
+ * @param durationMillis Duration in milliseconds
+ *
+ * @throws java.lang.IllegalArgumentException if the duration is < 0
+ *
+ * @attr ref android.R.styleable#Animation_duration
+ */
+ public void setDuration(long durationMillis) {
+ if (durationMillis < 0) {
+ throw new IllegalArgumentException("Animation duration cannot be negative");
+ }
+ mDuration = durationMillis;
+ }
+
+ /**
+ * Ensure that the duration that this animation will run is not longer
+ * than <var>durationMillis</var>. In addition to adjusting the duration
+ * itself, this ensures that the repeat count also will not make it run
+ * longer than the given time.
+ *
+ * @param durationMillis The maximum duration the animation is allowed
+ * to run.
+ */
+ public void restrictDuration(long durationMillis) {
+ // If we start after the duration, then we just won't run.
+ if (mStartOffset > durationMillis) {
+ mStartOffset = durationMillis;
+ mDuration = 0;
+ mRepeatCount = 0;
+ return;
+ }
+
+ long dur = mDuration + mStartOffset;
+ if (dur > durationMillis) {
+ mDuration = durationMillis-mStartOffset;
+ dur = durationMillis;
+ }
+ // If the duration is 0 or less, then we won't run.
+ if (mDuration <= 0) {
+ mDuration = 0;
+ mRepeatCount = 0;
+ return;
+ }
+ // Reduce the number of repeats to keep below the maximum duration.
+ // The comparison between mRepeatCount and duration is to catch
+ // overflows after multiplying them.
+ if (mRepeatCount < 0 || mRepeatCount > durationMillis
+ || (dur*mRepeatCount) > durationMillis) {
+ // Figure out how many times to do the animation. Subtract 1 since
+ // repeat count is the number of times to repeat so 0 runs once.
+ mRepeatCount = (int)(durationMillis/dur) - 1;
+ if (mRepeatCount < 0) {
+ mRepeatCount = 0;
+ }
+ }
+ }
+
+ /**
+ * How much to scale the duration by.
+ *
+ * @param scale The amount to scale the duration.
+ */
+ public void scaleCurrentDuration(float scale) {
+ mDuration = (long) (mDuration * scale);
+ mStartOffset = (long) (mStartOffset * scale);
+ }
+
+ /**
+ * When this animation should start. When the start time is set to
+ * {@link #START_ON_FIRST_FRAME}, the animation will start the first time
+ * {@link #getTransformation(long, Transformation)} is invoked. The time passed
+ * to this method should be obtained by calling
+ * {@link AnimationUtils#currentAnimationTimeMillis()} instead of
+ * {@link System#currentTimeMillis()}.
+ *
+ * @param startTimeMillis the start time in milliseconds
+ */
+ public void setStartTime(long startTimeMillis) {
+ mStartTime = startTimeMillis;
+ mStarted = mEnded = false;
+ mCycleFlip = false;
+ mRepeated = 0;
+ mMore = true;
+ }
+
+ /**
+ * Convenience method to start the animation the first time
+ * {@link #getTransformation(long, Transformation)} is invoked.
+ */
+ public void start() {
+ setStartTime(-1);
+ }
+
+ /**
+ * Convenience method to start the animation at the current time in
+ * milliseconds.
+ */
+ public void startNow() {
+ setStartTime(AnimationUtils.currentAnimationTimeMillis());
+ }
+
+ /**
+ * Defines what this animation should do when it reaches the end. This
+ * setting is applied only when the repeat count is either greater than
+ * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
+ *
+ * @param repeatMode {@link #RESTART} or {@link #REVERSE}
+ * @attr ref android.R.styleable#Animation_repeatMode
+ */
+ public void setRepeatMode(int repeatMode) {
+ mRepeatMode = repeatMode;
+ }
+
+ /**
+ * Sets how many times the animation should be repeated. If the repeat
+ * count is 0, the animation is never repeated. If the repeat count is
+ * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
+ * into account. The repeat count is 0 by default.
+ *
+ * @param repeatCount the number of times the animation should be repeated
+ * @attr ref android.R.styleable#Animation_repeatCount
+ */
+ public void setRepeatCount(int repeatCount) {
+ if (repeatCount < 0) {
+ repeatCount = INFINITE;
+ }
+ mRepeatCount = repeatCount;
+ }
+
+ /**
+ * If fillEnabled is true, this animation will apply the value of fillBefore.
+ *
+ * @return true if the animation will take fillBefore into account
+ * @attr ref android.R.styleable#Animation_fillEnabled
+ */
+ public boolean isFillEnabled() {
+ return mFillEnabled;
+ }
+
+ /**
+ * If fillEnabled is true, the animation will apply the value of fillBefore.
+ * Otherwise, fillBefore is ignored and the animation
+ * transformation is always applied until the animation ends.
+ *
+ * @param fillEnabled true if the animation should take the value of fillBefore into account
+ * @attr ref android.R.styleable#Animation_fillEnabled
+ *
+ * @see #setFillBefore(boolean)
+ * @see #setFillAfter(boolean)
+ */
+ public void setFillEnabled(boolean fillEnabled) {
+ mFillEnabled = fillEnabled;
+ }
+
+ /**
+ * If fillBefore is true, this animation will apply its transformation
+ * before the start time of the animation. Defaults to true if
+ * {@link #setFillEnabled(boolean)} is not set to true.
+ * Note that this applies when using an {@link
+ * android.view.animation.AnimationSet AnimationSet} to chain
+ * animations. The transformation is not applied before the AnimationSet
+ * itself starts.
+ *
+ * @param fillBefore true if the animation should apply its transformation before it starts
+ * @attr ref android.R.styleable#Animation_fillBefore
+ *
+ * @see #setFillEnabled(boolean)
+ */
+ public void setFillBefore(boolean fillBefore) {
+ mFillBefore = fillBefore;
+ }
+
+ /**
+ * If fillAfter is true, the transformation that this animation performed
+ * will persist when it is finished. Defaults to false if not set.
+ * Note that this applies to individual animations and when using an {@link
+ * android.view.animation.AnimationSet AnimationSet} to chain
+ * animations.
+ *
+ * @param fillAfter true if the animation should apply its transformation after it ends
+ * @attr ref android.R.styleable#Animation_fillAfter
+ *
+ * @see #setFillEnabled(boolean)
+ */
+ public void setFillAfter(boolean fillAfter) {
+ mFillAfter = fillAfter;
+ }
+
+ /**
+ * Set the Z ordering mode to use while running the animation.
+ *
+ * @param zAdjustment The desired mode, one of {@link #ZORDER_NORMAL},
+ * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
+ * @attr ref android.R.styleable#Animation_zAdjustment
+ */
+ public void setZAdjustment(int zAdjustment) {
+ mZAdjustment = zAdjustment;
+ }
+
+ /**
+ * Set background behind animation.
+ *
+ * @param bg The background color. If 0, no background. Currently must
+ * be black, with any desired alpha level.
+ */
+ public void setBackgroundColor(@ColorInt int bg) {
+ mBackgroundColor = bg;
+ }
+
+ /**
+ * The scale factor is set by the call to <code>getTransformation</code>. Overrides of
+ * {@link #getTransformation(long, Transformation, float)} will get this value
+ * directly. Overrides of {@link #applyTransformation(float, Transformation)} can
+ * call this method to get the value.
+ *
+ * @return float The scale factor that should be applied to pre-scaled values in
+ * an Animation such as the pivot points in {@link ScaleAnimation} and {@link RotateAnimation}.
+ */
+ protected float getScaleFactor() {
+ return mScaleFactor;
+ }
+
+ /**
+ * If detachWallpaper is true, and this is a window animation of a window
+ * that has a wallpaper background, then the window will be detached from
+ * the wallpaper while it runs. That is, the animation will only be applied
+ * to the window, and the wallpaper behind it will remain static.
+ *
+ * @param detachWallpaper true if the wallpaper should be detached from the animation
+ * @attr ref android.R.styleable#Animation_detachWallpaper
+ */
+ public void setDetachWallpaper(boolean detachWallpaper) {
+ mDetachWallpaper = detachWallpaper;
+ }
+
+ /**
+ * Gets the acceleration curve type for this animation.
+ *
+ * @return the {@link Interpolator} associated to this animation
+ * @attr ref android.R.styleable#Animation_interpolator
+ */
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * When this animation should start. If the animation has not startet yet,
+ * this method might return {@link #START_ON_FIRST_FRAME}.
+ *
+ * @return the time in milliseconds when the animation should start or
+ * {@link #START_ON_FIRST_FRAME}
+ */
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ /**
+ * How long this animation should last
+ *
+ * @return the duration in milliseconds of the animation
+ * @attr ref android.R.styleable#Animation_duration
+ */
+ public long getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * When this animation should start, relative to StartTime
+ *
+ * @return the start offset in milliseconds
+ * @attr ref android.R.styleable#Animation_startOffset
+ */
+ public long getStartOffset() {
+ return mStartOffset;
+ }
+
+ /**
+ * Defines what this animation should do when it reaches the end.
+ *
+ * @return either one of {@link #REVERSE} or {@link #RESTART}
+ * @attr ref android.R.styleable#Animation_repeatMode
+ */
+ public int getRepeatMode() {
+ return mRepeatMode;
+ }
+
+ /**
+ * Defines how many times the animation should repeat. The default value
+ * is 0.
+ *
+ * @return the number of times the animation should repeat, or {@link #INFINITE}
+ * @attr ref android.R.styleable#Animation_repeatCount
+ */
+ public int getRepeatCount() {
+ return mRepeatCount;
+ }
+
+ /**
+ * If fillBefore is true, this animation will apply its transformation
+ * before the start time of the animation. If fillBefore is false and
+ * {@link #isFillEnabled() fillEnabled} is true, the transformation will not be applied until
+ * the start time of the animation.
+ *
+ * @return true if the animation applies its transformation before it starts
+ * @attr ref android.R.styleable#Animation_fillBefore
+ */
+ public boolean getFillBefore() {
+ return mFillBefore;
+ }
+
+ /**
+ * If fillAfter is true, this animation will apply its transformation
+ * after the end time of the animation.
+ *
+ * @return true if the animation applies its transformation after it ends
+ * @attr ref android.R.styleable#Animation_fillAfter
+ */
+ public boolean getFillAfter() {
+ return mFillAfter;
+ }
+
+ /**
+ * Returns the Z ordering mode to use while running the animation as
+ * previously set by {@link #setZAdjustment}.
+ *
+ * @return Returns one of {@link #ZORDER_NORMAL},
+ * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}.
+ * @attr ref android.R.styleable#Animation_zAdjustment
+ */
+ public int getZAdjustment() {
+ return mZAdjustment;
+ }
+
+ /**
+ * Returns the background color behind the animation.
+ */
+ @ColorInt
+ public int getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
+ /**
+ * Return value of {@link #setDetachWallpaper(boolean)}.
+ * @attr ref android.R.styleable#Animation_detachWallpaper
+ */
+ public boolean getDetachWallpaper() {
+ return mDetachWallpaper;
+ }
+
+ /**
+ * <p>Indicates whether or not this animation will affect the transformation
+ * matrix. For instance, a fade animation will not affect the matrix whereas
+ * a scale animation will.</p>
+ *
+ * @return true if this animation will change the transformation matrix
+ */
+ public boolean willChangeTransformationMatrix() {
+ // assume we will change the matrix
+ return true;
+ }
+
+ /**
+ * <p>Indicates whether or not this animation will affect the bounds of the
+ * animated view. For instance, a fade animation will not affect the bounds
+ * whereas a 200% scale animation will.</p>
+ *
+ * @return true if this animation will change the view's bounds
+ */
+ public boolean willChangeBounds() {
+ // assume we will change the bounds
+ return true;
+ }
+
+ /**
+ * <p>Binds an animation listener to this animation. The animation listener
+ * is notified of animation events such as the end of the animation or the
+ * repetition of the animation.</p>
+ *
+ * @param listener the animation listener to be notified
+ */
+ public void setAnimationListener(AnimationListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Gurantees that this animation has an interpolator. Will use
+ * a AccelerateDecelerateInterpolator is nothing else was specified.
+ */
+ protected void ensureInterpolator() {
+ if (mInterpolator == null) {
+ mInterpolator = new AccelerateDecelerateInterpolator();
+ }
+ }
+
+ /**
+ * Compute a hint at how long the entire animation may last, in milliseconds.
+ * Animations can be written to cause themselves to run for a different
+ * duration than what is computed here, but generally this should be
+ * accurate.
+ */
+ public long computeDurationHint() {
+ return (getStartOffset() + getDuration()) * (getRepeatCount() + 1);
+ }
+
+ /**
+ * Gets the transformation to apply at a specified point in time. Implementations of this
+ * method should always replace the specified Transformation or document they are doing
+ * otherwise.
+ *
+ * @param currentTime Where we are in the animation. This is wall clock time.
+ * @param outTransformation A transformation object that is provided by the
+ * caller and will be filled in by the animation.
+ * @return True if the animation is still running
+ */
+ public boolean getTransformation(long currentTime, Transformation outTransformation) {
+ if (mStartTime == -1) {
+ mStartTime = currentTime;
+ }
+
+ final long startOffset = getStartOffset();
+ final long duration = mDuration;
+ float normalizedTime;
+ if (duration != 0) {
+ normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
+ (float) duration;
+ } else {
+ // time is a step-change with a zero duration
+ normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
+ }
+
+ final boolean expired = normalizedTime >= 1.0f || isCanceled();
+ mMore = !expired;
+
+ if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
+
+ if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
+ if (!mStarted) {
+ fireAnimationStart();
+ mStarted = true;
+ if (NoImagePreloadHolder.USE_CLOSEGUARD) {
+ guard.open("cancel or detach or getTransformation");
+ }
+ }
+
+ if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
+
+ if (mCycleFlip) {
+ normalizedTime = 1.0f - normalizedTime;
+ }
+
+ final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
+ applyTransformation(interpolatedTime, outTransformation);
+ }
+
+ if (expired) {
+ if (mRepeatCount == mRepeated || isCanceled()) {
+ if (!mEnded) {
+ mEnded = true;
+ guard.close();
+ fireAnimationEnd();
+ }
+ } else {
+ if (mRepeatCount > 0) {
+ mRepeated++;
+ }
+
+ if (mRepeatMode == REVERSE) {
+ mCycleFlip = !mCycleFlip;
+ }
+
+ mStartTime = -1;
+ mMore = true;
+
+ fireAnimationRepeat();
+ }
+ }
+
+ if (!mMore && mOneMoreTime) {
+ mOneMoreTime = false;
+ return true;
+ }
+
+ return mMore;
+ }
+
+ private boolean isCanceled() {
+ return mStartTime == Long.MIN_VALUE;
+ }
+
+ private void fireAnimationStart() {
+ if (mListener != null) {
+ if (mListenerHandler == null) mListener.onAnimationStart(this);
+ else mListenerHandler.postAtFrontOfQueue(mOnStart);
+ }
+ }
+
+ private void fireAnimationRepeat() {
+ if (mListener != null) {
+ if (mListenerHandler == null) mListener.onAnimationRepeat(this);
+ else mListenerHandler.postAtFrontOfQueue(mOnRepeat);
+ }
+ }
+
+ private void fireAnimationEnd() {
+ if (mListener != null) {
+ if (mListenerHandler == null) mListener.onAnimationEnd(this);
+ else mListenerHandler.postAtFrontOfQueue(mOnEnd);
+ }
+ }
+
+ /**
+ * Gets the transformation to apply at a specified point in time. Implementations of this
+ * method should always replace the specified Transformation or document they are doing
+ * otherwise.
+ *
+ * @param currentTime Where we are in the animation. This is wall clock time.
+ * @param outTransformation A transformation object that is provided by the
+ * caller and will be filled in by the animation.
+ * @param scale Scaling factor to apply to any inputs to the transform operation, such
+ * pivot points being rotated or scaled around.
+ * @return True if the animation is still running
+ */
+ public boolean getTransformation(long currentTime, Transformation outTransformation,
+ float scale) {
+ mScaleFactor = scale;
+ return getTransformation(currentTime, outTransformation);
+ }
+
+ /**
+ * <p>Indicates whether this animation has started or not.</p>
+ *
+ * @return true if the animation has started, false otherwise
+ */
+ public boolean hasStarted() {
+ return mStarted;
+ }
+
+ /**
+ * <p>Indicates whether this animation has ended or not.</p>
+ *
+ * @return true if the animation has ended, false otherwise
+ */
+ public boolean hasEnded() {
+ return mEnded;
+ }
+
+ /**
+ * Helper for getTransformation. Subclasses should implement this to apply
+ * their transforms given an interpolation value. Implementations of this
+ * method should always replace the specified Transformation or document
+ * they are doing otherwise.
+ *
+ * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
+ * after it has been run through the interpolation function.
+ * @param t The Transformation object to fill in with the current
+ * transforms.
+ */
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ }
+
+ /**
+ * Convert the information in the description of a size to an actual
+ * dimension
+ *
+ * @param type One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param value The dimension associated with the type parameter
+ * @param size The size of the object being animated
+ * @param parentSize The size of the parent of the object being animated
+ * @return The dimension to use for the animation
+ */
+ protected float resolveSize(int type, float value, int size, int parentSize) {
+ switch (type) {
+ case ABSOLUTE:
+ return value;
+ case RELATIVE_TO_SELF:
+ return size * value;
+ case RELATIVE_TO_PARENT:
+ return parentSize * value;
+ default:
+ return value;
+ }
+ }
+
+ /**
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ * @param invalidate
+ * @param transformation
+ *
+ * @hide
+ */
+ public void getInvalidateRegion(int left, int top, int right, int bottom,
+ RectF invalidate, Transformation transformation) {
+
+ final RectF tempRegion = mRegion;
+ final RectF previousRegion = mPreviousRegion;
+
+ invalidate.set(left, top, right, bottom);
+ transformation.getMatrix().mapRect(invalidate);
+ // Enlarge the invalidate region to account for rounding errors
+ invalidate.inset(-1.0f, -1.0f);
+ tempRegion.set(invalidate);
+ invalidate.union(previousRegion);
+
+ previousRegion.set(tempRegion);
+
+ final Transformation tempTransformation = mTransformation;
+ final Transformation previousTransformation = mPreviousTransformation;
+
+ tempTransformation.set(transformation);
+ transformation.set(previousTransformation);
+ previousTransformation.set(tempTransformation);
+ }
+
+ /**
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ *
+ * @hide
+ */
+ public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+ final RectF region = mPreviousRegion;
+ region.set(left, top, right, bottom);
+ // Enlarge the invalidate region to account for rounding errors
+ region.inset(-1.0f, -1.0f);
+ if (mFillBefore) {
+ final Transformation previousTransformation = mPreviousTransformation;
+ applyTransformation(mInterpolator.getInterpolation(0.0f), previousTransformation);
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (guard != null) {
+ guard.warnIfOpen();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Return true if this animation changes the view's alpha property.
+ *
+ * @hide
+ */
+ public boolean hasAlpha() {
+ return false;
+ }
+
+ /**
+ * Utility class to parse a string description of a size.
+ */
+ protected static class Description {
+ /**
+ * One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ */
+ public int type;
+
+ /**
+ * The absolute or relative dimension for this Description.
+ */
+ public float value;
+
+ /**
+ * Size descriptions can appear inthree forms:
+ * <ol>
+ * <li>An absolute size. This is represented by a number.</li>
+ * <li>A size relative to the size of the object being animated. This
+ * is represented by a number followed by "%".</li> *
+ * <li>A size relative to the size of the parent of object being
+ * animated. This is represented by a number followed by "%p".</li>
+ * </ol>
+ * @param value The typed value to parse
+ * @return The parsed version of the description
+ */
+ static Description parseValue(TypedValue value) {
+ Description d = new Description();
+ if (value == null) {
+ d.type = ABSOLUTE;
+ d.value = 0;
+ } else {
+ if (value.type == TypedValue.TYPE_FRACTION) {
+ d.type = (value.data & TypedValue.COMPLEX_UNIT_MASK) ==
+ TypedValue.COMPLEX_UNIT_FRACTION_PARENT ?
+ RELATIVE_TO_PARENT : RELATIVE_TO_SELF;
+ d.value = TypedValue.complexToFloat(value.data);
+ return d;
+ } else if (value.type == TypedValue.TYPE_FLOAT) {
+ d.type = ABSOLUTE;
+ d.value = value.getFloat();
+ return d;
+ } else if (value.type >= TypedValue.TYPE_FIRST_INT &&
+ value.type <= TypedValue.TYPE_LAST_INT) {
+ d.type = ABSOLUTE;
+ d.value = value.data;
+ return d;
+ }
+ }
+
+ d.type = ABSOLUTE;
+ d.value = 0.0f;
+
+ return d;
+ }
+ }
+
+ /**
+ * <p>An animation listener receives notifications from an animation.
+ * Notifications indicate animation related events, such as the end or the
+ * repetition of the animation.</p>
+ */
+ public static interface AnimationListener {
+ /**
+ * <p>Notifies the start of the animation.</p>
+ *
+ * @param animation The started animation.
+ */
+ void onAnimationStart(Animation animation);
+
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animation The animation which reached its end.
+ */
+ void onAnimationEnd(Animation animation);
+
+ /**
+ * <p>Notifies the repetition of the animation.</p>
+ *
+ * @param animation The animation which was repeated.
+ */
+ void onAnimationRepeat(Animation animation);
+ }
+}
diff --git a/android/view/animation/AnimationSet.java b/android/view/animation/AnimationSet.java
new file mode 100644
index 00000000..767024ec
--- /dev/null
+++ b/android/view/animation/AnimationSet.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a group of Animations that should be played together.
+ * The transformation of each individual animation are composed
+ * together into a single transform.
+ * If AnimationSet sets any properties that its children also set
+ * (for example, duration or fillBefore), the values of AnimationSet
+ * override the child values.
+ *
+ * <p>The way that AnimationSet inherits behavior from Animation is important to
+ * understand. Some of the Animation attributes applied to AnimationSet affect the
+ * AnimationSet itself, some are pushed down to the children, and some are ignored,
+ * as follows:
+ * <ul>
+ * <li>duration, repeatMode, fillBefore, fillAfter: These properties, when set
+ * on an AnimationSet object, will be pushed down to all child animations.</li>
+ * <li>repeatCount, fillEnabled: These properties are ignored for AnimationSet.</li>
+ * <li>startOffset, shareInterpolator: These properties apply to the AnimationSet itself.</li>
+ * </ul>
+ * Starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH},
+ * the behavior of these properties is the same in XML resources and at runtime (prior to that
+ * release, the values set in XML were ignored for AnimationSet). That is, calling
+ * <code>setDuration(500)</code> on an AnimationSet has the same effect as declaring
+ * <code>android:duration="500"</code> in an XML resource for an AnimationSet object.</p>
+ */
+public class AnimationSet extends Animation {
+ private static final int PROPERTY_FILL_AFTER_MASK = 0x1;
+ private static final int PROPERTY_FILL_BEFORE_MASK = 0x2;
+ private static final int PROPERTY_REPEAT_MODE_MASK = 0x4;
+ private static final int PROPERTY_START_OFFSET_MASK = 0x8;
+ private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10;
+ private static final int PROPERTY_DURATION_MASK = 0x20;
+ private static final int PROPERTY_MORPH_MATRIX_MASK = 0x40;
+ private static final int PROPERTY_CHANGE_BOUNDS_MASK = 0x80;
+
+ private int mFlags = 0;
+ private boolean mDirty;
+ private boolean mHasAlpha;
+
+ private ArrayList<Animation> mAnimations = new ArrayList<Animation>();
+
+ private Transformation mTempTransformation = new Transformation();
+
+ private long mLastEnd;
+
+ private long[] mStoredOffsets;
+
+ /**
+ * Constructor used when an AnimationSet is loaded from a resource.
+ *
+ * @param context Application context to use
+ * @param attrs Attribute set from which to read values
+ */
+ public AnimationSet(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimationSet);
+
+ setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
+ a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
+ init();
+
+ if (context.getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (a.hasValue(com.android.internal.R.styleable.AnimationSet_duration)) {
+ mFlags |= PROPERTY_DURATION_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillBefore)) {
+ mFlags |= PROPERTY_FILL_BEFORE_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.AnimationSet_fillAfter)) {
+ mFlags |= PROPERTY_FILL_AFTER_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.AnimationSet_repeatMode)) {
+ mFlags |= PROPERTY_REPEAT_MODE_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.AnimationSet_startOffset)) {
+ mFlags |= PROPERTY_START_OFFSET_MASK;
+ }
+ }
+
+ a.recycle();
+ }
+
+
+ /**
+ * Constructor to use when building an AnimationSet from code
+ *
+ * @param shareInterpolator Pass true if all of the animations in this set
+ * should use the interpolator associated with this AnimationSet.
+ * Pass false if each animation should use its own interpolator.
+ */
+ public AnimationSet(boolean shareInterpolator) {
+ setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator);
+ init();
+ }
+
+ @Override
+ protected AnimationSet clone() throws CloneNotSupportedException {
+ final AnimationSet animation = (AnimationSet) super.clone();
+ animation.mTempTransformation = new Transformation();
+ animation.mAnimations = new ArrayList<Animation>();
+
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+
+ for (int i = 0; i < count; i++) {
+ animation.mAnimations.add(animations.get(i).clone());
+ }
+
+ return animation;
+ }
+
+ private void setFlag(int mask, boolean value) {
+ if (value) {
+ mFlags |= mask;
+ } else {
+ mFlags &= ~mask;
+ }
+ }
+
+ private void init() {
+ mStartTime = 0;
+ }
+
+ @Override
+ public void setFillAfter(boolean fillAfter) {
+ mFlags |= PROPERTY_FILL_AFTER_MASK;
+ super.setFillAfter(fillAfter);
+ }
+
+ @Override
+ public void setFillBefore(boolean fillBefore) {
+ mFlags |= PROPERTY_FILL_BEFORE_MASK;
+ super.setFillBefore(fillBefore);
+ }
+
+ @Override
+ public void setRepeatMode(int repeatMode) {
+ mFlags |= PROPERTY_REPEAT_MODE_MASK;
+ super.setRepeatMode(repeatMode);
+ }
+
+ @Override
+ public void setStartOffset(long startOffset) {
+ mFlags |= PROPERTY_START_OFFSET_MASK;
+ super.setStartOffset(startOffset);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean hasAlpha() {
+ if (mDirty) {
+ mDirty = mHasAlpha = false;
+
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+
+ for (int i = 0; i < count; i++) {
+ if (animations.get(i).hasAlpha()) {
+ mHasAlpha = true;
+ break;
+ }
+ }
+ }
+
+ return mHasAlpha;
+ }
+
+ /**
+ * <p>Sets the duration of every child animation.</p>
+ *
+ * @param durationMillis the duration of the animation, in milliseconds, for
+ * every child in this set
+ */
+ @Override
+ public void setDuration(long durationMillis) {
+ mFlags |= PROPERTY_DURATION_MASK;
+ super.setDuration(durationMillis);
+ mLastEnd = mStartOffset + mDuration;
+ }
+
+ /**
+ * Add a child animation to this animation set.
+ * The transforms of the child animations are applied in the order
+ * that they were added
+ * @param a Animation to add.
+ */
+ public void addAnimation(Animation a) {
+ mAnimations.add(a);
+
+ boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0;
+ if (noMatrix && a.willChangeTransformationMatrix()) {
+ mFlags |= PROPERTY_MORPH_MATRIX_MASK;
+ }
+
+ boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0;
+
+
+ if (changeBounds && a.willChangeBounds()) {
+ mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
+ }
+
+ if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
+ mLastEnd = mStartOffset + mDuration;
+ } else {
+ if (mAnimations.size() == 1) {
+ mDuration = a.getStartOffset() + a.getDuration();
+ mLastEnd = mStartOffset + mDuration;
+ } else {
+ mLastEnd = Math.max(mLastEnd, mStartOffset + a.getStartOffset() + a.getDuration());
+ mDuration = mLastEnd - mStartOffset;
+ }
+ }
+
+ mDirty = true;
+ }
+
+ /**
+ * Sets the start time of this animation and all child animations
+ *
+ * @see android.view.animation.Animation#setStartTime(long)
+ */
+ @Override
+ public void setStartTime(long startTimeMillis) {
+ super.setStartTime(startTimeMillis);
+
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+
+ for (int i = 0; i < count; i++) {
+ Animation a = animations.get(i);
+ a.setStartTime(startTimeMillis);
+ }
+ }
+
+ @Override
+ public long getStartTime() {
+ long startTime = Long.MAX_VALUE;
+
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+
+ for (int i = 0; i < count; i++) {
+ Animation a = animations.get(i);
+ startTime = Math.min(startTime, a.getStartTime());
+ }
+
+ return startTime;
+ }
+
+ @Override
+ public void restrictDuration(long durationMillis) {
+ super.restrictDuration(durationMillis);
+
+ final ArrayList<Animation> animations = mAnimations;
+ int count = animations.size();
+
+ for (int i = 0; i < count; i++) {
+ animations.get(i).restrictDuration(durationMillis);
+ }
+ }
+
+ /**
+ * The duration of an AnimationSet is defined to be the
+ * duration of the longest child animation.
+ *
+ * @see android.view.animation.Animation#getDuration()
+ */
+ @Override
+ public long getDuration() {
+ final ArrayList<Animation> animations = mAnimations;
+ final int count = animations.size();
+ long duration = 0;
+
+ boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
+ if (durationSet) {
+ duration = mDuration;
+ } else {
+ for (int i = 0; i < count; i++) {
+ duration = Math.max(duration, animations.get(i).getDuration());
+ }
+ }
+
+ return duration;
+ }
+
+ /**
+ * The duration hint of an animation set is the maximum of the duration
+ * hints of all of its component animations.
+ *
+ * @see android.view.animation.Animation#computeDurationHint
+ */
+ public long computeDurationHint() {
+ long duration = 0;
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+ for (int i = count - 1; i >= 0; --i) {
+ final long d = animations.get(i).computeDurationHint();
+ if (d > duration) duration = d;
+ }
+ return duration;
+ }
+
+ /**
+ * @hide
+ */
+ public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+ final RectF region = mPreviousRegion;
+ region.set(left, top, right, bottom);
+ region.inset(-1.0f, -1.0f);
+
+ if (mFillBefore) {
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+ final Transformation temp = mTempTransformation;
+
+ final Transformation previousTransformation = mPreviousTransformation;
+
+ for (int i = count - 1; i >= 0; --i) {
+ final Animation a = animations.get(i);
+ if (!a.isFillEnabled() || a.getFillBefore() || a.getStartOffset() == 0) {
+ temp.clear();
+ final Interpolator interpolator = a.mInterpolator;
+ a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f)
+ : 0.0f, temp);
+ previousTransformation.compose(temp);
+ }
+ }
+ }
+ }
+
+ /**
+ * The transformation of an animation set is the concatenation of all of its
+ * component animations.
+ *
+ * @see android.view.animation.Animation#getTransformation
+ */
+ @Override
+ public boolean getTransformation(long currentTime, Transformation t) {
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+ final Transformation temp = mTempTransformation;
+
+ boolean more = false;
+ boolean started = false;
+ boolean ended = true;
+
+ t.clear();
+
+ for (int i = count - 1; i >= 0; --i) {
+ final Animation a = animations.get(i);
+
+ temp.clear();
+ more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
+ t.compose(temp);
+
+ started = started || a.hasStarted();
+ ended = a.hasEnded() && ended;
+ }
+
+ if (started && !mStarted) {
+ if (mListener != null) {
+ mListener.onAnimationStart(this);
+ }
+ mStarted = true;
+ }
+
+ if (ended != mEnded) {
+ if (mListener != null) {
+ mListener.onAnimationEnd(this);
+ }
+ mEnded = ended;
+ }
+
+ return more;
+ }
+
+ /**
+ * @see android.view.animation.Animation#scaleCurrentDuration(float)
+ */
+ @Override
+ public void scaleCurrentDuration(float scale) {
+ final ArrayList<Animation> animations = mAnimations;
+ int count = animations.size();
+ for (int i = 0; i < count; i++) {
+ animations.get(i).scaleCurrentDuration(scale);
+ }
+ }
+
+ /**
+ * @see android.view.animation.Animation#initialize(int, int, int, int)
+ */
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+
+ boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
+ boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK;
+ boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK;
+ boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK;
+ boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK)
+ == PROPERTY_SHARE_INTERPOLATOR_MASK;
+ boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
+ == PROPERTY_START_OFFSET_MASK;
+
+ if (shareInterpolator) {
+ ensureInterpolator();
+ }
+
+ final ArrayList<Animation> children = mAnimations;
+ final int count = children.size();
+
+ final long duration = mDuration;
+ final boolean fillAfter = mFillAfter;
+ final boolean fillBefore = mFillBefore;
+ final int repeatMode = mRepeatMode;
+ final Interpolator interpolator = mInterpolator;
+ final long startOffset = mStartOffset;
+
+
+ long[] storedOffsets = mStoredOffsets;
+ if (startOffsetSet) {
+ if (storedOffsets == null || storedOffsets.length != count) {
+ storedOffsets = mStoredOffsets = new long[count];
+ }
+ } else if (storedOffsets != null) {
+ storedOffsets = mStoredOffsets = null;
+ }
+
+ for (int i = 0; i < count; i++) {
+ Animation a = children.get(i);
+ if (durationSet) {
+ a.setDuration(duration);
+ }
+ if (fillAfterSet) {
+ a.setFillAfter(fillAfter);
+ }
+ if (fillBeforeSet) {
+ a.setFillBefore(fillBefore);
+ }
+ if (repeatModeSet) {
+ a.setRepeatMode(repeatMode);
+ }
+ if (shareInterpolator) {
+ a.setInterpolator(interpolator);
+ }
+ if (startOffsetSet) {
+ long offset = a.getStartOffset();
+ a.setStartOffset(offset + startOffset);
+ storedOffsets[i] = offset;
+ }
+ a.initialize(width, height, parentWidth, parentHeight);
+ }
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ restoreChildrenStartOffset();
+ }
+
+ /**
+ * @hide
+ */
+ void restoreChildrenStartOffset() {
+ final long[] offsets = mStoredOffsets;
+ if (offsets == null) return;
+
+ final ArrayList<Animation> children = mAnimations;
+ final int count = children.size();
+
+ for (int i = 0; i < count; i++) {
+ children.get(i).setStartOffset(offsets[i]);
+ }
+ }
+
+ /**
+ * @return All the child animations in this AnimationSet. Note that
+ * this may include other AnimationSets, which are not expanded.
+ */
+ public List<Animation> getAnimations() {
+ return mAnimations;
+ }
+
+ @Override
+ public boolean willChangeTransformationMatrix() {
+ return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
+ }
+
+ @Override
+ public boolean willChangeBounds() {
+ return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
+ }
+}
diff --git a/android/view/animation/AnimationUtils.java b/android/view/animation/AnimationUtils.java
new file mode 100644
index 00000000..f5c36139
--- /dev/null
+++ b/android/view/animation/AnimationUtils.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2007 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.animation;
+
+import android.annotation.AnimRes;
+import android.annotation.InterpolatorRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.content.res.XmlResourceParser;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Defines common utilities for working with animations.
+ *
+ */
+public class AnimationUtils {
+
+ /**
+ * These flags are used when parsing AnimatorSet objects
+ */
+ private static final int TOGETHER = 0;
+ private static final int SEQUENTIALLY = 1;
+
+ private static class AnimationState {
+ boolean animationClockLocked;
+ long currentVsyncTimeMillis;
+ long lastReportedTimeMillis;
+ };
+
+ private static ThreadLocal<AnimationState> sAnimationState
+ = new ThreadLocal<AnimationState>() {
+ @Override
+ protected AnimationState initialValue() {
+ return new AnimationState();
+ }
+ };
+
+ /** @hide */
+ public static void lockAnimationClock(long vsyncMillis) {
+ AnimationState state = sAnimationState.get();
+ state.animationClockLocked = true;
+ state.currentVsyncTimeMillis = vsyncMillis;
+ }
+
+ /** @hide */
+ public static void unlockAnimationClock() {
+ sAnimationState.get().animationClockLocked = false;
+ }
+
+ /**
+ * Returns the current animation time in milliseconds. This time should be used when invoking
+ * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more
+ * information about the different available clocks. The clock used by this method is
+ * <em>not</em> the "wall" clock (it is not {@link System#currentTimeMillis}).
+ *
+ * @return the current animation time in milliseconds
+ *
+ * @see android.os.SystemClock
+ */
+ public static long currentAnimationTimeMillis() {
+ AnimationState state = sAnimationState.get();
+ if (state.animationClockLocked) {
+ // It's important that time never rewinds
+ return Math.max(state.currentVsyncTimeMillis,
+ state.lastReportedTimeMillis);
+ }
+ state.lastReportedTimeMillis = SystemClock.uptimeMillis();
+ return state.lastReportedTimeMillis;
+ }
+
+ /**
+ * Loads an {@link Animation} object from a resource
+ *
+ * @param context Application context used to access resources
+ * @param id The resource id of the animation to load
+ * @return The animation object reference by the specified id
+ * @throws NotFoundException when the animation cannot be loaded
+ */
+ public static Animation loadAnimation(Context context, @AnimRes int id)
+ throws NotFoundException {
+
+ XmlResourceParser parser = null;
+ try {
+ parser = context.getResources().getAnimation(id);
+ return createAnimationFromXml(context, parser);
+ } catch (XmlPullParserException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ private static Animation createAnimationFromXml(Context c, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+
+ return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));
+ }
+
+ private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
+ AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
+
+ Animation anim = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (name.equals("set")) {
+ anim = new AnimationSet(c, attrs);
+ createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
+ } else if (name.equals("alpha")) {
+ anim = new AlphaAnimation(c, attrs);
+ } else if (name.equals("scale")) {
+ anim = new ScaleAnimation(c, attrs);
+ } else if (name.equals("rotate")) {
+ anim = new RotateAnimation(c, attrs);
+ } else if (name.equals("translate")) {
+ anim = new TranslateAnimation(c, attrs);
+ } else {
+ throw new RuntimeException("Unknown animation name: " + parser.getName());
+ }
+
+ if (parent != null) {
+ parent.addAnimation(anim);
+ }
+ }
+
+ return anim;
+
+ }
+
+ /**
+ * Loads a {@link LayoutAnimationController} object from a resource
+ *
+ * @param context Application context used to access resources
+ * @param id The resource id of the animation to load
+ * @return The animation object reference by the specified id
+ * @throws NotFoundException when the layout animation controller cannot be loaded
+ */
+ public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id)
+ throws NotFoundException {
+
+ XmlResourceParser parser = null;
+ try {
+ parser = context.getResources().getAnimation(id);
+ return createLayoutAnimationFromXml(context, parser);
+ } catch (XmlPullParserException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
+ XmlPullParser parser) throws XmlPullParserException, IOException {
+
+ return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));
+ }
+
+ private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
+ XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+
+ LayoutAnimationController controller = null;
+
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if ("layoutAnimation".equals(name)) {
+ controller = new LayoutAnimationController(c, attrs);
+ } else if ("gridLayoutAnimation".equals(name)) {
+ controller = new GridLayoutAnimationController(c, attrs);
+ } else {
+ throw new RuntimeException("Unknown layout animation name: " + name);
+ }
+ }
+
+ return controller;
+ }
+
+ /**
+ * Make an animation for objects becoming visible. Uses a slide and fade
+ * effect.
+ *
+ * @param c Context for loading resources
+ * @param fromLeft is the object to be animated coming from the left
+ * @return The new animation
+ */
+ public static Animation makeInAnimation(Context c, boolean fromLeft) {
+ Animation a;
+ if (fromLeft) {
+ a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_left);
+ } else {
+ a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_right);
+ }
+
+ a.setInterpolator(new DecelerateInterpolator());
+ a.setStartTime(currentAnimationTimeMillis());
+ return a;
+ }
+
+ /**
+ * Make an animation for objects becoming invisible. Uses a slide and fade
+ * effect.
+ *
+ * @param c Context for loading resources
+ * @param toRight is the object to be animated exiting to the right
+ * @return The new animation
+ */
+ public static Animation makeOutAnimation(Context c, boolean toRight) {
+ Animation a;
+ if (toRight) {
+ a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_right);
+ } else {
+ a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left);
+ }
+
+ a.setInterpolator(new AccelerateInterpolator());
+ a.setStartTime(currentAnimationTimeMillis());
+ return a;
+ }
+
+
+ /**
+ * Make an animation for objects becoming visible. Uses a slide up and fade
+ * effect.
+ *
+ * @param c Context for loading resources
+ * @return The new animation
+ */
+ public static Animation makeInChildBottomAnimation(Context c) {
+ Animation a;
+ a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_child_bottom);
+ a.setInterpolator(new AccelerateInterpolator());
+ a.setStartTime(currentAnimationTimeMillis());
+ return a;
+ }
+
+ /**
+ * Loads an {@link Interpolator} object from a resource
+ *
+ * @param context Application context used to access resources
+ * @param id The resource id of the animation to load
+ * @return The animation object reference by the specified id
+ * @throws NotFoundException
+ */
+ public static Interpolator loadInterpolator(Context context, @AnimRes @InterpolatorRes int id)
+ throws NotFoundException {
+ XmlResourceParser parser = null;
+ try {
+ parser = context.getResources().getAnimation(id);
+ return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser);
+ } catch (XmlPullParserException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) parser.close();
+ }
+
+ }
+
+ /**
+ * Loads an {@link Interpolator} object from a resource
+ *
+ * @param res The resources
+ * @param id The resource id of the animation to load
+ * @return The interpolator object reference by the specified id
+ * @throws NotFoundException
+ * @hide
+ */
+ public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException {
+ XmlResourceParser parser = null;
+ try {
+ parser = res.getAnimation(id);
+ return createInterpolatorFromXml(res, theme, parser);
+ } catch (XmlPullParserException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null)
+ parser.close();
+ }
+
+ }
+
+ private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+
+ BaseInterpolator interpolator = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ String name = parser.getName();
+
+ if (name.equals("linearInterpolator")) {
+ interpolator = new LinearInterpolator();
+ } else if (name.equals("accelerateInterpolator")) {
+ interpolator = new AccelerateInterpolator(res, theme, attrs);
+ } else if (name.equals("decelerateInterpolator")) {
+ interpolator = new DecelerateInterpolator(res, theme, attrs);
+ } else if (name.equals("accelerateDecelerateInterpolator")) {
+ interpolator = new AccelerateDecelerateInterpolator();
+ } else if (name.equals("cycleInterpolator")) {
+ interpolator = new CycleInterpolator(res, theme, attrs);
+ } else if (name.equals("anticipateInterpolator")) {
+ interpolator = new AnticipateInterpolator(res, theme, attrs);
+ } else if (name.equals("overshootInterpolator")) {
+ interpolator = new OvershootInterpolator(res, theme, attrs);
+ } else if (name.equals("anticipateOvershootInterpolator")) {
+ interpolator = new AnticipateOvershootInterpolator(res, theme, attrs);
+ } else if (name.equals("bounceInterpolator")) {
+ interpolator = new BounceInterpolator();
+ } else if (name.equals("pathInterpolator")) {
+ interpolator = new PathInterpolator(res, theme, attrs);
+ } else {
+ throw new RuntimeException("Unknown interpolator name: " + parser.getName());
+ }
+ }
+ return interpolator;
+ }
+}
diff --git a/android/view/animation/AnticipateInterpolator.java b/android/view/animation/AnticipateInterpolator.java
new file mode 100644
index 00000000..7a837c37
--- /dev/null
+++ b/android/view/animation/AnticipateInterpolator.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 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.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the change starts backward then flings forward.
+ */
+@HasNativeInterpolator
+public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+ private final float mTension;
+
+ public AnticipateInterpolator() {
+ mTension = 2.0f;
+ }
+
+ /**
+ * @param tension Amount of anticipation. When tension equals 0.0f, there is
+ * no anticipation and the interpolator becomes a simple
+ * acceleration interpolator.
+ */
+ public AnticipateInterpolator(float tension) {
+ mTension = tension;
+ }
+
+ public AnticipateInterpolator(Context context, AttributeSet attrs) {
+ this(context.getResources(), context.getTheme(), attrs);
+ }
+
+ /** @hide */
+ public AnticipateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.AnticipateInterpolator, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.AnticipateInterpolator);
+ }
+
+ mTension = a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
+ a.recycle();
+ }
+
+ public float getInterpolation(float t) {
+ // a(t) = t * t * ((tension + 1) * t - tension)
+ return t * t * ((mTension + 1) * t - mTension);
+ }
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAnticipateInterpolator(mTension);
+ }
+}
diff --git a/android/view/animation/AnticipateOvershootInterpolator.java b/android/view/animation/AnticipateOvershootInterpolator.java
new file mode 100644
index 00000000..9a75134a
--- /dev/null
+++ b/android/view/animation/AnticipateOvershootInterpolator.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2009 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.animation;
+
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_extraTension;
+import static com.android.internal.R.styleable.AnticipateOvershootInterpolator_tension;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the change starts backward then flings forward and overshoots
+ * the target value and finally goes back to the final value.
+ */
+@HasNativeInterpolator
+public class AnticipateOvershootInterpolator extends BaseInterpolator
+ implements NativeInterpolatorFactory {
+ private final float mTension;
+
+ public AnticipateOvershootInterpolator() {
+ mTension = 2.0f * 1.5f;
+ }
+
+ /**
+ * @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
+ * there is no anticipation/overshoot and the interpolator becomes
+ * a simple acceleration/deceleration interpolator.
+ */
+ public AnticipateOvershootInterpolator(float tension) {
+ mTension = tension * 1.5f;
+ }
+
+ /**
+ * @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
+ * there is no anticipation/overshoot and the interpolator becomes
+ * a simple acceleration/deceleration interpolator.
+ * @param extraTension Amount by which to multiply the tension. For instance,
+ * to get the same overshoot as an OvershootInterpolator with
+ * a tension of 2.0f, you would use an extraTension of 1.5f.
+ */
+ public AnticipateOvershootInterpolator(float tension, float extraTension) {
+ mTension = tension * extraTension;
+ }
+
+ public AnticipateOvershootInterpolator(Context context, AttributeSet attrs) {
+ this(context.getResources(), context.getTheme(), attrs);
+ }
+
+ /** @hide */
+ public AnticipateOvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, AnticipateOvershootInterpolator, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, AnticipateOvershootInterpolator);
+ }
+
+ mTension = a.getFloat(AnticipateOvershootInterpolator_tension, 2.0f) *
+ a.getFloat(AnticipateOvershootInterpolator_extraTension, 1.5f);
+ setChangingConfiguration(a.getChangingConfigurations());
+ a.recycle();
+ }
+
+ private static float a(float t, float s) {
+ return t * t * ((s + 1) * t - s);
+ }
+
+ private static float o(float t, float s) {
+ return t * t * ((s + 1) * t + s);
+ }
+
+ public float getInterpolation(float t) {
+ // a(t, s) = t * t * ((s + 1) * t - s)
+ // o(t, s) = t * t * ((s + 1) * t + s)
+ // f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
+ // f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
+ if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
+ else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
+ }
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createAnticipateOvershootInterpolator(mTension);
+ }
+}
diff --git a/android/view/animation/BaseInterpolator.java b/android/view/animation/BaseInterpolator.java
new file mode 100644
index 00000000..a78fa1ea
--- /dev/null
+++ b/android/view/animation/BaseInterpolator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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.animation;
+
+import android.content.pm.ActivityInfo.Config;
+
+/**
+ * An abstract class which is extended by default interpolators.
+ */
+abstract public class BaseInterpolator implements Interpolator {
+ private @Config int mChangingConfiguration;
+ /**
+ * @hide
+ */
+ public @Config int getChangingConfiguration() {
+ return mChangingConfiguration;
+ }
+
+ /**
+ * @hide
+ */
+ void setChangingConfiguration(@Config int changingConfiguration) {
+ mChangingConfiguration = changingConfiguration;
+ }
+}
diff --git a/android/view/animation/BounceInterpolator.java b/android/view/animation/BounceInterpolator.java
new file mode 100644
index 00000000..909eaa4c
--- /dev/null
+++ b/android/view/animation/BounceInterpolator.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.animation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the change bounces at the end.
+ */
+@HasNativeInterpolator
+public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+ public BounceInterpolator() {
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public BounceInterpolator(Context context, AttributeSet attrs) {
+ }
+
+ private static float bounce(float t) {
+ return t * t * 8.0f;
+ }
+
+ public float getInterpolation(float t) {
+ // _b(t) = t * t * 8
+ // bs(t) = _b(t) for t < 0.3535
+ // bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
+ // bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
+ // bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
+ // b(t) = bs(t * 1.1226)
+ t *= 1.1226f;
+ if (t < 0.3535f) return bounce(t);
+ else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
+ else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
+ else return bounce(t - 1.0435f) + 0.95f;
+ }
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createBounceInterpolator();
+ }
+} \ No newline at end of file
diff --git a/android/view/animation/ClipRectAnimation.java b/android/view/animation/ClipRectAnimation.java
new file mode 100644
index 00000000..e194927e
--- /dev/null
+++ b/android/view/animation/ClipRectAnimation.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 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.animation;
+
+import android.graphics.Rect;
+
+/**
+ * An animation that controls the clip of an object. See the
+ * {@link android.view.animation full package} description for details and
+ * sample code.
+ *
+ * @hide
+ */
+public class ClipRectAnimation extends Animation {
+ protected Rect mFromRect = new Rect();
+ protected Rect mToRect = new Rect();
+
+ /**
+ * Constructor to use when building a ClipRectAnimation from code
+ *
+ * @param fromClip the clip rect to animate from
+ * @param toClip the clip rect to animate to
+ */
+ public ClipRectAnimation(Rect fromClip, Rect toClip) {
+ if (fromClip == null || toClip == null) {
+ throw new RuntimeException("Expected non-null animation clip rects");
+ }
+ mFromRect.set(fromClip);
+ mToRect.set(toClip);
+ }
+
+ /**
+ * Constructor to use when building a ClipRectAnimation from code
+ */
+ public ClipRectAnimation(int fromL, int fromT, int fromR, int fromB,
+ int toL, int toT, int toR, int toB) {
+ mFromRect.set(fromL, fromT, fromR, fromB);
+ mToRect.set(toL, toT, toR, toB);
+ }
+
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ int l = mFromRect.left + (int) ((mToRect.left - mFromRect.left) * it);
+ int t = mFromRect.top + (int) ((mToRect.top - mFromRect.top) * it);
+ int r = mFromRect.right + (int) ((mToRect.right - mFromRect.right) * it);
+ int b = mFromRect.bottom + (int) ((mToRect.bottom - mFromRect.bottom) * it);
+ tr.setClipRect(l, t, r, b);
+ }
+
+ @Override
+ public boolean willChangeTransformationMatrix() {
+ return false;
+ }
+}
diff --git a/android/view/animation/CycleInterpolator.java b/android/view/animation/CycleInterpolator.java
new file mode 100644
index 00000000..72d64a16
--- /dev/null
+++ b/android/view/animation/CycleInterpolator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 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.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * Repeats the animation for a specified number of cycles. The
+ * rate of change follows a sinusoidal pattern.
+ *
+ */
+@HasNativeInterpolator
+public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+ public CycleInterpolator(float cycles) {
+ mCycles = cycles;
+ }
+
+ public CycleInterpolator(Context context, AttributeSet attrs) {
+ this(context.getResources(), context.getTheme(), attrs);
+ }
+
+ /** @hide */
+ public CycleInterpolator(Resources resources, Theme theme, AttributeSet attrs) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.CycleInterpolator, 0, 0);
+ } else {
+ a = resources.obtainAttributes(attrs, R.styleable.CycleInterpolator);
+ }
+
+ mCycles = a.getFloat(R.styleable.CycleInterpolator_cycles, 1.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
+ a.recycle();
+ }
+
+ public float getInterpolation(float input) {
+ return (float)(Math.sin(2 * mCycles * Math.PI * input));
+ }
+
+ private float mCycles;
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createCycleInterpolator(mCycles);
+ }
+}
diff --git a/android/view/animation/DecelerateInterpolator.java b/android/view/animation/DecelerateInterpolator.java
new file mode 100644
index 00000000..f89743c1
--- /dev/null
+++ b/android/view/animation/DecelerateInterpolator.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 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.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the rate of change starts out quickly and
+ * and then decelerates.
+ *
+ */
+@HasNativeInterpolator
+public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+ public DecelerateInterpolator() {
+ }
+
+ /**
+ * Constructor
+ *
+ * @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
+ * an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the
+ * ease-out effect (i.e., it starts even faster and ends evens slower)
+ */
+ public DecelerateInterpolator(float factor) {
+ mFactor = factor;
+ }
+
+ public DecelerateInterpolator(Context context, AttributeSet attrs) {
+ this(context.getResources(), context.getTheme(), attrs);
+ }
+
+ /** @hide */
+ public DecelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.DecelerateInterpolator, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.DecelerateInterpolator);
+ }
+
+ mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
+ a.recycle();
+ }
+
+ public float getInterpolation(float input) {
+ float result;
+ if (mFactor == 1.0f) {
+ result = (float)(1.0f - (1.0f - input) * (1.0f - input));
+ } else {
+ result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
+ }
+ return result;
+ }
+
+ private float mFactor = 1.0f;
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createDecelerateInterpolator(mFactor);
+ }
+}
diff --git a/android/view/animation/GridLayoutAnimationController.java b/android/view/animation/GridLayoutAnimationController.java
new file mode 100644
index 00000000..0f189ae9
--- /dev/null
+++ b/android/view/animation/GridLayoutAnimationController.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2007 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Random;
+
+/**
+ * A layout animation controller is used to animated a grid layout's children.
+ *
+ * While {@link LayoutAnimationController} relies only on the index of the child
+ * in the view group to compute the animation delay, this class uses both the
+ * X and Y coordinates of the child within a grid.
+ *
+ * In addition, the animation direction can be controlled. The default direction
+ * is <code>DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM</code>. You can
+ * also set the animation priority to columns or rows. The default priority is
+ * none.
+ *
+ * Information used to compute the animation delay of each child are stored
+ * in an instance of
+ * {@link android.view.animation.GridLayoutAnimationController.AnimationParameters},
+ * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view.
+ *
+ * @see LayoutAnimationController
+ * @see android.widget.GridView
+ *
+ * @attr ref android.R.styleable#GridLayoutAnimation_columnDelay
+ * @attr ref android.R.styleable#GridLayoutAnimation_rowDelay
+ * @attr ref android.R.styleable#GridLayoutAnimation_direction
+ * @attr ref android.R.styleable#GridLayoutAnimation_directionPriority
+ */
+public class GridLayoutAnimationController extends LayoutAnimationController {
+ /**
+ * Animates the children starting from the left of the grid to the right.
+ */
+ public static final int DIRECTION_LEFT_TO_RIGHT = 0x0;
+
+ /**
+ * Animates the children starting from the right of the grid to the left.
+ */
+ public static final int DIRECTION_RIGHT_TO_LEFT = 0x1;
+
+ /**
+ * Animates the children starting from the top of the grid to the bottom.
+ */
+ public static final int DIRECTION_TOP_TO_BOTTOM = 0x0;
+
+ /**
+ * Animates the children starting from the bottom of the grid to the top.
+ */
+ public static final int DIRECTION_BOTTOM_TO_TOP = 0x2;
+
+ /**
+ * Bitmask used to retrieve the horizontal component of the direction.
+ */
+ public static final int DIRECTION_HORIZONTAL_MASK = 0x1;
+
+ /**
+ * Bitmask used to retrieve the vertical component of the direction.
+ */
+ public static final int DIRECTION_VERTICAL_MASK = 0x2;
+
+ /**
+ * Rows and columns are animated at the same time.
+ */
+ public static final int PRIORITY_NONE = 0;
+
+ /**
+ * Columns are animated first.
+ */
+ public static final int PRIORITY_COLUMN = 1;
+
+ /**
+ * Rows are animated first.
+ */
+ public static final int PRIORITY_ROW = 2;
+
+ private float mColumnDelay;
+ private float mRowDelay;
+
+ private int mDirection;
+ private int mDirectionPriority;
+
+ /**
+ * Creates a new grid layout animation controller from external resources.
+ *
+ * @param context the Context the view group is running in, through which
+ * it can access the resources
+ * @param attrs the attributes of the XML tag that is inflating the
+ * layout animation controller
+ */
+ public GridLayoutAnimationController(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.GridLayoutAnimation);
+
+ Animation.Description d = Animation.Description.parseValue(
+ a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay));
+ mColumnDelay = d.value;
+ d = Animation.Description.parseValue(
+ a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay));
+ mRowDelay = d.value;
+ //noinspection PointlessBitwiseExpression
+ mDirection = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_direction,
+ DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM);
+ mDirectionPriority = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_directionPriority,
+ PRIORITY_NONE);
+
+ a.recycle();
+ }
+
+ /**
+ * Creates a new layout animation controller with a delay of 50%
+ * for both rows and columns and the specified animation.
+ *
+ * @param animation the animation to use on each child of the view group
+ */
+ public GridLayoutAnimationController(Animation animation) {
+ this(animation, 0.5f, 0.5f);
+ }
+
+ /**
+ * Creates a new layout animation controller with the specified delays
+ * and the specified animation.
+ *
+ * @param animation the animation to use on each child of the view group
+ * @param columnDelay the delay by which each column animation must be offset
+ * @param rowDelay the delay by which each row animation must be offset
+ */
+ public GridLayoutAnimationController(Animation animation, float columnDelay, float rowDelay) {
+ super(animation);
+ mColumnDelay = columnDelay;
+ mRowDelay = rowDelay;
+ }
+
+ /**
+ * Returns the delay by which the children's animation are offset from one
+ * column to the other. The delay is expressed as a fraction of the
+ * animation duration.
+ *
+ * @return a fraction of the animation duration
+ *
+ * @see #setColumnDelay(float)
+ * @see #getRowDelay()
+ * @see #setRowDelay(float)
+ */
+ public float getColumnDelay() {
+ return mColumnDelay;
+ }
+
+ /**
+ * Sets the delay, as a fraction of the animation duration, by which the
+ * children's animations are offset from one column to the other.
+ *
+ * @param columnDelay a fraction of the animation duration
+ *
+ * @see #getColumnDelay()
+ * @see #getRowDelay()
+ * @see #setRowDelay(float)
+ */
+ public void setColumnDelay(float columnDelay) {
+ mColumnDelay = columnDelay;
+ }
+
+ /**
+ * Returns the delay by which the children's animation are offset from one
+ * row to the other. The delay is expressed as a fraction of the
+ * animation duration.
+ *
+ * @return a fraction of the animation duration
+ *
+ * @see #setRowDelay(float)
+ * @see #getColumnDelay()
+ * @see #setColumnDelay(float)
+ */
+ public float getRowDelay() {
+ return mRowDelay;
+ }
+
+ /**
+ * Sets the delay, as a fraction of the animation duration, by which the
+ * children's animations are offset from one row to the other.
+ *
+ * @param rowDelay a fraction of the animation duration
+ *
+ * @see #getRowDelay()
+ * @see #getColumnDelay()
+ * @see #setColumnDelay(float)
+ */
+ public void setRowDelay(float rowDelay) {
+ mRowDelay = rowDelay;
+ }
+
+ /**
+ * Returns the direction of the animation. {@link #DIRECTION_HORIZONTAL_MASK}
+ * and {@link #DIRECTION_VERTICAL_MASK} can be used to retrieve the
+ * horizontal and vertical components of the direction.
+ *
+ * @return the direction of the animation
+ *
+ * @see #setDirection(int)
+ * @see #DIRECTION_BOTTOM_TO_TOP
+ * @see #DIRECTION_TOP_TO_BOTTOM
+ * @see #DIRECTION_LEFT_TO_RIGHT
+ * @see #DIRECTION_RIGHT_TO_LEFT
+ * @see #DIRECTION_HORIZONTAL_MASK
+ * @see #DIRECTION_VERTICAL_MASK
+ */
+ public int getDirection() {
+ return mDirection;
+ }
+
+ /**
+ * Sets the direction of the animation. The direction is expressed as an
+ * integer containing a horizontal and vertical component. For instance,
+ * <code>DIRECTION_BOTTOM_TO_TOP | DIRECTION_RIGHT_TO_LEFT</code>.
+ *
+ * @param direction the direction of the animation
+ *
+ * @see #getDirection()
+ * @see #DIRECTION_BOTTOM_TO_TOP
+ * @see #DIRECTION_TOP_TO_BOTTOM
+ * @see #DIRECTION_LEFT_TO_RIGHT
+ * @see #DIRECTION_RIGHT_TO_LEFT
+ * @see #DIRECTION_HORIZONTAL_MASK
+ * @see #DIRECTION_VERTICAL_MASK
+ */
+ public void setDirection(int direction) {
+ mDirection = direction;
+ }
+
+ /**
+ * Returns the direction priority for the animation. The priority can
+ * be either {@link #PRIORITY_NONE}, {@link #PRIORITY_COLUMN} or
+ * {@link #PRIORITY_ROW}.
+ *
+ * @return the priority of the animation direction
+ *
+ * @see #setDirectionPriority(int)
+ * @see #PRIORITY_COLUMN
+ * @see #PRIORITY_NONE
+ * @see #PRIORITY_ROW
+ */
+ public int getDirectionPriority() {
+ return mDirectionPriority;
+ }
+
+ /**
+ * Specifies the direction priority of the animation. For instance,
+ * {@link #PRIORITY_COLUMN} will give priority to columns: the animation
+ * will first play on the column, then on the rows.Z
+ *
+ * @param directionPriority the direction priority of the animation
+ *
+ * @see #getDirectionPriority()
+ * @see #PRIORITY_COLUMN
+ * @see #PRIORITY_NONE
+ * @see #PRIORITY_ROW
+ */
+ public void setDirectionPriority(int directionPriority) {
+ mDirectionPriority = directionPriority;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean willOverlap() {
+ return mColumnDelay < 1.0f || mRowDelay < 1.0f;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected long getDelayForView(View view) {
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ AnimationParameters params = (AnimationParameters) lp.layoutAnimationParameters;
+
+ if (params == null) {
+ return 0;
+ }
+
+ final int column = getTransformedColumnIndex(params);
+ final int row = getTransformedRowIndex(params);
+
+ final int rowsCount = params.rowsCount;
+ final int columnsCount = params.columnsCount;
+
+ final long duration = mAnimation.getDuration();
+ final float columnDelay = mColumnDelay * duration;
+ final float rowDelay = mRowDelay * duration;
+
+ float totalDelay;
+ long viewDelay;
+
+ if (mInterpolator == null) {
+ mInterpolator = new LinearInterpolator();
+ }
+
+ switch (mDirectionPriority) {
+ case PRIORITY_COLUMN:
+ viewDelay = (long) (row * rowDelay + column * rowsCount * rowDelay);
+ totalDelay = rowsCount * rowDelay + columnsCount * rowsCount * rowDelay;
+ break;
+ case PRIORITY_ROW:
+ viewDelay = (long) (column * columnDelay + row * columnsCount * columnDelay);
+ totalDelay = columnsCount * columnDelay + rowsCount * columnsCount * columnDelay;
+ break;
+ case PRIORITY_NONE:
+ default:
+ viewDelay = (long) (column * columnDelay + row * rowDelay);
+ totalDelay = columnsCount * columnDelay + rowsCount * rowDelay;
+ break;
+ }
+
+ float normalizedDelay = viewDelay / totalDelay;
+ normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);
+
+ return (long) (normalizedDelay * totalDelay);
+ }
+
+ private int getTransformedColumnIndex(AnimationParameters params) {
+ int index;
+ switch (getOrder()) {
+ case ORDER_REVERSE:
+ index = params.columnsCount - 1 - params.column;
+ break;
+ case ORDER_RANDOM:
+ if (mRandomizer == null) {
+ mRandomizer = new Random();
+ }
+ index = (int) (params.columnsCount * mRandomizer.nextFloat());
+ break;
+ case ORDER_NORMAL:
+ default:
+ index = params.column;
+ break;
+ }
+
+ int direction = mDirection & DIRECTION_HORIZONTAL_MASK;
+ if (direction == DIRECTION_RIGHT_TO_LEFT) {
+ index = params.columnsCount - 1 - index;
+ }
+
+ return index;
+ }
+
+ private int getTransformedRowIndex(AnimationParameters params) {
+ int index;
+ switch (getOrder()) {
+ case ORDER_REVERSE:
+ index = params.rowsCount - 1 - params.row;
+ break;
+ case ORDER_RANDOM:
+ if (mRandomizer == null) {
+ mRandomizer = new Random();
+ }
+ index = (int) (params.rowsCount * mRandomizer.nextFloat());
+ break;
+ case ORDER_NORMAL:
+ default:
+ index = params.row;
+ break;
+ }
+
+ int direction = mDirection & DIRECTION_VERTICAL_MASK;
+ if (direction == DIRECTION_BOTTOM_TO_TOP) {
+ index = params.rowsCount - 1 - index;
+ }
+
+ return index;
+ }
+
+ /**
+ * The set of parameters that has to be attached to each view contained in
+ * the view group animated by the grid layout animation controller. These
+ * parameters are used to compute the start time of each individual view's
+ * animation.
+ */
+ public static class AnimationParameters extends
+ LayoutAnimationController.AnimationParameters {
+ /**
+ * The view group's column to which the view belongs.
+ */
+ public int column;
+
+ /**
+ * The view group's row to which the view belongs.
+ */
+ public int row;
+
+ /**
+ * The number of columns in the view's enclosing grid layout.
+ */
+ public int columnsCount;
+
+ /**
+ * The number of rows in the view's enclosing grid layout.
+ */
+ public int rowsCount;
+ }
+}
diff --git a/android/view/animation/Interpolator.java b/android/view/animation/Interpolator.java
new file mode 100644
index 00000000..5d0fe7ef
--- /dev/null
+++ b/android/view/animation/Interpolator.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * An interpolator defines the rate of change of an animation. This allows
+ * the basic animation effects (alpha, scale, translate, rotate) to be
+ * accelerated, decelerated, repeated, etc.
+ */
+public interface Interpolator extends TimeInterpolator {
+ // A new interface, TimeInterpolator, was introduced for the new android.animation
+ // package. This older Interpolator interface extends TimeInterpolator so that users of
+ // the new Animator-based animations can use either the old Interpolator implementations or
+ // new classes that implement TimeInterpolator directly.
+}
diff --git a/android/view/animation/LayoutAnimationController.java b/android/view/animation/LayoutAnimationController.java
new file mode 100644
index 00000000..7fa49c1a
--- /dev/null
+++ b/android/view/animation/LayoutAnimationController.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2007 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.animation;
+
+import android.annotation.AnimRes;
+import android.annotation.InterpolatorRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Random;
+
+/**
+ * A layout animation controller is used to animated a layout's, or a view
+ * group's, children. Each child uses the same animation but for every one of
+ * them, the animation starts at a different time. A layout animation controller
+ * is used by {@link android.view.ViewGroup} to compute the delay by which each
+ * child's animation start must be offset. The delay is computed by using
+ * characteristics of each child, like its index in the view group.
+ *
+ * This standard implementation computes the delay by multiplying a fixed
+ * amount of miliseconds by the index of the child in its parent view group.
+ * Subclasses are supposed to override
+ * {@link #getDelayForView(android.view.View)} to implement a different way
+ * of computing the delay. For instance, a
+ * {@link android.view.animation.GridLayoutAnimationController} will compute the
+ * delay based on the column and row indices of the child in its parent view
+ * group.
+ *
+ * Information used to compute the animation delay of each child are stored
+ * in an instance of
+ * {@link android.view.animation.LayoutAnimationController.AnimationParameters},
+ * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view.
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_delay
+ * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+ * @attr ref android.R.styleable#LayoutAnimation_interpolator
+ * @attr ref android.R.styleable#LayoutAnimation_animation
+ */
+public class LayoutAnimationController {
+ /**
+ * Distributes the animation delays in the order in which view were added
+ * to their view group.
+ */
+ public static final int ORDER_NORMAL = 0;
+
+ /**
+ * Distributes the animation delays in the reverse order in which view were
+ * added to their view group.
+ */
+ public static final int ORDER_REVERSE = 1;
+
+ /**
+ * Randomly distributes the animation delays.
+ */
+ public static final int ORDER_RANDOM = 2;
+
+ /**
+ * The animation applied on each child of the view group on which this
+ * layout animation controller is set.
+ */
+ protected Animation mAnimation;
+
+ /**
+ * The randomizer used when the order is set to random. Subclasses should
+ * use this object to avoid creating their own.
+ */
+ protected Random mRandomizer;
+
+ /**
+ * The interpolator used to interpolate the delays.
+ */
+ protected Interpolator mInterpolator;
+
+ private float mDelay;
+ private int mOrder;
+
+ private long mDuration;
+ private long mMaxDelay;
+
+ /**
+ * Creates a new layout animation controller from external resources.
+ *
+ * @param context the Context the view group is running in, through which
+ * it can access the resources
+ * @param attrs the attributes of the XML tag that is inflating the
+ * layout animation controller
+ */
+ public LayoutAnimationController(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation);
+
+ Animation.Description d = Animation.Description.parseValue(
+ a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay));
+ mDelay = d.value;
+
+ mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL);
+
+ int resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_animation, 0);
+ if (resource > 0) {
+ setAnimation(context, resource);
+ }
+
+ resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_interpolator, 0);
+ if (resource > 0) {
+ setInterpolator(context, resource);
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Creates a new layout animation controller with a delay of 50%
+ * and the specified animation.
+ *
+ * @param animation the animation to use on each child of the view group
+ */
+ public LayoutAnimationController(Animation animation) {
+ this(animation, 0.5f);
+ }
+
+ /**
+ * Creates a new layout animation controller with the specified delay
+ * and the specified animation.
+ *
+ * @param animation the animation to use on each child of the view group
+ * @param delay the delay by which each child's animation must be offset
+ */
+ public LayoutAnimationController(Animation animation, float delay) {
+ mDelay = delay;
+ setAnimation(animation);
+ }
+
+ /**
+ * Returns the order used to compute the delay of each child's animation.
+ *
+ * @return one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
+ * {@link #ORDER_RANDOM}
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+ */
+ public int getOrder() {
+ return mOrder;
+ }
+
+ /**
+ * Sets the order used to compute the delay of each child's animation.
+ *
+ * @param order one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or
+ * {@link #ORDER_RANDOM}
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_animationOrder
+ */
+ public void setOrder(int order) {
+ mOrder = order;
+ }
+
+ /**
+ * Sets the animation to be run on each child of the view group on which
+ * this layout animation controller is .
+ *
+ * @param context the context from which the animation must be inflated
+ * @param resourceID the resource identifier of the animation
+ *
+ * @see #setAnimation(Animation)
+ * @see #getAnimation()
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_animation
+ */
+ public void setAnimation(Context context, @AnimRes int resourceID) {
+ setAnimation(AnimationUtils.loadAnimation(context, resourceID));
+ }
+
+ /**
+ * Sets the animation to be run on each child of the view group on which
+ * this layout animation controller is .
+ *
+ * @param animation the animation to run on each child of the view group
+
+ * @see #setAnimation(android.content.Context, int)
+ * @see #getAnimation()
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_animation
+ */
+ public void setAnimation(Animation animation) {
+ mAnimation = animation;
+ mAnimation.setFillBefore(true);
+ }
+
+ /**
+ * Returns the animation applied to each child of the view group on which
+ * this controller is set.
+ *
+ * @return an {@link android.view.animation.Animation} instance
+ *
+ * @see #setAnimation(android.content.Context, int)
+ * @see #setAnimation(Animation)
+ */
+ public Animation getAnimation() {
+ return mAnimation;
+ }
+
+ /**
+ * Sets the interpolator used to interpolate the delays between the
+ * children.
+ *
+ * @param context the context from which the interpolator must be inflated
+ * @param resourceID the resource identifier of the interpolator
+ *
+ * @see #getInterpolator()
+ * @see #setInterpolator(Interpolator)
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_interpolator
+ */
+ public void setInterpolator(Context context, @InterpolatorRes int resourceID) {
+ setInterpolator(AnimationUtils.loadInterpolator(context, resourceID));
+ }
+
+ /**
+ * Sets the interpolator used to interpolate the delays between the
+ * children.
+ *
+ * @param interpolator the interpolator
+ *
+ * @see #getInterpolator()
+ * @see #setInterpolator(Interpolator)
+ *
+ * @attr ref android.R.styleable#LayoutAnimation_interpolator
+ */
+ public void setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Returns the interpolator used to interpolate the delays between the
+ * children.
+ *
+ * @return an {@link android.view.animation.Interpolator}
+ */
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * Returns the delay by which the children's animation are offset. The
+ * delay is expressed as a fraction of the animation duration.
+ *
+ * @return a fraction of the animation duration
+ *
+ * @see #setDelay(float)
+ */
+ public float getDelay() {
+ return mDelay;
+ }
+
+ /**
+ * Sets the delay, as a fraction of the animation duration, by which the
+ * children's animations are offset. The general formula is:
+ *
+ * <pre>
+ * child animation delay = child index * delay * animation duration
+ * </pre>
+ *
+ * @param delay a fraction of the animation duration
+ *
+ * @see #getDelay()
+ */
+ public void setDelay(float delay) {
+ mDelay = delay;
+ }
+
+ /**
+ * Indicates whether two children's animations will overlap. Animations
+ * overlap when the delay is lower than 100% (or 1.0).
+ *
+ * @return true if animations will overlap, false otherwise
+ */
+ public boolean willOverlap() {
+ return mDelay < 1.0f;
+ }
+
+ /**
+ * Starts the animation.
+ */
+ public void start() {
+ mDuration = mAnimation.getDuration();
+ mMaxDelay = Long.MIN_VALUE;
+ mAnimation.setStartTime(-1);
+ }
+
+ /**
+ * Returns the animation to be applied to the specified view. The returned
+ * animation is delayed by an offset computed according to the information
+ * provided by
+ * {@link android.view.animation.LayoutAnimationController.AnimationParameters}.
+ * This method is called by view groups to obtain the animation to set on
+ * a specific child.
+ *
+ * @param view the view to animate
+ * @return an animation delayed by the number of milliseconds returned by
+ * {@link #getDelayForView(android.view.View)}
+ *
+ * @see #getDelay()
+ * @see #setDelay(float)
+ * @see #getDelayForView(android.view.View)
+ */
+ public final Animation getAnimationForView(View view) {
+ final long delay = getDelayForView(view) + mAnimation.getStartOffset();
+ mMaxDelay = Math.max(mMaxDelay, delay);
+
+ try {
+ final Animation animation = mAnimation.clone();
+ animation.setStartOffset(delay);
+ return animation;
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Indicates whether the layout animation is over or not. A layout animation
+ * is considered done when the animation with the longest delay is done.
+ *
+ * @return true if all of the children's animations are over, false otherwise
+ */
+ public boolean isDone() {
+ return AnimationUtils.currentAnimationTimeMillis() >
+ mAnimation.getStartTime() + mMaxDelay + mDuration;
+ }
+
+ /**
+ * Returns the amount of milliseconds by which the specified view's
+ * animation must be delayed or offset. Subclasses should override this
+ * method to return a suitable value.
+ *
+ * This implementation returns <code>child animation delay</code>
+ * milliseconds where:
+ *
+ * <pre>
+ * child animation delay = child index * delay
+ * </pre>
+ *
+ * The index is retrieved from the
+ * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
+ * found in the view's {@link android.view.ViewGroup.LayoutParams}.
+ *
+ * @param view the view for which to obtain the animation's delay
+ * @return a delay in milliseconds
+ *
+ * @see #getAnimationForView(android.view.View)
+ * @see #getDelay()
+ * @see #getTransformedIndex(android.view.animation.LayoutAnimationController.AnimationParameters)
+ * @see android.view.ViewGroup.LayoutParams
+ */
+ protected long getDelayForView(View view) {
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ AnimationParameters params = lp.layoutAnimationParameters;
+
+ if (params == null) {
+ return 0;
+ }
+
+ final float delay = mDelay * mAnimation.getDuration();
+ final long viewDelay = (long) (getTransformedIndex(params) * delay);
+ final float totalDelay = delay * params.count;
+
+ if (mInterpolator == null) {
+ mInterpolator = new LinearInterpolator();
+ }
+
+ float normalizedDelay = viewDelay / totalDelay;
+ normalizedDelay = mInterpolator.getInterpolation(normalizedDelay);
+
+ return (long) (normalizedDelay * totalDelay);
+ }
+
+ /**
+ * Transforms the index stored in
+ * {@link android.view.animation.LayoutAnimationController.AnimationParameters}
+ * by the order returned by {@link #getOrder()}. Subclasses should override
+ * this method to provide additional support for other types of ordering.
+ * This method should be invoked by
+ * {@link #getDelayForView(android.view.View)} prior to any computation.
+ *
+ * @param params the animation parameters containing the index
+ * @return a transformed index
+ */
+ protected int getTransformedIndex(AnimationParameters params) {
+ switch (getOrder()) {
+ case ORDER_REVERSE:
+ return params.count - 1 - params.index;
+ case ORDER_RANDOM:
+ if (mRandomizer == null) {
+ mRandomizer = new Random();
+ }
+ return (int) (params.count * mRandomizer.nextFloat());
+ case ORDER_NORMAL:
+ default:
+ return params.index;
+ }
+ }
+
+ /**
+ * The set of parameters that has to be attached to each view contained in
+ * the view group animated by the layout animation controller. These
+ * parameters are used to compute the start time of each individual view's
+ * animation.
+ */
+ public static class AnimationParameters {
+ /**
+ * The number of children in the view group containing the view to which
+ * these parameters are attached.
+ */
+ public int count;
+
+ /**
+ * The index of the view to which these parameters are attached in its
+ * containing view group.
+ */
+ public int index;
+ }
+}
diff --git a/android/view/animation/LinearInterpolator.java b/android/view/animation/LinearInterpolator.java
new file mode 100644
index 00000000..2a047b48
--- /dev/null
+++ b/android/view/animation/LinearInterpolator.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the rate of change is constant
+ */
+@HasNativeInterpolator
+public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+
+ public LinearInterpolator() {
+ }
+
+ public LinearInterpolator(Context context, AttributeSet attrs) {
+ }
+
+ public float getInterpolation(float input) {
+ return input;
+ }
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createLinearInterpolator();
+ }
+}
diff --git a/android/view/animation/OvershootInterpolator.java b/android/view/animation/OvershootInterpolator.java
new file mode 100644
index 00000000..306688a1
--- /dev/null
+++ b/android/view/animation/OvershootInterpolator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2009 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.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator where the change flings forward and overshoots the last value
+ * then comes back.
+ */
+@HasNativeInterpolator
+public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+ private final float mTension;
+
+ public OvershootInterpolator() {
+ mTension = 2.0f;
+ }
+
+ /**
+ * @param tension Amount of overshoot. When tension equals 0.0f, there is
+ * no overshoot and the interpolator becomes a simple
+ * deceleration interpolator.
+ */
+ public OvershootInterpolator(float tension) {
+ mTension = tension;
+ }
+
+ public OvershootInterpolator(Context context, AttributeSet attrs) {
+ this(context.getResources(), context.getTheme(), attrs);
+ }
+
+ /** @hide */
+ public OvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.OvershootInterpolator, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.OvershootInterpolator);
+ }
+
+ mTension = a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
+ a.recycle();
+ }
+
+ public float getInterpolation(float t) {
+ // _o(t) = t * t * ((tension + 1) * t + tension)
+ // o(t) = _o(t - 1) + 1
+ t -= 1.0f;
+ return t * t * ((mTension + 1) * t + mTension) + 1.0f;
+ }
+
+ /** @hide */
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createOvershootInterpolator(mTension);
+ }
+}
diff --git a/android/view/animation/PathInterpolator.java b/android/view/animation/PathInterpolator.java
new file mode 100644
index 00000000..924437a2
--- /dev/null
+++ b/android/view/animation/PathInterpolator.java
@@ -0,0 +1,243 @@
+/*
+ * 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.view.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.util.PathParser;
+import android.view.InflateException;
+
+import com.android.internal.R;
+import com.android.internal.view.animation.HasNativeInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * An interpolator that can traverse a Path that extends from <code>Point</code>
+ * <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code>
+ * is the input value and the output is the y coordinate of the line at that point.
+ * This means that the Path must conform to a function <code>y = f(x)</code>.
+ *
+ * <p>The <code>Path</code> must not have gaps in the x direction and must not
+ * loop back on itself such that there can be two points sharing the same x coordinate.
+ * It is alright to have a disjoint line in the vertical direction:</p>
+ * <p><blockquote><pre>
+ * Path path = new Path();
+ * path.lineTo(0.25f, 0.25f);
+ * path.moveTo(0.25f, 0.5f);
+ * path.lineTo(1f, 1f);
+ * </pre></blockquote></p>
+ */
+@HasNativeInterpolator
+public class PathInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+
+ // This governs how accurate the approximation of the Path is.
+ private static final float PRECISION = 0.002f;
+
+ private float[] mX; // x coordinates in the line
+
+ private float[] mY; // y coordinates in the line
+
+ /**
+ * Create an interpolator for an arbitrary <code>Path</code>. The <code>Path</code>
+ * must begin at <code>(0, 0)</code> and end at <code>(1, 1)</code>.
+ *
+ * @param path The <code>Path</code> to use to make the line representing the interpolator.
+ */
+ public PathInterpolator(Path path) {
+ initPath(path);
+ }
+
+ /**
+ * Create an interpolator for a quadratic Bezier curve. The end points
+ * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
+ *
+ * @param controlX The x coordinate of the quadratic Bezier control point.
+ * @param controlY The y coordinate of the quadratic Bezier control point.
+ */
+ public PathInterpolator(float controlX, float controlY) {
+ initQuad(controlX, controlY);
+ }
+
+ /**
+ * Create an interpolator for a cubic Bezier curve. The end points
+ * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
+ *
+ * @param controlX1 The x coordinate of the first control point of the cubic Bezier.
+ * @param controlY1 The y coordinate of the first control point of the cubic Bezier.
+ * @param controlX2 The x coordinate of the second control point of the cubic Bezier.
+ * @param controlY2 The y coordinate of the second control point of the cubic Bezier.
+ */
+ public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
+ initCubic(controlX1, controlY1, controlX2, controlY2);
+ }
+
+ public PathInterpolator(Context context, AttributeSet attrs) {
+ this(context.getResources(), context.getTheme(), attrs);
+ }
+
+ /** @hide */
+ public PathInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.PathInterpolator, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.PathInterpolator);
+ }
+ parseInterpolatorFromTypeArray(a);
+ setChangingConfiguration(a.getChangingConfigurations());
+ a.recycle();
+ }
+
+ private void parseInterpolatorFromTypeArray(TypedArray a) {
+ // If there is pathData defined in the xml file, then the controls points
+ // will be all coming from pathData.
+ if (a.hasValue(R.styleable.PathInterpolator_pathData)) {
+ String pathData = a.getString(R.styleable.PathInterpolator_pathData);
+ Path path = PathParser.createPathFromPathData(pathData);
+ if (path == null) {
+ throw new InflateException("The path is null, which is created"
+ + " from " + pathData);
+ }
+ initPath(path);
+ } else {
+ if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
+ throw new InflateException("pathInterpolator requires the controlX1 attribute");
+ } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
+ throw new InflateException("pathInterpolator requires the controlY1 attribute");
+ }
+ float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
+ float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
+
+ boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
+ boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
+
+ if (hasX2 != hasY2) {
+ throw new InflateException(
+ "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
+ }
+
+ if (!hasX2) {
+ initQuad(x1, y1);
+ } else {
+ float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
+ float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
+ initCubic(x1, y1, x2, y2);
+ }
+ }
+ }
+
+ private void initQuad(float controlX, float controlY) {
+ Path path = new Path();
+ path.moveTo(0, 0);
+ path.quadTo(controlX, controlY, 1f, 1f);
+ initPath(path);
+ }
+
+ private void initCubic(float x1, float y1, float x2, float y2) {
+ Path path = new Path();
+ path.moveTo(0, 0);
+ path.cubicTo(x1, y1, x2, y2, 1f, 1f);
+ initPath(path);
+ }
+
+ private void initPath(Path path) {
+ float[] pointComponents = path.approximate(PRECISION);
+
+ int numPoints = pointComponents.length / 3;
+ if (pointComponents[1] != 0 || pointComponents[2] != 0
+ || pointComponents[pointComponents.length - 2] != 1
+ || pointComponents[pointComponents.length - 1] != 1) {
+ throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
+ }
+
+ mX = new float[numPoints];
+ mY = new float[numPoints];
+ float prevX = 0;
+ float prevFraction = 0;
+ int componentIndex = 0;
+ for (int i = 0; i < numPoints; i++) {
+ float fraction = pointComponents[componentIndex++];
+ float x = pointComponents[componentIndex++];
+ float y = pointComponents[componentIndex++];
+ if (fraction == prevFraction && x != prevX) {
+ throw new IllegalArgumentException(
+ "The Path cannot have discontinuity in the X axis.");
+ }
+ if (x < prevX) {
+ throw new IllegalArgumentException("The Path cannot loop back on itself.");
+ }
+ mX[i] = x;
+ mY[i] = y;
+ prevX = x;
+ prevFraction = fraction;
+ }
+ }
+
+ /**
+ * Using the line in the Path in this interpolator that can be described as
+ * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
+ * as the x coordinate. Values less than 0 will always return 0 and values greater
+ * than 1 will always return 1.
+ *
+ * @param t Treated as the x coordinate along the line.
+ * @return The y coordinate of the Path along the line where x = <code>t</code>.
+ * @see Interpolator#getInterpolation(float)
+ */
+ @Override
+ public float getInterpolation(float t) {
+ if (t <= 0) {
+ return 0;
+ } else if (t >= 1) {
+ return 1;
+ }
+ // Do a binary search for the correct x to interpolate between.
+ int startIndex = 0;
+ int endIndex = mX.length - 1;
+
+ while (endIndex - startIndex > 1) {
+ int midIndex = (startIndex + endIndex) / 2;
+ if (t < mX[midIndex]) {
+ endIndex = midIndex;
+ } else {
+ startIndex = midIndex;
+ }
+ }
+
+ float xRange = mX[endIndex] - mX[startIndex];
+ if (xRange == 0) {
+ return mY[startIndex];
+ }
+
+ float tInRange = t - mX[startIndex];
+ float fraction = tInRange / xRange;
+
+ float startY = mY[startIndex];
+ float endY = mY[endIndex];
+ return startY + (fraction * (endY - startY));
+ }
+
+ /** @hide **/
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactoryHelper.createPathInterpolator(mX, mY);
+ }
+
+}
diff --git a/android/view/animation/RotateAnimation.java b/android/view/animation/RotateAnimation.java
new file mode 100644
index 00000000..3c325d9b
--- /dev/null
+++ b/android/view/animation/RotateAnimation.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the rotation of an object. This rotation takes
+ * place in the X-Y plane. You can specify the point to use for the center of
+ * the rotation, where (0,0) is the top left point. If not specified, (0,0) is
+ * the default rotation point.
+ *
+ */
+public class RotateAnimation extends Animation {
+ private float mFromDegrees;
+ private float mToDegrees;
+
+ private int mPivotXType = ABSOLUTE;
+ private int mPivotYType = ABSOLUTE;
+ private float mPivotXValue = 0.0f;
+ private float mPivotYValue = 0.0f;
+
+ private float mPivotX;
+ private float mPivotY;
+
+ /**
+ * Constructor used when a RotateAnimation is loaded from a resource.
+ *
+ * @param context Application context to use
+ * @param attrs Attribute set from which to read values
+ */
+ public RotateAnimation(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.RotateAnimation);
+
+ mFromDegrees = a.getFloat(
+ com.android.internal.R.styleable.RotateAnimation_fromDegrees, 0.0f);
+ mToDegrees = a.getFloat(com.android.internal.R.styleable.RotateAnimation_toDegrees, 0.0f);
+
+ Description d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.RotateAnimation_pivotX));
+ mPivotXType = d.type;
+ mPivotXValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.RotateAnimation_pivotY));
+ mPivotYType = d.type;
+ mPivotYValue = d.value;
+
+ a.recycle();
+
+ initializePivotPoint();
+ }
+
+ /**
+ * Constructor to use when building a RotateAnimation from code.
+ * Default pivotX/pivotY point is (0,0).
+ *
+ * @param fromDegrees Rotation offset to apply at the start of the
+ * animation.
+ *
+ * @param toDegrees Rotation offset to apply at the end of the animation.
+ */
+ public RotateAnimation(float fromDegrees, float toDegrees) {
+ mFromDegrees = fromDegrees;
+ mToDegrees = toDegrees;
+ mPivotX = 0.0f;
+ mPivotY = 0.0f;
+ }
+
+ /**
+ * Constructor to use when building a RotateAnimation from code
+ *
+ * @param fromDegrees Rotation offset to apply at the start of the
+ * animation.
+ *
+ * @param toDegrees Rotation offset to apply at the end of the animation.
+ *
+ * @param pivotX The X coordinate of the point about which the object is
+ * being rotated, specified as an absolute number where 0 is the left
+ * edge.
+ * @param pivotY The Y coordinate of the point about which the object is
+ * being rotated, specified as an absolute number where 0 is the top
+ * edge.
+ */
+ public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY) {
+ mFromDegrees = fromDegrees;
+ mToDegrees = toDegrees;
+
+ mPivotXType = ABSOLUTE;
+ mPivotYType = ABSOLUTE;
+ mPivotXValue = pivotX;
+ mPivotYValue = pivotY;
+ initializePivotPoint();
+ }
+
+ /**
+ * Constructor to use when building a RotateAnimation from code
+ *
+ * @param fromDegrees Rotation offset to apply at the start of the
+ * animation.
+ *
+ * @param toDegrees Rotation offset to apply at the end of the animation.
+ *
+ * @param pivotXType Specifies how pivotXValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param pivotXValue The X coordinate of the point about which the object
+ * is being rotated, specified as an absolute number where 0 is the
+ * left edge. This value can either be an absolute number if
+ * pivotXType is ABSOLUTE, or a percentage (where 1.0 is 100%)
+ * otherwise.
+ * @param pivotYType Specifies how pivotYValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param pivotYValue The Y coordinate of the point about which the object
+ * is being rotated, specified as an absolute number where 0 is the
+ * top edge. This value can either be an absolute number if
+ * pivotYType is ABSOLUTE, or a percentage (where 1.0 is 100%)
+ * otherwise.
+ */
+ public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
+ int pivotYType, float pivotYValue) {
+ mFromDegrees = fromDegrees;
+ mToDegrees = toDegrees;
+
+ mPivotXValue = pivotXValue;
+ mPivotXType = pivotXType;
+ mPivotYValue = pivotYValue;
+ mPivotYType = pivotYType;
+ initializePivotPoint();
+ }
+
+ /**
+ * Called at the end of constructor methods to initialize, if possible, values for
+ * the pivot point. This is only possible for ABSOLUTE pivot values.
+ */
+ private void initializePivotPoint() {
+ if (mPivotXType == ABSOLUTE) {
+ mPivotX = mPivotXValue;
+ }
+ if (mPivotYType == ABSOLUTE) {
+ mPivotY = mPivotYValue;
+ }
+ }
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
+ float scale = getScaleFactor();
+
+ if (mPivotX == 0.0f && mPivotY == 0.0f) {
+ t.getMatrix().setRotate(degrees);
+ } else {
+ t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
+ }
+ }
+
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+ mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
+ mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
+ }
+}
diff --git a/android/view/animation/ScaleAnimation.java b/android/view/animation/ScaleAnimation.java
new file mode 100644
index 00000000..e9a84364
--- /dev/null
+++ b/android/view/animation/ScaleAnimation.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+/**
+ * An animation that controls the scale of an object. You can specify the point
+ * to use for the center of scaling.
+ *
+ */
+public class ScaleAnimation extends Animation {
+ private final Resources mResources;
+
+ private float mFromX;
+ private float mToX;
+ private float mFromY;
+ private float mToY;
+
+ private int mFromXType = TypedValue.TYPE_NULL;
+ private int mToXType = TypedValue.TYPE_NULL;
+ private int mFromYType = TypedValue.TYPE_NULL;
+ private int mToYType = TypedValue.TYPE_NULL;
+
+ private int mFromXData = 0;
+ private int mToXData = 0;
+ private int mFromYData = 0;
+ private int mToYData = 0;
+
+ private int mPivotXType = ABSOLUTE;
+ private int mPivotYType = ABSOLUTE;
+ private float mPivotXValue = 0.0f;
+ private float mPivotYValue = 0.0f;
+
+ private float mPivotX;
+ private float mPivotY;
+
+ /**
+ * Constructor used when a ScaleAnimation is loaded from a resource.
+ *
+ * @param context Application context to use
+ * @param attrs Attribute set from which to read values
+ */
+ public ScaleAnimation(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mResources = context.getResources();
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ScaleAnimation);
+
+ TypedValue tv = a.peekValue(
+ com.android.internal.R.styleable.ScaleAnimation_fromXScale);
+ mFromX = 0.0f;
+ if (tv != null) {
+ if (tv.type == TypedValue.TYPE_FLOAT) {
+ // This is a scaling factor.
+ mFromX = tv.getFloat();
+ } else {
+ mFromXType = tv.type;
+ mFromXData = tv.data;
+ }
+ }
+ tv = a.peekValue(
+ com.android.internal.R.styleable.ScaleAnimation_toXScale);
+ mToX = 0.0f;
+ if (tv != null) {
+ if (tv.type == TypedValue.TYPE_FLOAT) {
+ // This is a scaling factor.
+ mToX = tv.getFloat();
+ } else {
+ mToXType = tv.type;
+ mToXData = tv.data;
+ }
+ }
+
+ tv = a.peekValue(
+ com.android.internal.R.styleable.ScaleAnimation_fromYScale);
+ mFromY = 0.0f;
+ if (tv != null) {
+ if (tv.type == TypedValue.TYPE_FLOAT) {
+ // This is a scaling factor.
+ mFromY = tv.getFloat();
+ } else {
+ mFromYType = tv.type;
+ mFromYData = tv.data;
+ }
+ }
+ tv = a.peekValue(
+ com.android.internal.R.styleable.ScaleAnimation_toYScale);
+ mToY = 0.0f;
+ if (tv != null) {
+ if (tv.type == TypedValue.TYPE_FLOAT) {
+ // This is a scaling factor.
+ mToY = tv.getFloat();
+ } else {
+ mToYType = tv.type;
+ mToYData = tv.data;
+ }
+ }
+
+ Description d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ScaleAnimation_pivotX));
+ mPivotXType = d.type;
+ mPivotXValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ScaleAnimation_pivotY));
+ mPivotYType = d.type;
+ mPivotYValue = d.value;
+
+ a.recycle();
+
+ initializePivotPoint();
+ }
+
+ /**
+ * Constructor to use when building a ScaleAnimation from code
+ *
+ * @param fromX Horizontal scaling factor to apply at the start of the
+ * animation
+ * @param toX Horizontal scaling factor to apply at the end of the animation
+ * @param fromY Vertical scaling factor to apply at the start of the
+ * animation
+ * @param toY Vertical scaling factor to apply at the end of the animation
+ */
+ public ScaleAnimation(float fromX, float toX, float fromY, float toY) {
+ mResources = null;
+ mFromX = fromX;
+ mToX = toX;
+ mFromY = fromY;
+ mToY = toY;
+ mPivotX = 0;
+ mPivotY = 0;
+ }
+
+ /**
+ * Constructor to use when building a ScaleAnimation from code
+ *
+ * @param fromX Horizontal scaling factor to apply at the start of the
+ * animation
+ * @param toX Horizontal scaling factor to apply at the end of the animation
+ * @param fromY Vertical scaling factor to apply at the start of the
+ * animation
+ * @param toY Vertical scaling factor to apply at the end of the animation
+ * @param pivotX The X coordinate of the point about which the object is
+ * being scaled, specified as an absolute number where 0 is the left
+ * edge. (This point remains fixed while the object changes size.)
+ * @param pivotY The Y coordinate of the point about which the object is
+ * being scaled, specified as an absolute number where 0 is the top
+ * edge. (This point remains fixed while the object changes size.)
+ */
+ public ScaleAnimation(float fromX, float toX, float fromY, float toY,
+ float pivotX, float pivotY) {
+ mResources = null;
+ mFromX = fromX;
+ mToX = toX;
+ mFromY = fromY;
+ mToY = toY;
+
+ mPivotXType = ABSOLUTE;
+ mPivotYType = ABSOLUTE;
+ mPivotXValue = pivotX;
+ mPivotYValue = pivotY;
+ initializePivotPoint();
+ }
+
+ /**
+ * Constructor to use when building a ScaleAnimation from code
+ *
+ * @param fromX Horizontal scaling factor to apply at the start of the
+ * animation
+ * @param toX Horizontal scaling factor to apply at the end of the animation
+ * @param fromY Vertical scaling factor to apply at the start of the
+ * animation
+ * @param toY Vertical scaling factor to apply at the end of the animation
+ * @param pivotXType Specifies how pivotXValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param pivotXValue The X coordinate of the point about which the object
+ * is being scaled, specified as an absolute number where 0 is the
+ * left edge. (This point remains fixed while the object changes
+ * size.) This value can either be an absolute number if pivotXType
+ * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+ * @param pivotYType Specifies how pivotYValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param pivotYValue The Y coordinate of the point about which the object
+ * is being scaled, specified as an absolute number where 0 is the
+ * top edge. (This point remains fixed while the object changes
+ * size.) This value can either be an absolute number if pivotYType
+ * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+ */
+ public ScaleAnimation(float fromX, float toX, float fromY, float toY,
+ int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
+ mResources = null;
+ mFromX = fromX;
+ mToX = toX;
+ mFromY = fromY;
+ mToY = toY;
+
+ mPivotXValue = pivotXValue;
+ mPivotXType = pivotXType;
+ mPivotYValue = pivotYValue;
+ mPivotYType = pivotYType;
+ initializePivotPoint();
+ }
+
+ /**
+ * Called at the end of constructor methods to initialize, if possible, values for
+ * the pivot point. This is only possible for ABSOLUTE pivot values.
+ */
+ private void initializePivotPoint() {
+ if (mPivotXType == ABSOLUTE) {
+ mPivotX = mPivotXValue;
+ }
+ if (mPivotYType == ABSOLUTE) {
+ mPivotY = mPivotYValue;
+ }
+ }
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float sx = 1.0f;
+ float sy = 1.0f;
+ float scale = getScaleFactor();
+
+ if (mFromX != 1.0f || mToX != 1.0f) {
+ sx = mFromX + ((mToX - mFromX) * interpolatedTime);
+ }
+ if (mFromY != 1.0f || mToY != 1.0f) {
+ sy = mFromY + ((mToY - mFromY) * interpolatedTime);
+ }
+
+ if (mPivotX == 0 && mPivotY == 0) {
+ t.getMatrix().setScale(sx, sy);
+ } else {
+ t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
+ }
+ }
+
+ float resolveScale(float scale, int type, int data, int size, int psize) {
+ float targetSize;
+ if (type == TypedValue.TYPE_FRACTION) {
+ targetSize = TypedValue.complexToFraction(data, size, psize);
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ targetSize = TypedValue.complexToDimension(data, mResources.getDisplayMetrics());
+ } else {
+ return scale;
+ }
+
+ if (size == 0) {
+ return 1;
+ }
+
+ return targetSize/(float)size;
+ }
+
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+
+ mFromX = resolveScale(mFromX, mFromXType, mFromXData, width, parentWidth);
+ mToX = resolveScale(mToX, mToXType, mToXData, width, parentWidth);
+ mFromY = resolveScale(mFromY, mFromYType, mFromYData, height, parentHeight);
+ mToY = resolveScale(mToY, mToYType, mToYData, height, parentHeight);
+
+ mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
+ mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
+ }
+}
diff --git a/android/view/animation/Transformation.java b/android/view/animation/Transformation.java
new file mode 100644
index 00000000..8eb5b5cf
--- /dev/null
+++ b/android/view/animation/Transformation.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.annotation.FloatRange;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+
+import java.io.PrintWriter;
+
+/**
+ * Defines the transformation to be applied at
+ * one point in time of an Animation.
+ *
+ */
+public class Transformation {
+ /**
+ * Indicates a transformation that has no effect (alpha = 1 and identity matrix.)
+ */
+ public static final int TYPE_IDENTITY = 0x0;
+ /**
+ * Indicates a transformation that applies an alpha only (uses an identity matrix.)
+ */
+ public static final int TYPE_ALPHA = 0x1;
+ /**
+ * Indicates a transformation that applies a matrix only (alpha = 1.)
+ */
+ public static final int TYPE_MATRIX = 0x2;
+ /**
+ * Indicates a transformation that applies an alpha and a matrix.
+ */
+ public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;
+
+ protected Matrix mMatrix;
+ protected float mAlpha;
+ protected int mTransformationType;
+
+ private boolean mHasClipRect;
+ private Rect mClipRect = new Rect();
+
+ /**
+ * Creates a new transformation with alpha = 1 and the identity matrix.
+ */
+ public Transformation() {
+ clear();
+ }
+
+ /**
+ * Reset the transformation to a state that leaves the object
+ * being animated in an unmodified state. The transformation type is
+ * {@link #TYPE_BOTH} by default.
+ */
+ public void clear() {
+ if (mMatrix == null) {
+ mMatrix = new Matrix();
+ } else {
+ mMatrix.reset();
+ }
+ mClipRect.setEmpty();
+ mHasClipRect = false;
+ mAlpha = 1.0f;
+ mTransformationType = TYPE_BOTH;
+ }
+
+ /**
+ * Indicates the nature of this transformation.
+ *
+ * @return {@link #TYPE_ALPHA}, {@link #TYPE_MATRIX},
+ * {@link #TYPE_BOTH} or {@link #TYPE_IDENTITY}.
+ */
+ public int getTransformationType() {
+ return mTransformationType;
+ }
+
+ /**
+ * Sets the transformation type.
+ *
+ * @param transformationType One of {@link #TYPE_ALPHA},
+ * {@link #TYPE_MATRIX}, {@link #TYPE_BOTH} or
+ * {@link #TYPE_IDENTITY}.
+ */
+ public void setTransformationType(int transformationType) {
+ mTransformationType = transformationType;
+ }
+
+ /**
+ * Clones the specified transformation.
+ *
+ * @param t The transformation to clone.
+ */
+ public void set(Transformation t) {
+ mAlpha = t.getAlpha();
+ mMatrix.set(t.getMatrix());
+ if (t.mHasClipRect) {
+ setClipRect(t.getClipRect());
+ } else {
+ mHasClipRect = false;
+ mClipRect.setEmpty();
+ }
+ mTransformationType = t.getTransformationType();
+ }
+
+ /**
+ * Apply this Transformation to an existing Transformation, e.g. apply
+ * a scale effect to something that has already been rotated.
+ * @param t
+ */
+ public void compose(Transformation t) {
+ mAlpha *= t.getAlpha();
+ mMatrix.preConcat(t.getMatrix());
+ if (t.mHasClipRect) {
+ Rect bounds = t.getClipRect();
+ if (mHasClipRect) {
+ setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
+ mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
+ } else {
+ setClipRect(bounds);
+ }
+ }
+ }
+
+ /**
+ * Like {@link #compose(Transformation)} but does this.postConcat(t) of
+ * the transformation matrix.
+ * @hide
+ */
+ public void postCompose(Transformation t) {
+ mAlpha *= t.getAlpha();
+ mMatrix.postConcat(t.getMatrix());
+ if (t.mHasClipRect) {
+ Rect bounds = t.getClipRect();
+ if (mHasClipRect) {
+ setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
+ mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
+ } else {
+ setClipRect(bounds);
+ }
+ }
+ }
+
+ /**
+ * @return The 3x3 Matrix representing the trnasformation to apply to the
+ * coordinates of the object being animated
+ */
+ public Matrix getMatrix() {
+ return mMatrix;
+ }
+
+ /**
+ * Sets the degree of transparency
+ * @param alpha 1.0 means fully opaqe and 0.0 means fully transparent
+ */
+ public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
+ mAlpha = alpha;
+ }
+
+ /**
+ * Sets the current Transform's clip rect
+ * @hide
+ */
+ public void setClipRect(Rect r) {
+ setClipRect(r.left, r.top, r.right, r.bottom);
+ }
+
+ /**
+ * Sets the current Transform's clip rect
+ * @hide
+ */
+ public void setClipRect(int l, int t, int r, int b) {
+ mClipRect.set(l, t, r, b);
+ mHasClipRect = true;
+ }
+
+ /**
+ * Returns the current Transform's clip rect
+ * @hide
+ */
+ public Rect getClipRect() {
+ return mClipRect;
+ }
+
+ /**
+ * Returns whether the current Transform's clip rect is set
+ * @hide
+ */
+ public boolean hasClipRect() {
+ return mHasClipRect;
+ }
+
+ /**
+ * @return The degree of transparency
+ */
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("Transformation");
+ toShortString(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Return a string representation of the transformation in a compact form.
+ */
+ public String toShortString() {
+ StringBuilder sb = new StringBuilder(64);
+ toShortString(sb);
+ return sb.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public void toShortString(StringBuilder sb) {
+ sb.append("{alpha="); sb.append(mAlpha);
+ sb.append(" matrix="); mMatrix.toShortString(sb);
+ sb.append('}');
+ }
+
+ /**
+ * Print short string, to optimize dumping.
+ * @hide
+ */
+ public void printShortString(PrintWriter pw) {
+ pw.print("{alpha="); pw.print(mAlpha);
+ pw.print(" matrix=");
+ mMatrix.printShortString(pw);
+ pw.print('}');
+ }
+}
diff --git a/android/view/animation/TranslateAnimation.java b/android/view/animation/TranslateAnimation.java
new file mode 100644
index 00000000..216022b2
--- /dev/null
+++ b/android/view/animation/TranslateAnimation.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2006 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the position of an object. See the
+ * {@link android.view.animation full package} description for details and
+ * sample code.
+ *
+ */
+public class TranslateAnimation extends Animation {
+ private int mFromXType = ABSOLUTE;
+ private int mToXType = ABSOLUTE;
+
+ private int mFromYType = ABSOLUTE;
+ private int mToYType = ABSOLUTE;
+
+ /** @hide */
+ protected float mFromXValue = 0.0f;
+ /** @hide */
+ protected float mToXValue = 0.0f;
+
+ /** @hide */
+ protected float mFromYValue = 0.0f;
+ /** @hide */
+ protected float mToYValue = 0.0f;
+
+ /** @hide */
+ protected float mFromXDelta;
+ /** @hide */
+ protected float mToXDelta;
+ /** @hide */
+ protected float mFromYDelta;
+ /** @hide */
+ protected float mToYDelta;
+
+ /**
+ * Constructor used when a TranslateAnimation is loaded from a resource.
+ *
+ * @param context Application context to use
+ * @param attrs Attribute set from which to read values
+ */
+ public TranslateAnimation(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.TranslateAnimation);
+
+ Description d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.TranslateAnimation_fromXDelta));
+ mFromXType = d.type;
+ mFromXValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.TranslateAnimation_toXDelta));
+ mToXType = d.type;
+ mToXValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.TranslateAnimation_fromYDelta));
+ mFromYType = d.type;
+ mFromYValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.TranslateAnimation_toYDelta));
+ mToYType = d.type;
+ mToYValue = d.value;
+
+ a.recycle();
+ }
+
+ /**
+ * Constructor to use when building a TranslateAnimation from code
+ *
+ * @param fromXDelta Change in X coordinate to apply at the start of the
+ * animation
+ * @param toXDelta Change in X coordinate to apply at the end of the
+ * animation
+ * @param fromYDelta Change in Y coordinate to apply at the start of the
+ * animation
+ * @param toYDelta Change in Y coordinate to apply at the end of the
+ * animation
+ */
+ public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
+ mFromXValue = fromXDelta;
+ mToXValue = toXDelta;
+ mFromYValue = fromYDelta;
+ mToYValue = toYDelta;
+
+ mFromXType = ABSOLUTE;
+ mToXType = ABSOLUTE;
+ mFromYType = ABSOLUTE;
+ mToYType = ABSOLUTE;
+ }
+
+ /**
+ * Constructor to use when building a TranslateAnimation from code
+ *
+ * @param fromXType Specifies how fromXValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param fromXValue Change in X coordinate to apply at the start of the
+ * animation. This value can either be an absolute number if fromXType
+ * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+ * @param toXType Specifies how toXValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param toXValue Change in X coordinate to apply at the end of the
+ * animation. This value can either be an absolute number if toXType
+ * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+ * @param fromYType Specifies how fromYValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param fromYValue Change in Y coordinate to apply at the start of the
+ * animation. This value can either be an absolute number if fromYType
+ * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+ * @param toYType Specifies how toYValue should be interpreted. One of
+ * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
+ * Animation.RELATIVE_TO_PARENT.
+ * @param toYValue Change in Y coordinate to apply at the end of the
+ * animation. This value can either be an absolute number if toYType
+ * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise.
+ */
+ public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
+ int fromYType, float fromYValue, int toYType, float toYValue) {
+
+ mFromXValue = fromXValue;
+ mToXValue = toXValue;
+ mFromYValue = fromYValue;
+ mToYValue = toYValue;
+
+ mFromXType = fromXType;
+ mToXType = toXType;
+ mFromYType = fromYType;
+ mToYType = toYType;
+ }
+
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float dx = mFromXDelta;
+ float dy = mFromYDelta;
+ if (mFromXDelta != mToXDelta) {
+ dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
+ }
+ if (mFromYDelta != mToYDelta) {
+ dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
+ }
+ t.getMatrix().setTranslate(dx, dy);
+ }
+
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+ mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);
+ mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
+ mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
+ mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
+ }
+}
diff --git a/android/view/animation/TranslateXAnimation.java b/android/view/animation/TranslateXAnimation.java
new file mode 100644
index 00000000..d75323f2
--- /dev/null
+++ b/android/view/animation/TranslateXAnimation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 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.animation;
+
+import android.graphics.Matrix;
+
+/**
+ * Special case of TranslateAnimation that translates only horizontally, picking up the
+ * vertical values from whatever is set on the Transformation already. When used in
+ * conjunction with a TranslateYAnimation, allows independent animation of x and y
+ * position.
+ * @hide
+ */
+public class TranslateXAnimation extends TranslateAnimation {
+ float[] mTmpValues = new float[9];
+
+ /**
+ * Constructor. Passes in 0 for the y parameters of TranslateAnimation
+ */
+ public TranslateXAnimation(float fromXDelta, float toXDelta) {
+ super(fromXDelta, toXDelta, 0, 0);
+ }
+
+ /**
+ * Constructor. Passes in 0 for the y parameters of TranslateAnimation
+ */
+ public TranslateXAnimation(int fromXType, float fromXValue, int toXType, float toXValue) {
+ super(fromXType, fromXValue, toXType, toXValue, ABSOLUTE, 0, ABSOLUTE, 0);
+ }
+
+ /**
+ * Calculates and sets x translation values on given transformation.
+ */
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ Matrix m = t.getMatrix();
+ m.getValues(mTmpValues);
+ float dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
+ t.getMatrix().setTranslate(dx, mTmpValues[Matrix.MTRANS_Y]);
+ }
+}
diff --git a/android/view/animation/TranslateYAnimation.java b/android/view/animation/TranslateYAnimation.java
new file mode 100644
index 00000000..714558dc
--- /dev/null
+++ b/android/view/animation/TranslateYAnimation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 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.animation;
+
+import android.graphics.Matrix;
+
+/**
+ * Special case of TranslateAnimation that translates only vertically, picking up the
+ * horizontal values from whatever is set on the Transformation already. When used in
+ * conjunction with a TranslateXAnimation, allows independent animation of x and y
+ * position.
+ * @hide
+ */
+public class TranslateYAnimation extends TranslateAnimation {
+ float[] mTmpValues = new float[9];
+
+ /**
+ * Constructor. Passes in 0 for the x parameters of TranslateAnimation
+ */
+ public TranslateYAnimation(float fromYDelta, float toYDelta) {
+ super(0, 0, fromYDelta, toYDelta);
+ }
+
+ /**
+ * Constructor. Passes in 0 for the x parameters of TranslateAnimation
+ */
+ public TranslateYAnimation(int fromYType, float fromYValue, int toYType, float toYValue) {
+ super(ABSOLUTE, 0, ABSOLUTE, 0, fromYType, fromYValue, toYType, toYValue);
+ }
+
+ /**
+ * Calculates and sets y translation values on given transformation.
+ */
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ Matrix m = t.getMatrix();
+ m.getValues(mTmpValues);
+ float dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
+ t.getMatrix().setTranslate(mTmpValues[Matrix.MTRANS_X], dy);
+ }
+}
diff --git a/android/view/autofill/AutofillId.java b/android/view/autofill/AutofillId.java
new file mode 100644
index 00000000..5ce2421a
--- /dev/null
+++ b/android/view/autofill/AutofillId.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 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.autofill;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+/**
+ * A unique identifier for an autofill node inside an {@link android.app.Activity}.
+ */
+public final class AutofillId implements Parcelable {
+
+ private final int mViewId;
+ private final boolean mVirtual;
+ private final int mVirtualId;
+
+ /** @hide */
+ @TestApi
+ public AutofillId(int id) {
+ mVirtual = false;
+ mViewId = id;
+ mVirtualId = View.NO_ID;
+ }
+
+ /** @hide */
+ public AutofillId(AutofillId parent, int virtualChildId) {
+ mVirtual = true;
+ mViewId = parent.mViewId;
+ mVirtualId = virtualChildId;
+ }
+
+ /** @hide */
+ public AutofillId(int parentId, int virtualChildId) {
+ mVirtual = true;
+ mViewId = parentId;
+ mVirtualId = virtualChildId;
+ }
+
+ /** @hide */
+ public int getViewId() {
+ return mViewId;
+ }
+
+ /** @hide */
+ public int getVirtualChildId() {
+ return mVirtualId;
+ }
+
+ /** @hide */
+ public boolean isVirtual() {
+ return mVirtual;
+ }
+
+ /////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mViewId;
+ result = prime * result + mVirtualId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ final AutofillId other = (AutofillId) obj;
+ if (mViewId != other.mViewId) return false;
+ if (mVirtualId != other.mVirtualId) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder().append(mViewId);
+ if (mVirtual) {
+ builder.append(':').append(mVirtualId);
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mViewId);
+ parcel.writeInt(mVirtual ? 1 : 0);
+ parcel.writeInt(mVirtualId);
+ }
+
+ private AutofillId(Parcel parcel) {
+ mViewId = parcel.readInt();
+ mVirtual = parcel.readInt() == 1;
+ mVirtualId = parcel.readInt();
+ }
+
+ public static final Parcelable.Creator<AutofillId> CREATOR =
+ new Parcelable.Creator<AutofillId>() {
+ @Override
+ public AutofillId createFromParcel(Parcel source) {
+ return new AutofillId(source);
+ }
+
+ @Override
+ public AutofillId[] newArray(int size) {
+ return new AutofillId[size];
+ }
+ };
+}
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
new file mode 100644
index 00000000..61cbce97
--- /dev/null
+++ b/android/view/autofill/AutofillManager.java
@@ -0,0 +1,1830 @@
+/*
+ * Copyright (C) 2017 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.autofill;
+
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.view.autofill.Helper.sDebug;
+import static android.view.autofill.Helper.sVerbose;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.graphics.Rect;
+import android.metrics.LogMaker;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillEventHistory;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The {@link AutofillManager} provides ways for apps and custom views to integrate with the
+ * Autofill Framework lifecycle.
+ *
+ * <p>The autofill lifecycle starts with the creation of an autofill context associated with an
+ * activity context; the autofill context is created when one of the following methods is called for
+ * the first time in an activity context, and the current user has an enabled autofill service:
+ *
+ * <ul>
+ * <li>{@link #notifyViewEntered(View)}
+ * <li>{@link #notifyViewEntered(View, int, Rect)}
+ * <li>{@link #requestAutofill(View)}
+ * </ul>
+ *
+ * <p>Tipically, the context is automatically created when the first view of the activity is
+ * focused because {@code View.onFocusChanged()} indirectly calls
+ * {@link #notifyViewEntered(View)}. App developers can call {@link #requestAutofill(View)} to
+ * explicitly create it (for example, a custom view developer could offer a contextual menu action
+ * in a text-field view to let users manually request autofill).
+ *
+ * <p>After the context is created, the Android System creates a {@link android.view.ViewStructure}
+ * that represents the view hierarchy by calling
+ * {@link View#dispatchProvideAutofillStructure(android.view.ViewStructure, int)} in the root views
+ * of all application windows. By default, {@code dispatchProvideAutofillStructure()} results in
+ * subsequent calls to {@link View#onProvideAutofillStructure(android.view.ViewStructure, int)} and
+ * {@link View#onProvideAutofillVirtualStructure(android.view.ViewStructure, int)} for each view in
+ * the hierarchy.
+ *
+ * <p>The resulting {@link android.view.ViewStructure} is then passed to the autofill service, which
+ * parses it looking for views that can be autofilled. If the service finds such views, it returns
+ * a data structure to the Android System containing the following optional info:
+ *
+ * <ul>
+ * <li>Datasets used to autofill subsets of views in the activity.
+ * <li>Id of views that the service can save their values for future autofilling.
+ * </ul>
+ *
+ * <p>When the service returns datasets, the Android System displays an autofill dataset picker
+ * UI affordance associated with the view, when the view is focused on and is part of a dataset.
+ * The application can be notified when the affordance is shown by registering an
+ * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
+ * selects a dataset from the affordance, all views present in the dataset are autofilled, through
+ * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
+ *
+ * <p>When the service returns ids of savable views, the Android System keeps track of changes
+ * made to these views, so they can be used to determine if the autofill save UI is shown later.
+ *
+ * <p>The context is then finished when one of the following occurs:
+ *
+ * <ul>
+ * <li>{@link #commit()} is called or all savable views are gone.
+ * <li>{@link #cancel()} is called.
+ * </ul>
+ *
+ * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
+ * shows a save UI affordance if the value of savable views have changed. If the user selects the
+ * option to Save, the current value of the views is then sent to the autofill service.
+ *
+ * <p>It is safe to call into its methods from any thread.
+ */
+@SystemService(Context.AUTOFILL_MANAGER_SERVICE)
+public final class AutofillManager {
+
+ private static final String TAG = "AutofillManager";
+
+ /**
+ * Intent extra: The assist structure which captures the filled screen.
+ *
+ * <p>
+ * Type: {@link android.app.assist.AssistStructure}
+ */
+ public static final String EXTRA_ASSIST_STRUCTURE =
+ "android.view.autofill.extra.ASSIST_STRUCTURE";
+
+ /**
+ * Intent extra: The result of an authentication operation. It is
+ * either a fully populated {@link android.service.autofill.FillResponse}
+ * or a fully populated {@link android.service.autofill.Dataset} if
+ * a response or a dataset is being authenticated respectively.
+ *
+ * <p>
+ * Type: {@link android.service.autofill.FillResponse} or a
+ * {@link android.service.autofill.Dataset}
+ */
+ public static final String EXTRA_AUTHENTICATION_RESULT =
+ "android.view.autofill.extra.AUTHENTICATION_RESULT";
+
+ /**
+ * Intent extra: The optional extras provided by the
+ * {@link android.service.autofill.AutofillService}.
+ *
+ * <p>For example, when the service responds to a {@link
+ * android.service.autofill.FillCallback#onSuccess(android.service.autofill.FillResponse)} with
+ * a {@code FillResponse} that requires authentication, the Intent that launches the
+ * service authentication will contain the Bundle set by
+ * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
+ *
+ * <p>
+ * Type: {@link android.os.Bundle}
+ */
+ public static final String EXTRA_CLIENT_STATE =
+ "android.view.autofill.extra.CLIENT_STATE";
+
+
+ /** @hide */
+ public static final String EXTRA_RESTORE_SESSION_TOKEN =
+ "android.view.autofill.extra.RESTORE_SESSION_TOKEN";
+
+ private static final String SESSION_ID_TAG = "android:sessionId";
+ private static final String STATE_TAG = "android:state";
+ private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
+
+
+ /** @hide */ public static final int ACTION_START_SESSION = 1;
+ /** @hide */ public static final int ACTION_VIEW_ENTERED = 2;
+ /** @hide */ public static final int ACTION_VIEW_EXITED = 3;
+ /** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
+
+
+ /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
+ /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
+ /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
+
+ /** Which bits in an authentication id are used for the dataset id */
+ private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF;
+ /** How many bits in an authentication id are used for the dataset id */
+ private static final int AUTHENTICATION_ID_DATASET_ID_SHIFT = 16;
+ /** @hide The index for an undefined data set */
+ public static final int AUTHENTICATION_ID_DATASET_ID_UNDEFINED = 0xFFFF;
+
+ /**
+ * Used on {@link #onPendingSaveUi(int, IBinder)} to cancel the pending UI.
+ *
+ * @hide
+ */
+ public static final int PENDING_UI_OPERATION_CANCEL = 1;
+
+ /**
+ * Used on {@link #onPendingSaveUi(int, IBinder)} to restore the pending UI.
+ *
+ * @hide
+ */
+ public static final int PENDING_UI_OPERATION_RESTORE = 2;
+
+ /**
+ * Initial state of the autofill context, set when there is no session (i.e., when
+ * {@link #mSessionId} is {@link #NO_SESSION}).
+ *
+ * @hide
+ */
+ public static final int STATE_UNKNOWN = 1;
+
+ /**
+ * State where the autofill context hasn't been {@link #commit() finished} nor
+ * {@link #cancel() canceled} yet.
+ *
+ * @hide
+ */
+ public static final int STATE_ACTIVE = 2;
+
+ /**
+ * State where the autofill context has been {@link #commit() finished} but the server still has
+ * a session because the Save UI hasn't been dismissed yet.
+ *
+ * @hide
+ */
+ public static final int STATE_SHOWING_SAVE_UI = 4;
+
+ /**
+ * Makes an authentication id from a request id and a dataset id.
+ *
+ * @param requestId The request id.
+ * @param datasetId The dataset id.
+ * @return The authentication id.
+ * @hide
+ */
+ public static int makeAuthenticationId(int requestId, int datasetId) {
+ return (requestId << AUTHENTICATION_ID_DATASET_ID_SHIFT)
+ | (datasetId & AUTHENTICATION_ID_DATASET_ID_MASK);
+ }
+
+ /**
+ * Gets the request id from an authentication id.
+ *
+ * @param authRequestId The authentication id.
+ * @return The request id.
+ * @hide
+ */
+ public static int getRequestIdFromAuthenticationId(int authRequestId) {
+ return (authRequestId >> AUTHENTICATION_ID_DATASET_ID_SHIFT);
+ }
+
+ /**
+ * Gets the dataset id from an authentication id.
+ *
+ * @param authRequestId The authentication id.
+ * @return The dataset id.
+ * @hide
+ */
+ public static int getDatasetIdFromAuthenticationId(int authRequestId) {
+ return (authRequestId & AUTHENTICATION_ID_DATASET_ID_MASK);
+ }
+
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+ /**
+ * There is currently no session running.
+ * {@hide}
+ */
+ public static final int NO_SESSION = Integer.MIN_VALUE;
+
+ private final IAutoFillManager mService;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private IAutoFillManagerClient mServiceClient;
+
+ @GuardedBy("mLock")
+ private AutofillCallback mCallback;
+
+ private final Context mContext;
+
+ @GuardedBy("mLock")
+ private int mSessionId = NO_SESSION;
+
+ @GuardedBy("mLock")
+ private int mState = STATE_UNKNOWN;
+
+ @GuardedBy("mLock")
+ private boolean mEnabled;
+
+ /** If a view changes to this mapping the autofill operation was successful */
+ @GuardedBy("mLock")
+ @Nullable private ParcelableMap mLastAutofilledData;
+
+ /** If view tracking is enabled, contains the tracking state */
+ @GuardedBy("mLock")
+ @Nullable private TrackedViews mTrackedViews;
+
+ /** Views that are only tracked because they are fillable and could be anchoring the UI. */
+ @GuardedBy("mLock")
+ @Nullable private ArraySet<AutofillId> mFillableIds;
+
+ /** @hide */
+ public interface AutofillClient {
+ /**
+ * Asks the client to start an authentication flow.
+ *
+ * @param authenticationId A unique id of the authentication operation.
+ * @param intent The authentication intent.
+ * @param fillInIntent The authentication fill-in intent.
+ */
+ void autofillCallbackAuthenticate(int authenticationId, IntentSender intent,
+ Intent fillInIntent);
+
+ /**
+ * Tells the client this manager has state to be reset.
+ */
+ void autofillCallbackResetableStateAvailable();
+
+ /**
+ * Request showing the autofill UI.
+ *
+ * @param anchor The real view the UI needs to anchor to.
+ * @param width The width of the fill UI content.
+ * @param height The height of the fill UI content.
+ * @param virtualBounds The bounds of the virtual decendant of the anchor.
+ * @param presenter The presenter that controls the fill UI window.
+ * @return Whether the UI was shown.
+ */
+ boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
+ @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
+
+ /**
+ * Request hiding the autofill UI.
+ *
+ * @return Whether the UI was hidden.
+ */
+ boolean autofillCallbackRequestHideFillUi();
+
+ /**
+ * Checks if views are currently attached and visible.
+ *
+ * @return And array with {@code true} iff the view is attached or visible
+ */
+ @NonNull boolean[] getViewVisibility(@NonNull int[] viewId);
+
+ /**
+ * Checks is the client is currently visible as understood by autofill.
+ *
+ * @return {@code true} if the client is currently visible
+ */
+ boolean isVisibleForAutofill();
+
+ /**
+ * Finds views by traversing the hierarchies of the client.
+ *
+ * @param viewIds The autofill ids of the views to find
+ *
+ * @return And array containing the views (empty if no views found).
+ */
+ @NonNull View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds);
+
+ /**
+ * Finds a view by traversing the hierarchies of the client.
+ *
+ * @param viewId The autofill id of the views to find
+ *
+ * @return The view, or {@code null} if not found
+ */
+ @Nullable View findViewByAutofillIdTraversal(int viewId);
+
+ /**
+ * Runs the specified action on the UI thread.
+ */
+ void runOnUiThread(Runnable action);
+ }
+
+ /**
+ * @hide
+ */
+ public AutofillManager(Context context, IAutoFillManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Restore state after activity lifecycle
+ *
+ * @param savedInstanceState The state to be restored
+ *
+ * {@hide}
+ */
+ public void onCreate(Bundle savedInstanceState) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
+
+ if (isActiveLocked()) {
+ Log.w(TAG, "New session was started before onCreate()");
+ return;
+ }
+
+ mSessionId = savedInstanceState.getInt(SESSION_ID_TAG, NO_SESSION);
+ mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN);
+
+ if (mSessionId != NO_SESSION) {
+ ensureServiceClientAddedIfNeededLocked();
+
+ final AutofillClient client = getClientLocked();
+ if (client != null) {
+ try {
+ final boolean sessionWasRestored = mService.restoreSession(mSessionId,
+ mContext.getActivityToken(), mServiceClient.asBinder());
+
+ if (!sessionWasRestored) {
+ Log.w(TAG, "Session " + mSessionId + " could not be restored");
+ mSessionId = NO_SESSION;
+ mState = STATE_UNKNOWN;
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "session " + mSessionId + " was restored");
+ }
+
+ client.autofillCallbackResetableStateAvailable();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not figure out if there was an autofill session", e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Called once the client becomes visible.
+ *
+ * @see AutofillClient#isVisibleForAutofill()
+ *
+ * {@hide}
+ */
+ public void onVisibleForAutofill() {
+ synchronized (mLock) {
+ if (mEnabled && isActiveLocked() && mTrackedViews != null) {
+ mTrackedViews.onVisibleForAutofillLocked();
+ }
+ }
+ }
+
+ /**
+ * Save state before activity lifecycle
+ *
+ * @param outState Place to store the state
+ *
+ * {@hide}
+ */
+ public void onSaveInstanceState(Bundle outState) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mSessionId != NO_SESSION) {
+ outState.putInt(SESSION_ID_TAG, mSessionId);
+ }
+ if (mState != STATE_UNKNOWN) {
+ outState.putInt(STATE_TAG, mState);
+ }
+ if (mLastAutofilledData != null) {
+ outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
+ }
+ }
+ }
+
+ /**
+ * Checks whether autofill is enabled for the current user.
+ *
+ * <p>Typically used to determine whether the option to explicitly request autofill should
+ * be offered - see {@link #requestAutofill(View)}.
+ *
+ * @return whether autofill is enabled for the current user.
+ */
+ public boolean isEnabled() {
+ if (!hasAutofillFeature()) {
+ return false;
+ }
+ synchronized (mLock) {
+ ensureServiceClientAddedIfNeededLocked();
+ return mEnabled;
+ }
+ }
+
+ /**
+ * Should always be called from {@link AutofillService#getFillEventHistory()}.
+ *
+ * @hide
+ */
+ @Nullable public FillEventHistory getFillEventHistory() {
+ try {
+ return mService.getFillEventHistory();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Explicitly requests a new autofill context.
+ *
+ * <p>Normally, the autofill context is automatically started if necessary when
+ * {@link #notifyViewEntered(View)} is called, but this method should be used in the
+ * cases where it must be explicitly started. For example, when the view offers an AUTOFILL
+ * option on its contextual overflow menu, and the user selects it.
+ *
+ * @param view view requesting the new autofill context.
+ */
+ public void requestAutofill(@NonNull View view) {
+ notifyViewEntered(view, FLAG_MANUAL_REQUEST);
+ }
+
+ /**
+ * Explicitly requests a new autofill context for virtual views.
+ *
+ * <p>Normally, the autofill context is automatically started if necessary when
+ * {@link #notifyViewEntered(View, int, Rect)} is called, but this method should be used in the
+ * cases where it must be explicitly started. For example, when the virtual view offers an
+ * AUTOFILL option on its contextual overflow menu, and the user selects it.
+ *
+ * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
+ * parent view uses {@code bounds} to draw the virtual view inside its Canvas,
+ * the absolute bounds could be calculated by:
+ *
+ * <pre class="prettyprint">
+ * int offset[] = new int[2];
+ * getLocationOnScreen(offset);
+ * Rect absBounds = new Rect(bounds.left + offset[0],
+ * bounds.top + offset[1],
+ * bounds.right + offset[0], bounds.bottom + offset[1]);
+ * </pre>
+ *
+ * @param view the virtual view parent.
+ * @param virtualId id identifying the virtual child inside the parent view.
+ * @param absBounds absolute boundaries of the virtual view in the screen.
+ */
+ public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
+ notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST);
+ }
+
+ /**
+ * Called when a {@link View} that supports autofill is entered.
+ *
+ * @param view {@link View} that was entered.
+ */
+ public void notifyViewEntered(@NonNull View view) {
+ notifyViewEntered(view, 0);
+ }
+
+ private void notifyViewEntered(@NonNull View view, int flags) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ AutofillCallback callback = null;
+ synchronized (mLock) {
+ ensureServiceClientAddedIfNeededLocked();
+
+ if (!mEnabled) {
+ if (mCallback != null) {
+ callback = mCallback;
+ }
+ } else {
+ final AutofillId id = getAutofillId(view);
+ final AutofillValue value = view.getAutofillValue();
+
+ if (!isActiveLocked()) {
+ // Starts new session.
+ startSessionLocked(id, null, value, flags);
+ } else {
+ // Update focus on existing session.
+ updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
+ }
+ }
+ }
+
+ if (callback != null) {
+ mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ }
+ }
+
+ /**
+ * Called when a {@link View} that supports autofill is exited.
+ *
+ * @param view {@link View} that was exited.
+ */
+ public void notifyViewExited(@NonNull View view) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ ensureServiceClientAddedIfNeededLocked();
+
+ if (mEnabled && isActiveLocked()) {
+ final AutofillId id = getAutofillId(view);
+
+ // Update focus on existing session.
+ updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
+ }
+ }
+ }
+
+ /**
+ * Called when a {@link View view's} visibility changed.
+ *
+ * @param view {@link View} that was exited.
+ * @param isVisible visible if the view is visible in the view hierarchy.
+ */
+ public void notifyViewVisibilityChanged(@NonNull View view, boolean isVisible) {
+ notifyViewVisibilityChangedInternal(view, 0, isVisible, false);
+ }
+
+ /**
+ * Called when a virtual view's visibility changed.
+ *
+ * @param view {@link View} that was exited.
+ * @param virtualId id identifying the virtual child inside the parent view.
+ * @param isVisible visible if the view is visible in the view hierarchy.
+ */
+ public void notifyViewVisibilityChanged(@NonNull View view, int virtualId, boolean isVisible) {
+ notifyViewVisibilityChangedInternal(view, virtualId, isVisible, true);
+ }
+
+ /**
+ * Called when a view/virtual view's visibility changed.
+ *
+ * @param view {@link View} that was exited.
+ * @param virtualId id identifying the virtual child inside the parent view.
+ * @param isVisible visible if the view is visible in the view hierarchy.
+ * @param virtual Whether the view is virtual.
+ */
+ private void notifyViewVisibilityChangedInternal(@NonNull View view, int virtualId,
+ boolean isVisible, boolean virtual) {
+ synchronized (mLock) {
+ if (mEnabled && isActiveLocked()) {
+ final AutofillId id = virtual ? getAutofillId(view, virtualId)
+ : view.getAutofillId();
+ if (!isVisible && mFillableIds != null) {
+ if (mFillableIds.contains(id)) {
+ if (sDebug) Log.d(TAG, "Hidding UI when view " + id + " became invisible");
+ requestHideFillUi(id, view);
+ }
+ }
+ if (mTrackedViews != null) {
+ mTrackedViews.notifyViewVisibilityChanged(id, isVisible);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when a virtual view that supports autofill is entered.
+ *
+ * <p>The virtual view boundaries must be absolute screen coordinates. For example, if the
+ * parent, non-virtual view uses {@code bounds} to draw the virtual view inside its Canvas,
+ * the absolute bounds could be calculated by:
+ *
+ * <pre class="prettyprint">
+ * int offset[] = new int[2];
+ * getLocationOnScreen(offset);
+ * Rect absBounds = new Rect(bounds.left + offset[0],
+ * bounds.top + offset[1],
+ * bounds.right + offset[0], bounds.bottom + offset[1]);
+ * </pre>
+ *
+ * @param view the virtual view parent.
+ * @param virtualId id identifying the virtual child inside the parent view.
+ * @param absBounds absolute boundaries of the virtual view in the screen.
+ */
+ public void notifyViewEntered(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
+ notifyViewEntered(view, virtualId, absBounds, 0);
+ }
+
+ private void notifyViewEntered(View view, int virtualId, Rect bounds, int flags) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ AutofillCallback callback = null;
+ synchronized (mLock) {
+ ensureServiceClientAddedIfNeededLocked();
+
+ if (!mEnabled) {
+ if (mCallback != null) {
+ callback = mCallback;
+ }
+ } else {
+ final AutofillId id = getAutofillId(view, virtualId);
+
+ if (!isActiveLocked()) {
+ // Starts new session.
+ startSessionLocked(id, bounds, null, flags);
+ } else {
+ // Update focus on existing session.
+ updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
+ }
+ }
+ }
+
+ if (callback != null) {
+ callback.onAutofillEvent(view, virtualId,
+ AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ }
+ }
+
+ /**
+ * Called when a virtual view that supports autofill is exited.
+ *
+ * @param view the virtual view parent.
+ * @param virtualId id identifying the virtual child inside the parent view.
+ */
+ public void notifyViewExited(@NonNull View view, int virtualId) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ ensureServiceClientAddedIfNeededLocked();
+
+ if (mEnabled && isActiveLocked()) {
+ final AutofillId id = getAutofillId(view, virtualId);
+
+ // Update focus on existing session.
+ updateSessionLocked(id, null, null, ACTION_VIEW_EXITED, 0);
+ }
+ }
+ }
+
+ /**
+ * Called to indicate the value of an autofillable {@link View} changed.
+ *
+ * @param view view whose value changed.
+ */
+ public void notifyValueChanged(View view) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ AutofillId id = null;
+ boolean valueWasRead = false;
+ AutofillValue value = null;
+
+ synchronized (mLock) {
+ // If the session is gone some fields might still be highlighted, hence we have to
+ // remove the isAutofilled property even if no sessions are active.
+ if (mLastAutofilledData == null) {
+ view.setAutofilled(false);
+ } else {
+ id = getAutofillId(view);
+ if (mLastAutofilledData.containsKey(id)) {
+ value = view.getAutofillValue();
+ valueWasRead = true;
+
+ if (Objects.equals(mLastAutofilledData.get(id), value)) {
+ view.setAutofilled(true);
+ } else {
+ view.setAutofilled(false);
+ mLastAutofilledData.remove(id);
+ }
+ } else {
+ view.setAutofilled(false);
+ }
+ }
+
+ if (!mEnabled || !isActiveLocked()) {
+ return;
+ }
+
+ if (id == null) {
+ id = getAutofillId(view);
+ }
+
+ if (!valueWasRead) {
+ value = view.getAutofillValue();
+ }
+
+ updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
+ }
+ }
+
+ /**
+ * Called to indicate the value of an autofillable virtual view has changed.
+ *
+ * @param view the virtual view parent.
+ * @param virtualId id identifying the virtual child inside the parent view.
+ * @param value new value of the child.
+ */
+ public void notifyValueChanged(View view, int virtualId, AutofillValue value) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (!mEnabled || !isActiveLocked()) {
+ return;
+ }
+
+ final AutofillId id = getAutofillId(view, virtualId);
+ updateSessionLocked(id, null, value, ACTION_VALUE_CHANGED, 0);
+ }
+ }
+
+ /**
+ * Called to indicate the current autofill context should be commited.
+ *
+ * <p>This method is typically called by {@link View Views} that manage virtual views; for
+ * example, when the view is rendering an {@code HTML} page with a form and virtual views
+ * that represent the HTML elements, it should call this method after the form is submitted and
+ * another page is rendered.
+ *
+ * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
+ * methods such as {@link android.app.Activity#finish()}.
+ */
+ public void commit() {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (!mEnabled && !isActiveLocked()) {
+ return;
+ }
+
+ finishSessionLocked();
+ }
+ }
+
+ /**
+ * Called to indicate the current autofill context should be cancelled.
+ *
+ * <p>This method is typically called by {@link View Views} that manage virtual views; for
+ * example, when the view is rendering an {@code HTML} page with a form and virtual views
+ * that represent the HTML elements, it should call this method if the user does not post the
+ * form but moves to another form in this page.
+ *
+ * <p><b>Note:</b> This method does not need to be called on regular application lifecycle
+ * methods such as {@link android.app.Activity#finish()}.
+ */
+ public void cancel() {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (!mEnabled && !isActiveLocked()) {
+ return;
+ }
+
+ cancelSessionLocked();
+ }
+ }
+
+ /** @hide */
+ public void disableOwnedAutofillServices() {
+ disableAutofillServices();
+ }
+
+ /**
+ * If the app calling this API has enabled autofill services they
+ * will be disabled.
+ */
+ public void disableAutofillServices() {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ try {
+ mService.disableOwnedAutofillServices(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if the calling application provides a {@link AutofillService} that is
+ * enabled for the current user, or {@code false} otherwise.
+ */
+ public boolean hasEnabledAutofillServices() {
+ if (mService == null) return false;
+
+ try {
+ return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if autofill is supported by the current device and
+ * is supported for this user.
+ *
+ * <p>Autofill is typically supported, but it could be unsupported in cases like:
+ * <ol>
+ * <li>Low-end devices.
+ * <li>Device policy rules that forbid its usage.
+ * </ol>
+ */
+ public boolean isAutofillSupported() {
+ if (mService == null) return false;
+
+ try {
+ return mService.isServiceSupported(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private AutofillClient getClientLocked() {
+ if (mContext instanceof AutofillClient) {
+ return (AutofillClient) mContext;
+ }
+ return null;
+ }
+
+ /** @hide */
+ public void onAuthenticationResult(int authenticationId, Intent data) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ // TODO: the result code is being ignored, so this method is not reliably
+ // handling the cases where it's not RESULT_OK: it works fine if the service does not
+ // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
+ // service set the extra and returned RESULT_CANCELED...
+
+ if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data);
+
+ synchronized (mLock) {
+ if (!isActiveLocked() || data == null) {
+ return;
+ }
+ final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+ final Bundle responseData = new Bundle();
+ responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+ try {
+ mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering authentication result", e);
+ }
+ }
+ }
+
+ private static AutofillId getAutofillId(View view) {
+ return new AutofillId(view.getAutofillViewId());
+ }
+
+ private static AutofillId getAutofillId(View parent, int virtualId) {
+ return new AutofillId(parent.getAutofillViewId(), virtualId);
+ }
+
+ private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
+ @NonNull AutofillValue value, int flags) {
+ if (sVerbose) {
+ Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
+ + ", flags=" + flags + ", state=" + mState);
+ }
+ if (mState != STATE_UNKNOWN) {
+ if (sDebug) Log.d(TAG, "not starting session for " + id + " on state " + mState);
+ return;
+ }
+ try {
+ mSessionId = mService.startSession(mContext.getActivityToken(),
+ mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
+ mCallback != null, flags, mContext.getOpPackageName());
+ if (mSessionId != NO_SESSION) {
+ mState = STATE_ACTIVE;
+ }
+ final AutofillClient client = getClientLocked();
+ if (client != null) {
+ client.autofillCallbackResetableStateAvailable();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void finishSessionLocked() {
+ if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + mState);
+
+ if (!isActiveLocked()) return;
+
+ try {
+ mService.finishSession(mSessionId, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ resetSessionLocked();
+ }
+
+ private void cancelSessionLocked() {
+ if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + mState);
+
+ if (!isActiveLocked()) return;
+
+ try {
+ mService.cancelSession(mSessionId, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ resetSessionLocked();
+ }
+
+ private void resetSessionLocked() {
+ mSessionId = NO_SESSION;
+ mState = STATE_UNKNOWN;
+ mTrackedViews = null;
+ mFillableIds = null;
+ }
+
+ private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
+ int flags) {
+ if (sVerbose && action != ACTION_VIEW_EXITED) {
+ Log.v(TAG, "updateSessionLocked(): id=" + id + ", bounds=" + bounds
+ + ", value=" + value + ", action=" + action + ", flags=" + flags);
+ }
+ boolean restartIfNecessary = (flags & FLAG_MANUAL_REQUEST) != 0;
+
+ try {
+ if (restartIfNecessary) {
+ final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
+ mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
+ mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
+ if (newId != mSessionId) {
+ if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
+ mSessionId = newId;
+ mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
+ final AutofillClient client = getClientLocked();
+ if (client != null) {
+ client.autofillCallbackResetableStateAvailable();
+ }
+ }
+ } else {
+ mService.updateSession(mSessionId, id, bounds, value, action, flags,
+ mContext.getUserId());
+ }
+
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void ensureServiceClientAddedIfNeededLocked() {
+ if (getClientLocked() == null) {
+ return;
+ }
+
+ if (mServiceClient == null) {
+ mServiceClient = new AutofillManagerClient(this);
+ try {
+ final int flags = mService.addClient(mServiceClient, mContext.getUserId());
+ mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
+ sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
+ sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Registers a {@link AutofillCallback} to receive autofill events.
+ *
+ * @param callback callback to receive events.
+ */
+ public void registerCallback(@Nullable AutofillCallback callback) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (callback == null) return;
+
+ final boolean hadCallback = mCallback != null;
+ mCallback = callback;
+
+ if (!hadCallback) {
+ try {
+ mService.setHasCallback(mSessionId, mContext.getUserId(), true);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregisters a {@link AutofillCallback} to receive autofill events.
+ *
+ * @param callback callback to stop receiving events.
+ */
+ public void unregisterCallback(@Nullable AutofillCallback callback) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (callback == null || mCallback == null || callback != mCallback) return;
+
+ mCallback = null;
+
+ try {
+ mService.setHasCallback(mSessionId, mContext.getUserId(), false);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final View anchor = findView(id);
+ if (anchor == null) {
+ return;
+ }
+
+ AutofillCallback callback = null;
+ synchronized (mLock) {
+ if (mSessionId == sessionId) {
+ AutofillClient client = getClientLocked();
+
+ if (client != null) {
+ if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
+ anchorBounds, presenter) && mCallback != null) {
+ callback = mCallback;
+ }
+ }
+ }
+ }
+
+ if (callback != null) {
+ if (id.isVirtual()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_SHOWN);
+ } else {
+ callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
+ }
+ }
+ }
+
+ private void authenticate(int sessionId, int authenticationId, IntentSender intent,
+ Intent fillInIntent) {
+ synchronized (mLock) {
+ if (sessionId == mSessionId) {
+ AutofillClient client = getClientLocked();
+ if (client != null) {
+ client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent);
+ }
+ }
+ }
+ }
+
+ private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ synchronized (mLock) {
+ mEnabled = enabled;
+ if (!mEnabled || resetSession) {
+ // Reset the session state
+ resetSessionLocked();
+ }
+ if (resetClient) {
+ // Reset connection to system
+ mServiceClient = null;
+ }
+ }
+ }
+
+ /**
+ * Sets a view as autofilled if the current value is the {code targetValue}.
+ *
+ * @param view The view that is to be autofilled
+ * @param targetValue The value we want to fill into view
+ */
+ private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
+ AutofillValue currentValue = view.getAutofillValue();
+ if (Objects.equals(currentValue, targetValue)) {
+ synchronized (mLock) {
+ if (mLastAutofilledData == null) {
+ mLastAutofilledData = new ParcelableMap(1);
+ }
+ mLastAutofilledData.put(getAutofillId(view), targetValue);
+ }
+ view.setAutofilled(true);
+ }
+ }
+
+ private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
+ synchronized (mLock) {
+ if (sessionId != mSessionId) {
+ return;
+ }
+
+ final AutofillClient client = getClientLocked();
+ if (client == null) {
+ return;
+ }
+
+ final int itemCount = ids.size();
+ int numApplied = 0;
+ ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
+ final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids));
+
+ for (int i = 0; i < itemCount; i++) {
+ final AutofillId id = ids.get(i);
+ final AutofillValue value = values.get(i);
+ final int viewId = id.getViewId();
+ final View view = views[i];
+ if (view == null) {
+ Log.w(TAG, "autofill(): no View with id " + viewId);
+ continue;
+ }
+ if (id.isVirtual()) {
+ if (virtualValues == null) {
+ // Most likely there will be just one view with virtual children.
+ virtualValues = new ArrayMap<>(1);
+ }
+ SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+ if (valuesByParent == null) {
+ // We don't know the size yet, but usually it will be just a few fields...
+ valuesByParent = new SparseArray<>(5);
+ virtualValues.put(view, valuesByParent);
+ }
+ valuesByParent.put(id.getVirtualChildId(), value);
+ } else {
+ // Mark the view as to be autofilled with 'value'
+ if (mLastAutofilledData == null) {
+ mLastAutofilledData = new ParcelableMap(itemCount - i);
+ }
+ mLastAutofilledData.put(id, value);
+
+ view.autofill(value);
+
+ // Set as autofilled if the values match now, e.g. when the value was updated
+ // synchronously.
+ // If autofill happens async, the view is set to autofilled in
+ // notifyValueChanged.
+ setAutofilledIfValuesIs(view, value);
+
+ numApplied++;
+ }
+ }
+
+ if (virtualValues != null) {
+ for (int i = 0; i < virtualValues.size(); i++) {
+ final View parent = virtualValues.keyAt(i);
+ final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+ parent.autofill(childrenValues);
+ numApplied += childrenValues.size();
+ }
+ }
+
+ final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
+ log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
+ log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
+ numApplied);
+ mMetricsLogger.write(log);
+ }
+ }
+
+ /**
+ * Set the tracked views.
+ *
+ * @param trackedIds The views to be tracked
+ * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
+ * @param fillableIds Views that might anchor FillUI.
+ */
+ private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
+ boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
+ synchronized (mLock) {
+ if (mEnabled && mSessionId == sessionId) {
+ if (saveOnAllViewsInvisible) {
+ mTrackedViews = new TrackedViews(trackedIds);
+ } else {
+ mTrackedViews = null;
+ }
+ if (fillableIds != null) {
+ if (mFillableIds == null) {
+ mFillableIds = new ArraySet<>(fillableIds.length);
+ }
+ for (AutofillId id : fillableIds) {
+ mFillableIds.add(id);
+ }
+ if (sVerbose) {
+ Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
+ + ", mFillableIds" + mFillableIds);
+ }
+ }
+ }
+ }
+ }
+
+ private void setSaveUiState(int sessionId, boolean shown) {
+ if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
+ synchronized (mLock) {
+ if (mSessionId != NO_SESSION) {
+ // Race condition: app triggered a new session after the previous session was
+ // finished but before server called setSaveUiState() - need to cancel the new
+ // session to avoid further inconsistent behavior.
+ Log.w(TAG, "setSaveUiState(" + sessionId + ", " + shown
+ + ") called on existing session " + mSessionId + "; cancelling it");
+ cancelSessionLocked();
+ }
+ if (shown) {
+ mSessionId = sessionId;
+ mState = STATE_SHOWING_SAVE_UI;
+ } else {
+ mSessionId = NO_SESSION;
+ mState = STATE_UNKNOWN;
+ }
+ }
+ }
+
+ private void requestHideFillUi(AutofillId id) {
+ final View anchor = findView(id);
+ if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
+ if (anchor == null) {
+ return;
+ }
+ requestHideFillUi(id, anchor);
+ }
+
+ private void requestHideFillUi(AutofillId id, View anchor) {
+
+ AutofillCallback callback = null;
+ synchronized (mLock) {
+ // We do not check the session id for two reasons:
+ // 1. If local and remote session id are off sync the UI would be stuck shown
+ // 2. There is a race between the user state being destroyed due the fill
+ // service being uninstalled and the UI being dismissed.
+ AutofillClient client = getClientLocked();
+ if (client != null) {
+ if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
+ callback = mCallback;
+ }
+ }
+ }
+
+ if (callback != null) {
+ if (id.isVirtual()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_HIDDEN);
+ } else {
+ callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
+ }
+ }
+ }
+
+ private void notifyNoFillUi(int sessionId, AutofillId id) {
+ final View anchor = findView(id);
+ if (anchor == null) {
+ return;
+ }
+
+ AutofillCallback callback = null;
+ synchronized (mLock) {
+ if (mSessionId == sessionId && getClientLocked() != null) {
+ callback = mCallback;
+ }
+ }
+
+ if (callback != null) {
+ if (id.isVirtual()) {
+ callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ } else {
+ callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ }
+
+ }
+ }
+
+ /**
+ * Get an array of viewIds from a List of {@link AutofillId}.
+ *
+ * @param autofillIds The autofill ids to convert
+ *
+ * @return The array of viewIds.
+ */
+ // TODO: move to Helper as static method
+ @NonNull private int[] getViewIds(@NonNull AutofillId[] autofillIds) {
+ final int numIds = autofillIds.length;
+ final int[] viewIds = new int[numIds];
+ for (int i = 0; i < numIds; i++) {
+ viewIds[i] = autofillIds[i].getViewId();
+ }
+
+ return viewIds;
+ }
+
+ // TODO: move to Helper as static method
+ @NonNull private int[] getViewIds(@NonNull List<AutofillId> autofillIds) {
+ final int numIds = autofillIds.size();
+ final int[] viewIds = new int[numIds];
+ for (int i = 0; i < numIds; i++) {
+ viewIds[i] = autofillIds.get(i).getViewId();
+ }
+
+ return viewIds;
+ }
+
+ /**
+ * Find a single view by its id.
+ *
+ * @param autofillId The autofill id of the view
+ *
+ * @return The view or {@code null} if view was not found
+ */
+ private View findView(@NonNull AutofillId autofillId) {
+ final AutofillClient client = getClientLocked();
+
+ if (client == null) {
+ return null;
+ }
+
+ return client.findViewByAutofillIdTraversal(autofillId.getViewId());
+ }
+
+ /** @hide */
+ public boolean hasAutofillFeature() {
+ return mService != null;
+ }
+
+ /** @hide */
+ public void onPendingSaveUi(int operation, IBinder token) {
+ if (sVerbose) Log.v(TAG, "onPendingSaveUi(" + operation + "): " + token);
+
+ synchronized (mLock) {
+ try {
+ mService.onPendingSaveUi(operation, token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /** @hide */
+ public void dump(String outerPrefix, PrintWriter pw) {
+ pw.print(outerPrefix); pw.println("AutofillManager:");
+ final String pfx = outerPrefix + " ";
+ pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
+ pw.print(pfx); pw.print("state: "); pw.println(
+ DebugUtils.flagsToString(AutofillManager.class, "STATE_", mState));
+ pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
+ pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
+ pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
+ pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData);
+ pw.print(pfx); pw.print("tracked views: ");
+ if (mTrackedViews == null) {
+ pw.println("null");
+ } else {
+ final String pfx2 = pfx + " ";
+ pw.println();
+ pw.print(pfx2); pw.print("visible:"); pw.println(mTrackedViews.mVisibleTrackedIds);
+ pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
+ }
+ pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
+ }
+
+ private boolean isActiveLocked() {
+ return mState == STATE_ACTIVE;
+ }
+
+ private void post(Runnable runnable) {
+ final AutofillClient client = getClientLocked();
+ if (client == null) {
+ if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
+ return;
+ }
+ client.runOnUiThread(runnable);
+ }
+
+ /**
+ * View tracking information. Once all tracked views become invisible the session is finished.
+ */
+ private class TrackedViews {
+ /** Visible tracked views */
+ @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
+
+ /** Invisible tracked views */
+ @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
+
+ /**
+ * Check if set is null or value is in set.
+ *
+ * @param set The set or null (== empty set)
+ * @param value The value that might be in the set
+ *
+ * @return {@code true} iff set is not empty and value is in set
+ */
+ // TODO: move to Helper as static method
+ private <T> boolean isInSet(@Nullable ArraySet<T> set, T value) {
+ return set != null && set.contains(value);
+ }
+
+ /**
+ * Add a value to a set. If set is null, create a new set.
+ *
+ * @param set The set or null (== empty set)
+ * @param valueToAdd The value to add
+ *
+ * @return The set including the new value. If set was {@code null}, a set containing only
+ * the new value.
+ */
+ // TODO: move to Helper as static method
+ @NonNull
+ private <T> ArraySet<T> addToSet(@Nullable ArraySet<T> set, T valueToAdd) {
+ if (set == null) {
+ set = new ArraySet<>(1);
+ }
+
+ set.add(valueToAdd);
+
+ return set;
+ }
+
+ /**
+ * Remove a value from a set.
+ *
+ * @param set The set or null (== empty set)
+ * @param valueToRemove The value to remove
+ *
+ * @return The set without the removed value. {@code null} if set was null, or is empty
+ * after removal.
+ */
+ // TODO: move to Helper as static method
+ @Nullable
+ private <T> ArraySet<T> removeFromSet(@Nullable ArraySet<T> set, T valueToRemove) {
+ if (set == null) {
+ return null;
+ }
+
+ set.remove(valueToRemove);
+
+ if (set.isEmpty()) {
+ return null;
+ }
+
+ return set;
+ }
+
+ /**
+ * Set the tracked views.
+ *
+ * @param trackedIds The views to be tracked
+ */
+ TrackedViews(@Nullable AutofillId[] trackedIds) {
+ final AutofillClient client = getClientLocked();
+ if (trackedIds != null && client != null) {
+ final boolean[] isVisible;
+
+ if (client.isVisibleForAutofill()) {
+ isVisible = client.getViewVisibility(getViewIds(trackedIds));
+ } else {
+ // All false
+ isVisible = new boolean[trackedIds.length];
+ }
+
+ final int numIds = trackedIds.length;
+ for (int i = 0; i < numIds; i++) {
+ final AutofillId id = trackedIds[i];
+
+ if (isVisible[i]) {
+ mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
+ } else {
+ mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
+ }
+ }
+ }
+
+ if (sVerbose) {
+ Log.v(TAG, "TrackedViews(trackedIds=" + trackedIds + "): "
+ + " mVisibleTrackedIds=" + mVisibleTrackedIds
+ + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
+ }
+
+ if (mVisibleTrackedIds == null) {
+ finishSessionLocked();
+ }
+ }
+
+ /**
+ * Called when a {@link View view's} visibility changes.
+ *
+ * @param id the id of the view/virtual view whose visibility changed.
+ * @param isVisible visible if the view is visible in the view hierarchy.
+ */
+ void notifyViewVisibilityChanged(@NonNull AutofillId id, boolean isVisible) {
+ AutofillClient client = getClientLocked();
+
+ if (sDebug) {
+ Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible="
+ + isVisible);
+ }
+
+ if (client != null && client.isVisibleForAutofill()) {
+ if (isVisible) {
+ if (isInSet(mInvisibleTrackedIds, id)) {
+ mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
+ mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
+ }
+ } else {
+ if (isInSet(mVisibleTrackedIds, id)) {
+ mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
+ mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
+ }
+ }
+ }
+
+ if (mVisibleTrackedIds == null) {
+ if (sVerbose) {
+ Log.v(TAG, "No more visible ids. Invisibile = " + mInvisibleTrackedIds);
+ }
+ finishSessionLocked();
+ }
+ }
+
+ /**
+ * Called once the client becomes visible.
+ *
+ * @see AutofillClient#isVisibleForAutofill()
+ */
+ void onVisibleForAutofillLocked() {
+ // The visibility of the views might have changed while the client was not be visible,
+ // hence update the visibility state for all views.
+ AutofillClient client = getClientLocked();
+ ArraySet<AutofillId> updatedVisibleTrackedIds = null;
+ ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
+ if (client != null) {
+ if (mInvisibleTrackedIds != null) {
+ final ArrayList<AutofillId> orderedInvisibleIds =
+ new ArrayList<>(mInvisibleTrackedIds);
+ final boolean[] isVisible = client.getViewVisibility(
+ getViewIds(orderedInvisibleIds));
+
+ final int numInvisibleTrackedIds = orderedInvisibleIds.size();
+ for (int i = 0; i < numInvisibleTrackedIds; i++) {
+ final AutofillId id = orderedInvisibleIds.get(i);
+ if (isVisible[i]) {
+ updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
+
+ if (sDebug) {
+ Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
+ }
+ } else {
+ updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
+ }
+ }
+ }
+
+ if (mVisibleTrackedIds != null) {
+ final ArrayList<AutofillId> orderedVisibleIds =
+ new ArrayList<>(mVisibleTrackedIds);
+ final boolean[] isVisible = client.getViewVisibility(
+ getViewIds(orderedVisibleIds));
+
+ final int numVisibleTrackedIds = orderedVisibleIds.size();
+ for (int i = 0; i < numVisibleTrackedIds; i++) {
+ final AutofillId id = orderedVisibleIds.get(i);
+
+ if (isVisible[i]) {
+ updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
+ } else {
+ updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
+
+ if (sDebug) {
+ Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
+ }
+ }
+ }
+ }
+
+ mInvisibleTrackedIds = updatedInvisibleTrackedIds;
+ mVisibleTrackedIds = updatedVisibleTrackedIds;
+ }
+
+ if (mVisibleTrackedIds == null) {
+ finishSessionLocked();
+ }
+ }
+ }
+
+ /**
+ * Callback for autofill related events.
+ *
+ * <p>Typically used for applications that display their own "auto-complete" views, so they can
+ * enable / disable such views when the autofill UI affordance is shown / hidden.
+ */
+ public abstract static class AutofillCallback {
+
+ /** @hide */
+ @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutofillEventType {}
+
+ /**
+ * The autofill input UI affordance associated with the view was shown.
+ *
+ * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
+ * should be hidden upon receiving this event.
+ */
+ public static final int EVENT_INPUT_SHOWN = 1;
+
+ /**
+ * The autofill input UI affordance associated with the view was hidden.
+ *
+ * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
+ * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
+ */
+ public static final int EVENT_INPUT_HIDDEN = 2;
+
+ /**
+ * The autofill input UI affordance associated with the view isn't shown because
+ * autofill is not available.
+ *
+ * <p>If the view provides its own auto-complete UI affordance but was not displaying it
+ * to avoid flickering, it could shown it upon receiving this event.
+ */
+ public static final int EVENT_INPUT_UNAVAILABLE = 3;
+
+ /**
+ * Called after a change in the autofill state associated with a view.
+ *
+ * @param view view associated with the change.
+ *
+ * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
+ */
+ public void onAutofillEvent(@NonNull View view, @AutofillEventType int event) {
+ }
+
+ /**
+ * Called after a change in the autofill state associated with a virtual view.
+ *
+ * @param view parent view associated with the change.
+ * @param virtualId id identifying the virtual child inside the parent view.
+ *
+ * @param event currently either {@link #EVENT_INPUT_SHOWN} or {@link #EVENT_INPUT_HIDDEN}.
+ */
+ public void onAutofillEvent(@NonNull View view, int virtualId,
+ @AutofillEventType int event) {
+ }
+ }
+
+ private static final class AutofillManagerClient extends IAutoFillManagerClient.Stub {
+ private final WeakReference<AutofillManager> mAfm;
+
+ AutofillManagerClient(AutofillManager autofillManager) {
+ mAfm = new WeakReference<>(autofillManager);
+ }
+
+ @Override
+ public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.setState(enabled, resetSession, resetClient));
+ }
+ }
+
+ @Override
+ public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.autofill(sessionId, ids, values));
+ }
+ }
+
+ @Override
+ public void authenticate(int sessionId, int authenticationId, IntentSender intent,
+ Intent fillInIntent) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent));
+ }
+ }
+
+ @Override
+ public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
+ presenter));
+ }
+ }
+
+ @Override
+ public void requestHideFillUi(int sessionId, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.requestHideFillUi(id));
+ }
+ }
+
+ @Override
+ public void notifyNoFillUi(int sessionId, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.notifyNoFillUi(sessionId, id));
+ }
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intentSender, Intent intent) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> {
+ try {
+ afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setTrackedViews(int sessionId, AutofillId[] ids,
+ boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() ->
+ afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
+ );
+ }
+ }
+
+ @Override
+ public void setSaveUiState(int sessionId, boolean shown) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() ->afm.setSaveUiState(sessionId, shown));
+ }
+ }
+ }
+}
diff --git a/android/view/autofill/AutofillManagerInternal.java b/android/view/autofill/AutofillManagerInternal.java
new file mode 100644
index 00000000..fc5d306d
--- /dev/null
+++ b/android/view/autofill/AutofillManagerInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 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.autofill;
+
+/**
+ * Autofill Manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AutofillManagerInternal {
+
+ /**
+ * Notifies the manager that the back key was pressed.
+ */
+ public abstract void onBackKeyPressed();
+}
diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java
new file mode 100644
index 00000000..5f476380
--- /dev/null
+++ b/android/view/autofill/AutofillPopupWindow.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2017 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.autofill;
+
+import static android.view.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.transition.Transition;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.PopupWindow;
+
+/**
+ * Custom {@link PopupWindow} used to isolate its content from the autofilled app - the
+ * UI is rendered in a framework process, but it's controlled by the app.
+ *
+ * TODO(b/34943932): use an app surface control solution.
+ *
+ * @hide
+ */
+public class AutofillPopupWindow extends PopupWindow {
+
+ private static final String TAG = "AutofillPopupWindow";
+
+ private final WindowPresenter mWindowPresenter;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ /**
+ * Creates a popup window with a presenter owning the window and responsible for
+ * showing/hiding/updating the backing window. This can be useful of the window is
+ * being shown by another process while the popup logic is in the process hosting
+ * the anchor view.
+ * <p>
+ * Using this constructor means that the presenter completely owns the content of
+ * the window and the following methods manipulating the window content shouldn't
+ * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)},
+ * {@link #getExitTransition()}, {@link #setExitTransition(Transition)},
+ * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()},
+ * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()},
+ * {@link #setElevation(float)}, ({@link #getAnimationStyle()},
+ * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p>
+ */
+ public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
+ mWindowPresenter = new WindowPresenter(presenter);
+
+ setOutsideTouchable(true);
+ setInputMethodMode(INPUT_METHOD_NEEDED);
+ }
+
+ @Override
+ protected boolean hasContentView() {
+ return true;
+ }
+
+ @Override
+ protected boolean hasDecorView() {
+ return true;
+ }
+
+ @Override
+ protected LayoutParams getDecorViewLayoutParams() {
+ return mWindowLayoutParams;
+ }
+
+ /**
+ * The effective {@code update} method that should be called by its clients.
+ */
+ public void update(View anchor, int offsetX, int offsetY, int width, int height,
+ Rect virtualBounds) {
+ // If we are showing the popup for a virtual view we use a fake view which
+ // delegates to the anchor but present itself with the same bounds as the
+ // virtual view. This ensures that the location logic in popup works
+ // symmetrically when the dropdown is below and above the anchor.
+ final View actualAnchor;
+ if (virtualBounds != null) {
+ actualAnchor = new View(anchor.getContext()) {
+ @Override
+ public void getLocationOnScreen(int[] location) {
+ location[0] = virtualBounds.left;
+ location[1] = virtualBounds.top;
+ }
+
+ @Override
+ public int getAccessibilityViewId() {
+ return anchor.getAccessibilityViewId();
+ }
+
+ @Override
+ public ViewTreeObserver getViewTreeObserver() {
+ return anchor.getViewTreeObserver();
+ }
+
+ @Override
+ public IBinder getApplicationWindowToken() {
+ return anchor.getApplicationWindowToken();
+ }
+
+ @Override
+ public View getRootView() {
+ return anchor.getRootView();
+ }
+
+ @Override
+ public int getLayoutDirection() {
+ return anchor.getLayoutDirection();
+ }
+
+ @Override
+ public void getWindowDisplayFrame(Rect outRect) {
+ anchor.getWindowDisplayFrame(outRect);
+ }
+
+ @Override
+ public void addOnAttachStateChangeListener(
+ OnAttachStateChangeListener listener) {
+ anchor.addOnAttachStateChangeListener(listener);
+ }
+
+ @Override
+ public void removeOnAttachStateChangeListener(
+ OnAttachStateChangeListener listener) {
+ anchor.removeOnAttachStateChangeListener(listener);
+ }
+
+ @Override
+ public boolean isAttachedToWindow() {
+ return anchor.isAttachedToWindow();
+ }
+
+ @Override
+ public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
+ return anchor.requestRectangleOnScreen(rectangle, immediate);
+ }
+
+ @Override
+ public IBinder getWindowToken() {
+ return anchor.getWindowToken();
+ }
+ };
+
+ actualAnchor.setLeftTopRightBottom(
+ virtualBounds.left, virtualBounds.top,
+ virtualBounds.right, virtualBounds.bottom);
+ actualAnchor.setScrollX(anchor.getScrollX());
+ actualAnchor.setScrollY(anchor.getScrollY());
+ } else {
+ actualAnchor = anchor;
+ }
+
+ if (!isShowing()) {
+ setWidth(width);
+ setHeight(height);
+ showAsDropDown(actualAnchor, offsetX, offsetY);
+ } else {
+ update(actualAnchor, offsetX, offsetY, width, height);
+ }
+ }
+
+ @Override
+ protected void update(View anchor, WindowManager.LayoutParams params) {
+ final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
+ : View.LAYOUT_DIRECTION_LOCALE;
+ mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
+ layoutDirection);
+ }
+
+ @Override
+ public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
+ if (sVerbose) {
+ Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff
+ + ", isShowing(): " + isShowing());
+ }
+ if (isShowing()) {
+ return;
+ }
+
+ setShowing(true);
+ setDropDown(true);
+ attachToAnchor(anchor, xoff, yoff, gravity);
+ final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
+ anchor.getWindowToken());
+ final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
+ p.width, p.height, gravity, getAllowScrollingAnchorParent());
+ updateAboveAnchor(aboveAnchor);
+ p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+ p.packageName = anchor.getContext().getPackageName();
+ mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
+ anchor.getLayoutDirection());
+ return;
+ }
+
+ @Override
+ public void dismiss() {
+ if (!isShowing() || isTransitioningToDismiss()) {
+ return;
+ }
+
+ setShowing(false);
+ setTransitioningToDismiss(true);
+
+ mWindowPresenter.hide(getTransitionEpicenter());
+ detachFromAnchor();
+ if (getOnDismissListener() != null) {
+ getOnDismissListener().onDismiss();
+ }
+ }
+
+ @Override
+ public int getAnimationStyle() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Drawable getBackground() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public View getContentView() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public float getElevation() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Transition getEnterTransition() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Transition getExitTransition() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setAnimationStyle(int animationStyle) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setBackgroundDrawable(Drawable background) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setContentView(View contentView) {
+ if (contentView != null) {
+ throw new IllegalStateException("You can't call this!");
+ }
+ }
+
+ @Override
+ public void setElevation(float elevation) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setEnterTransition(Transition enterTransition) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setExitTransition(Transition exitTransition) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setTouchInterceptor(OnTouchListener l) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ /**
+ * Contract between the popup window and a presenter that is responsible for
+ * showing/hiding/updating the actual window.
+ *
+ * <p>This can be useful if the anchor is in one process and the backing window is owned by
+ * another process.
+ */
+ private class WindowPresenter {
+ final IAutofillWindowPresenter mPresenter;
+
+ WindowPresenter(IAutofillWindowPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ /**
+ * Shows the backing window.
+ *
+ * @param p The window layout params.
+ * @param transitionEpicenter The transition epicenter if animating.
+ * @param fitsSystemWindows Whether the content view should account for system decorations.
+ * @param layoutDirection The content layout direction to be consistent with the anchor.
+ */
+ void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
+ int layoutDirection) {
+ try {
+ mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error showing fill window", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Hides the backing window.
+ *
+ * @param transitionEpicenter The transition epicenter if animating.
+ */
+ void hide(Rect transitionEpicenter) {
+ try {
+ mPresenter.hide(transitionEpicenter);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error hiding fill window", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/android/view/autofill/AutofillValue.java b/android/view/autofill/AutofillValue.java
new file mode 100644
index 00000000..3beae11c
--- /dev/null
+++ b/android/view/autofill/AutofillValue.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2017 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.autofill;
+
+import static android.view.View.AUTOFILL_TYPE_DATE;
+import static android.view.View.AUTOFILL_TYPE_LIST;
+import static android.view.View.AUTOFILL_TYPE_TEXT;
+import static android.view.View.AUTOFILL_TYPE_TOGGLE;
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Abstracts how a {@link View} can be autofilled by an
+ * {@link android.service.autofill.AutofillService}.
+ *
+ * <p>Each {@link AutofillValue} is associated with a {@code type}, as defined by
+ * {@link View#getAutofillType()}.
+ */
+public final class AutofillValue implements Parcelable {
+ private final @View.AutofillType int mType;
+ private final @NonNull Object mValue;
+
+ private AutofillValue(@View.AutofillType int type, @NonNull Object value) {
+ mType = type;
+ mValue = value;
+ }
+
+ /**
+ * Gets the value to autofill a text field.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+ *
+ * @throws IllegalStateException if the value is not a text value
+ */
+ @NonNull public CharSequence getTextValue() {
+ Preconditions.checkState(isText(), "value must be a text value, not type=" + mType);
+ return (CharSequence) mValue;
+ }
+
+ /**
+ * Checks is this is a text value.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+ */
+ public boolean isText() {
+ return mType == AUTOFILL_TYPE_TEXT;
+ }
+
+ /**
+ * Gets the value to autofill a toggable field.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+ *
+ * @throws IllegalStateException if the value is not a toggle value
+ */
+ public boolean getToggleValue() {
+ Preconditions.checkState(isToggle(), "value must be a toggle value, not type=" + mType);
+ return (Boolean) mValue;
+ }
+
+ /**
+ * Checks is this is a toggle value.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+ */
+ public boolean isToggle() {
+ return mType == AUTOFILL_TYPE_TOGGLE;
+ }
+
+ /**
+ * Gets the value to autofill a selection list field.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+ *
+ * @throws IllegalStateException if the value is not a list value
+ */
+ public int getListValue() {
+ Preconditions.checkState(isList(), "value must be a list value, not type=" + mType);
+ return (Integer) mValue;
+ }
+
+ /**
+ * Checks is this is a list value.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+ */
+ public boolean isList() {
+ return mType == AUTOFILL_TYPE_LIST;
+ }
+
+ /**
+ * Gets the value to autofill a date field.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+ *
+ * @throws IllegalStateException if the value is not a date value
+ */
+ public long getDateValue() {
+ Preconditions.checkState(isDate(), "value must be a date value, not type=" + mType);
+ return (Long) mValue;
+ }
+
+ /**
+ * Checks is this is a date value.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+ */
+ public boolean isDate() {
+ return mType == AUTOFILL_TYPE_DATE;
+ }
+
+ /**
+ * Used to define whether a field is empty so it's not sent to service on save.
+ *
+ * <p>Only applies to some types, like text.
+ *
+ * @hide
+ */
+ public boolean isEmpty() {
+ return isText() && ((CharSequence) mValue).length() == 0;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int hashCode() {
+ return mType + mValue.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ final AutofillValue other = (AutofillValue) obj;
+
+ if (mType != other.mType) return false;
+
+ if (isText()) {
+ return mValue.toString().equals(other.mValue.toString());
+ } else {
+ return Objects.equals(mValue, other.mValue);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ final StringBuilder string = new StringBuilder()
+ .append("[type=").append(mType)
+ .append(", value=");
+ if (isText()) {
+ string.append(((CharSequence) mValue).length()).append("_chars");
+ } else {
+ string.append(mValue);
+ }
+ return string.append(']').toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+
+ switch (mType) {
+ case AUTOFILL_TYPE_TEXT:
+ parcel.writeCharSequence((CharSequence) mValue);
+ break;
+ case AUTOFILL_TYPE_TOGGLE:
+ parcel.writeInt((Boolean) mValue ? 1 : 0);
+ break;
+ case AUTOFILL_TYPE_LIST:
+ parcel.writeInt((Integer) mValue);
+ break;
+ case AUTOFILL_TYPE_DATE:
+ parcel.writeLong((Long) mValue);
+ break;
+ }
+ }
+
+ private AutofillValue(@NonNull Parcel parcel) {
+ mType = parcel.readInt();
+
+ switch (mType) {
+ case AUTOFILL_TYPE_TEXT:
+ mValue = parcel.readCharSequence();
+ break;
+ case AUTOFILL_TYPE_TOGGLE:
+ int rawValue = parcel.readInt();
+ mValue = rawValue != 0;
+ break;
+ case AUTOFILL_TYPE_LIST:
+ mValue = parcel.readInt();
+ break;
+ case AUTOFILL_TYPE_DATE:
+ mValue = parcel.readLong();
+ break;
+ default:
+ throw new IllegalArgumentException("type=" + mType + " not valid");
+ }
+ }
+
+ public static final Parcelable.Creator<AutofillValue> CREATOR =
+ new Parcelable.Creator<AutofillValue>() {
+ @Override
+ public AutofillValue createFromParcel(Parcel source) {
+ return new AutofillValue(source);
+ }
+
+ @Override
+ public AutofillValue[] newArray(int size) {
+ return new AutofillValue[size];
+ }
+ };
+
+ ////////////////////
+ // Factory methods //
+ ////////////////////
+
+ /**
+ * Creates a new {@link AutofillValue} to autofill a {@link View} representing a text field.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
+ */
+ public static AutofillValue forText(@Nullable CharSequence value) {
+ return value == null ? null : new AutofillValue(AUTOFILL_TYPE_TEXT,
+ TextUtils.trimNoCopySpans(value));
+ }
+
+ /**
+ * Creates a new {@link AutofillValue} to autofill a {@link View} representing a toggable
+ * field.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
+ */
+ public static AutofillValue forToggle(boolean value) {
+ return new AutofillValue(AUTOFILL_TYPE_TOGGLE, value);
+ }
+
+ /**
+ * Creates a new {@link AutofillValue} to autofill a {@link View} representing a selection
+ * list.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
+ */
+ public static AutofillValue forList(int value) {
+ return new AutofillValue(AUTOFILL_TYPE_LIST, value);
+ }
+
+ /**
+ * Creates a new {@link AutofillValue} to autofill a {@link View} representing a date.
+ *
+ * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
+ */
+ public static AutofillValue forDate(long value) {
+ return new AutofillValue(AUTOFILL_TYPE_DATE, value);
+ }
+}
diff --git a/android/view/autofill/Helper.java b/android/view/autofill/Helper.java
new file mode 100644
index 00000000..829e7f3a
--- /dev/null
+++ b/android/view/autofill/Helper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 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.autofill;
+
+import android.os.Bundle;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+/** @hide */
+public final class Helper {
+
+ // Debug-level flags are defined when service is bound.
+ public static boolean sDebug = false;
+ public static boolean sVerbose = false;
+
+ public static final String REDACTED = "[REDACTED]";
+
+ static StringBuilder append(StringBuilder builder, Bundle bundle) {
+ if (bundle == null || !sDebug) {
+ builder.append("N/A");
+ } else if (!sVerbose) {
+ builder.append(REDACTED);
+ } else {
+ final Set<String> keySet = bundle.keySet();
+ builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
+ for (String key : keySet) {
+ final Object value = bundle.get(key);
+ builder.append(' ').append(key).append('=');
+ builder.append((value instanceof Object[])
+ ? Arrays.toString((Objects[]) value) : value);
+ }
+ builder.append(']');
+ }
+ return builder;
+ }
+
+ private Helper() {
+ throw new UnsupportedOperationException("contains static members only");
+ }
+}
diff --git a/android/view/autofill/ParcelableMap.java b/android/view/autofill/ParcelableMap.java
new file mode 100644
index 00000000..f97b1a0d
--- /dev/null
+++ b/android/view/autofill/ParcelableMap.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.autofill;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A parcelable HashMap for {@link AutofillId} and {@link AutofillValue}
+ *
+ * {@hide}
+ */
+class ParcelableMap extends HashMap<AutofillId, AutofillValue> implements Parcelable {
+ ParcelableMap(int size) {
+ super(size);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(size());
+
+ for (Map.Entry<AutofillId, AutofillValue> entry : entrySet()) {
+ dest.writeParcelable(entry.getKey(), 0);
+ dest.writeParcelable(entry.getValue(), 0);
+ }
+ }
+
+ public static final Parcelable.Creator<ParcelableMap> CREATOR =
+ new Parcelable.Creator<ParcelableMap>() {
+ @Override
+ public ParcelableMap createFromParcel(Parcel source) {
+ int size = source.readInt();
+
+ ParcelableMap map = new ParcelableMap(size);
+
+ for (int i = 0; i < size; i++) {
+ AutofillId key = source.readParcelable(null);
+ AutofillValue value = source.readParcelable(null);
+
+ map.put(key, value);
+ }
+
+ return map;
+ }
+
+ @Override
+ public ParcelableMap[] newArray(int size) {
+ return new ParcelableMap[size];
+ }
+ };
+}
diff --git a/android/view/inputmethod/BaseInputConnection.java b/android/view/inputmethod/BaseInputConnection.java
new file mode 100644
index 00000000..5f7a0f78
--- /dev/null
+++ b/android/view/inputmethod/BaseInputConnection.java
@@ -0,0 +1,860 @@
+/*
+ * Copyright (C) 2008 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.inputmethod;
+
+import android.annotation.CallSuper;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+
+class ComposingText implements NoCopySpan {
+}
+
+/**
+ * Base class for implementors of the InputConnection interface, taking care
+ * of most of the common behavior for providing a connection to an Editable.
+ * Implementors of this class will want to be sure to implement
+ * {@link #getEditable} to provide access to their own editable object, and
+ * to refer to the documentation in {@link InputConnection}.
+ */
+public class BaseInputConnection implements InputConnection {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "BaseInputConnection";
+ static final Object COMPOSING = new ComposingText();
+
+ /** @hide */
+ protected final InputMethodManager mIMM;
+ final View mTargetView;
+ final boolean mDummyMode;
+
+ private Object[] mDefaultComposingSpans;
+
+ Editable mEditable;
+ KeyCharacterMap mKeyCharacterMap;
+
+ BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
+ mIMM = mgr;
+ mTargetView = null;
+ mDummyMode = !fullEditor;
+ }
+
+ public BaseInputConnection(View targetView, boolean fullEditor) {
+ mIMM = (InputMethodManager)targetView.getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ mTargetView = targetView;
+ mDummyMode = !fullEditor;
+ }
+
+ public static final void removeComposingSpans(Spannable text) {
+ text.removeSpan(COMPOSING);
+ Object[] sps = text.getSpans(0, text.length(), Object.class);
+ if (sps != null) {
+ for (int i=sps.length-1; i>=0; i--) {
+ Object o = sps[i];
+ if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
+ text.removeSpan(o);
+ }
+ }
+ }
+ }
+
+ public static void setComposingSpans(Spannable text) {
+ setComposingSpans(text, 0, text.length());
+ }
+
+ /** @hide */
+ public static void setComposingSpans(Spannable text, int start, int end) {
+ final Object[] sps = text.getSpans(start, end, Object.class);
+ if (sps != null) {
+ for (int i=sps.length-1; i>=0; i--) {
+ final Object o = sps[i];
+ if (o == COMPOSING) {
+ text.removeSpan(o);
+ continue;
+ }
+
+ final int fl = text.getSpanFlags(o);
+ if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK))
+ != (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
+ text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
+ (fl & ~Spanned.SPAN_POINT_MARK_MASK)
+ | Spanned.SPAN_COMPOSING
+ | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ }
+
+ text.setSpan(COMPOSING, start, end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+ }
+
+ public static int getComposingSpanStart(Spannable text) {
+ return text.getSpanStart(COMPOSING);
+ }
+
+ public static int getComposingSpanEnd(Spannable text) {
+ return text.getSpanEnd(COMPOSING);
+ }
+
+ /**
+ * Return the target of edit operations. The default implementation
+ * returns its own fake editable that is just used for composing text;
+ * subclasses that are real text editors should override this and
+ * supply their own.
+ */
+ public Editable getEditable() {
+ if (mEditable == null) {
+ mEditable = Editable.Factory.getInstance().newEditable("");
+ Selection.setSelection(mEditable, 0);
+ }
+ return mEditable;
+ }
+
+ /**
+ * Default implementation does nothing.
+ */
+ public boolean beginBatchEdit() {
+ return false;
+ }
+
+ /**
+ * Default implementation does nothing.
+ */
+ public boolean endBatchEdit() {
+ return false;
+ }
+
+ /**
+ * Default implementation calls {@link #finishComposingText()}.
+ */
+ @CallSuper
+ public void closeConnection() {
+ finishComposingText();
+ }
+
+ /**
+ * Default implementation uses
+ * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
+ * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
+ */
+ public boolean clearMetaKeyStates(int states) {
+ final Editable content = getEditable();
+ if (content == null) return false;
+ MetaKeyKeyListener.clearMetaKeyState(content, states);
+ return true;
+ }
+
+ /**
+ * Default implementation does nothing and returns false.
+ */
+ public boolean commitCompletion(CompletionInfo text) {
+ return false;
+ }
+
+ /**
+ * Default implementation does nothing and returns false.
+ */
+ public boolean commitCorrection(CorrectionInfo correctionInfo) {
+ return false;
+ }
+
+ /**
+ * Default implementation replaces any existing composing text with
+ * the given text. In addition, only if dummy mode, a key event is
+ * sent for the new text and the current editable buffer cleared.
+ */
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ if (DEBUG) Log.v(TAG, "commitText " + text);
+ replaceText(text, newCursorPosition, false);
+ sendCurrentText();
+ return true;
+ }
+
+ /**
+ * The default implementation performs the deletion around the current selection position of the
+ * editable text.
+ *
+ * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
+ */
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+ + " / " + afterLength);
+ final Editable content = getEditable();
+ if (content == null) return false;
+
+ beginBatchEdit();
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ // Ignore the composing text.
+ int ca = getComposingSpanStart(content);
+ int cb = getComposingSpanEnd(content);
+ if (cb < ca) {
+ int tmp = ca;
+ ca = cb;
+ cb = tmp;
+ }
+ if (ca != -1 && cb != -1) {
+ if (ca < a) a = ca;
+ if (cb > b) b = cb;
+ }
+
+ int deleted = 0;
+
+ if (beforeLength > 0) {
+ int start = a - beforeLength;
+ if (start < 0) start = 0;
+ content.delete(start, a);
+ deleted = a - start;
+ }
+
+ if (afterLength > 0) {
+ b = b - deleted;
+
+ int end = b + afterLength;
+ if (end > content.length()) end = content.length();
+
+ content.delete(b, end);
+ }
+
+ endBatchEdit();
+
+ return true;
+ }
+
+ private static int INVALID_INDEX = -1;
+ private static int findIndexBackward(final CharSequence cs, final int from,
+ final int numCodePoints) {
+ int currentIndex = from;
+ boolean waitingHighSurrogate = false;
+ final int N = cs.length();
+ if (currentIndex < 0 || N < currentIndex) {
+ return INVALID_INDEX; // The starting point is out of range.
+ }
+ if (numCodePoints < 0) {
+ return INVALID_INDEX; // Basically this should not happen.
+ }
+ int remainingCodePoints = numCodePoints;
+ while (true) {
+ if (remainingCodePoints == 0) {
+ return currentIndex; // Reached to the requested length in code points.
+ }
+
+ --currentIndex;
+ if (currentIndex < 0) {
+ if (waitingHighSurrogate) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ return 0; // Reached to the beginning of the text w/o any invalid surrogate pair.
+ }
+ final char c = cs.charAt(currentIndex);
+ if (waitingHighSurrogate) {
+ if (!java.lang.Character.isHighSurrogate(c)) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ waitingHighSurrogate = false;
+ --remainingCodePoints;
+ continue;
+ }
+ if (!java.lang.Character.isSurrogate(c)) {
+ --remainingCodePoints;
+ continue;
+ }
+ if (java.lang.Character.isHighSurrogate(c)) {
+ return INVALID_INDEX; // A invalid surrogate pair is found.
+ }
+ waitingHighSurrogate = true;
+ }
+ }
+
+ private static int findIndexForward(final CharSequence cs, final int from,
+ final int numCodePoints) {
+ int currentIndex = from;
+ boolean waitingLowSurrogate = false;
+ final int N = cs.length();
+ if (currentIndex < 0 || N < currentIndex) {
+ return INVALID_INDEX; // The starting point is out of range.
+ }
+ if (numCodePoints < 0) {
+ return INVALID_INDEX; // Basically this should not happen.
+ }
+ int remainingCodePoints = numCodePoints;
+
+ while (true) {
+ if (remainingCodePoints == 0) {
+ return currentIndex; // Reached to the requested length in code points.
+ }
+
+ if (currentIndex >= N) {
+ if (waitingLowSurrogate) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ return N; // Reached to the end of the text w/o any invalid surrogate pair.
+ }
+ final char c = cs.charAt(currentIndex);
+ if (waitingLowSurrogate) {
+ if (!java.lang.Character.isLowSurrogate(c)) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ --remainingCodePoints;
+ waitingLowSurrogate = false;
+ ++currentIndex;
+ continue;
+ }
+ if (!java.lang.Character.isSurrogate(c)) {
+ --remainingCodePoints;
+ ++currentIndex;
+ continue;
+ }
+ if (java.lang.Character.isLowSurrogate(c)) {
+ return INVALID_INDEX; // A invalid surrogate pair is found.
+ }
+ waitingLowSurrogate = true;
+ ++currentIndex;
+ }
+ }
+
+ /**
+ * The default implementation performs the deletion around the current selection position of the
+ * editable text.
+ * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
+ */
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+ + " / " + afterLength);
+ final Editable content = getEditable();
+ if (content == null) return false;
+
+ beginBatchEdit();
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ // Ignore the composing text.
+ int ca = getComposingSpanStart(content);
+ int cb = getComposingSpanEnd(content);
+ if (cb < ca) {
+ int tmp = ca;
+ ca = cb;
+ cb = tmp;
+ }
+ if (ca != -1 && cb != -1) {
+ if (ca < a) a = ca;
+ if (cb > b) b = cb;
+ }
+
+ if (a >= 0 && b >= 0) {
+ final int start = findIndexBackward(content, a, Math.max(beforeLength, 0));
+ if (start != INVALID_INDEX) {
+ final int end = findIndexForward(content, b, Math.max(afterLength, 0));
+ if (end != INVALID_INDEX) {
+ final int numDeleteBefore = a - start;
+ if (numDeleteBefore > 0) {
+ content.delete(start, a);
+ }
+ final int numDeleteAfter = end - b;
+ if (numDeleteAfter > 0) {
+ content.delete(b - numDeleteBefore, end - numDeleteBefore);
+ }
+ }
+ }
+ // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX,
+ // but the truth is that IInputConnectionWrapper running in the middle of IPC calls
+ // always returns true to the IME without waiting for the completion of this method as
+ // IInputConnectionWrapper#isAtive() returns true. This is actually why some methods
+ // including this method look like asynchronous calls from the IME.
+ }
+
+ endBatchEdit();
+
+ return true;
+ }
+
+ /**
+ * The default implementation removes the composing state from the
+ * current editable text. In addition, only if dummy mode, a key event is
+ * sent for the new text and the current editable buffer cleared.
+ */
+ public boolean finishComposingText() {
+ if (DEBUG) Log.v(TAG, "finishComposingText");
+ final Editable content = getEditable();
+ if (content != null) {
+ beginBatchEdit();
+ removeComposingSpans(content);
+ // Note: sendCurrentText does nothing unless mDummyMode is set
+ sendCurrentText();
+ endBatchEdit();
+ }
+ return true;
+ }
+
+ /**
+ * The default implementation uses TextUtils.getCapsMode to get the
+ * cursor caps mode for the current selection position in the editable
+ * text, unless in dummy mode in which case 0 is always returned.
+ */
+ public int getCursorCapsMode(int reqModes) {
+ if (mDummyMode) return 0;
+
+ final Editable content = getEditable();
+ if (content == null) return 0;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ return TextUtils.getCapsMode(content, a, reqModes);
+ }
+
+ /**
+ * The default implementation always returns null.
+ */
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ return null;
+ }
+
+ /**
+ * The default implementation returns the given amount of text from the
+ * current cursor position in the buffer.
+ */
+ public CharSequence getTextBeforeCursor(int length, int flags) {
+ final Editable content = getEditable();
+ if (content == null) return null;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ if (a <= 0) {
+ return "";
+ }
+
+ if (length > a) {
+ length = a;
+ }
+
+ if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+ return content.subSequence(a - length, a);
+ }
+ return TextUtils.substring(content, a - length, a);
+ }
+
+ /**
+ * The default implementation returns the text currently selected, or null if none is
+ * selected.
+ */
+ public CharSequence getSelectedText(int flags) {
+ final Editable content = getEditable();
+ if (content == null) return null;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ if (a == b) return null;
+
+ if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+ return content.subSequence(a, b);
+ }
+ return TextUtils.substring(content, a, b);
+ }
+
+ /**
+ * The default implementation returns the given amount of text from the
+ * current cursor position in the buffer.
+ */
+ public CharSequence getTextAfterCursor(int length, int flags) {
+ final Editable content = getEditable();
+ if (content == null) return null;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ // Guard against the case where the cursor has not been positioned yet.
+ if (b < 0) {
+ b = 0;
+ }
+
+ if (b + length > content.length()) {
+ length = content.length() - b;
+ }
+
+
+ if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+ return content.subSequence(b, b + length);
+ }
+ return TextUtils.substring(content, b, b + length);
+ }
+
+ /**
+ * The default implementation turns this into the enter key.
+ */
+ public boolean performEditorAction(int actionCode) {
+ long eventTime = SystemClock.uptimeMillis();
+ sendKeyEvent(new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+ | KeyEvent.FLAG_EDITOR_ACTION));
+ sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+ | KeyEvent.FLAG_EDITOR_ACTION));
+ return true;
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean performContextMenuAction(int id) {
+ return false;
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return false;
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean requestCursorUpdates(int cursorUpdateMode) {
+ return false;
+ }
+
+ public Handler getHandler() {
+ return null;
+ }
+
+ /**
+ * The default implementation places the given text into the editable,
+ * replacing any existing composing text. The new text is marked as
+ * in a composing state with the composing style.
+ */
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ if (DEBUG) Log.v(TAG, "setComposingText " + text);
+ replaceText(text, newCursorPosition, true);
+ return true;
+ }
+
+ public boolean setComposingRegion(int start, int end) {
+ final Editable content = getEditable();
+ if (content != null) {
+ beginBatchEdit();
+ removeComposingSpans(content);
+ int a = start;
+ int b = end;
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+ // Clip the end points to be within the content bounds.
+ final int length = content.length();
+ if (a < 0) a = 0;
+ if (b < 0) b = 0;
+ if (a > length) a = length;
+ if (b > length) b = length;
+
+ ensureDefaultComposingSpans();
+ if (mDefaultComposingSpans != null) {
+ for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
+ content.setSpan(mDefaultComposingSpans[i], a, b,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+ }
+ }
+
+ content.setSpan(COMPOSING, a, b,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+
+ // Note: sendCurrentText does nothing unless mDummyMode is set
+ sendCurrentText();
+ endBatchEdit();
+ }
+ return true;
+ }
+
+ /**
+ * The default implementation changes the selection position in the
+ * current editable text.
+ */
+ public boolean setSelection(int start, int end) {
+ if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
+ final Editable content = getEditable();
+ if (content == null) return false;
+ int len = content.length();
+ if (start > len || end > len || start < 0 || end < 0) {
+ // If the given selection is out of bounds, just ignore it.
+ // Most likely the text was changed out from under the IME,
+ // and the IME is going to have to update all of its state
+ // anyway.
+ return true;
+ }
+ if (start == end && MetaKeyKeyListener.getMetaState(content,
+ MetaKeyKeyListener.META_SELECTING) != 0) {
+ // If we are in selection mode, then we want to extend the
+ // selection instead of replacing it.
+ Selection.extendSelection(content, start);
+ } else {
+ Selection.setSelection(content, start, end);
+ }
+ return true;
+ }
+
+ /**
+ * Provides standard implementation for sending a key event to the window
+ * attached to the input connection's view.
+ */
+ public boolean sendKeyEvent(KeyEvent event) {
+ mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
+ return false;
+ }
+
+ /**
+ * Updates InputMethodManager with the current fullscreen mode.
+ */
+ public boolean reportFullscreenMode(boolean enabled) {
+ return true;
+ }
+
+ private void sendCurrentText() {
+ if (!mDummyMode) {
+ return;
+ }
+
+ Editable content = getEditable();
+ if (content != null) {
+ final int N = content.length();
+ if (N == 0) {
+ return;
+ }
+ if (N == 1) {
+ // If it's 1 character, we have a chance of being
+ // able to generate normal key events...
+ if (mKeyCharacterMap == null) {
+ mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ }
+ char[] chars = new char[1];
+ content.getChars(0, 1, chars, 0);
+ KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
+ if (events != null) {
+ for (int i=0; i<events.length; i++) {
+ if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
+ sendKeyEvent(events[i]);
+ }
+ content.clear();
+ return;
+ }
+ }
+
+ // Otherwise, revert to the special key event containing
+ // the actual characters.
+ KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
+ content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);
+ sendKeyEvent(event);
+ content.clear();
+ }
+ }
+
+ private void ensureDefaultComposingSpans() {
+ if (mDefaultComposingSpans == null) {
+ Context context;
+ if (mTargetView != null) {
+ context = mTargetView.getContext();
+ } else if (mIMM.mServedView != null) {
+ context = mIMM.mServedView.getContext();
+ } else {
+ context = null;
+ }
+ if (context != null) {
+ TypedArray ta = context.getTheme()
+ .obtainStyledAttributes(new int[] {
+ com.android.internal.R.attr.candidatesTextStyleSpans
+ });
+ CharSequence style = ta.getText(0);
+ ta.recycle();
+ if (style != null && style instanceof Spanned) {
+ mDefaultComposingSpans = ((Spanned)style).getSpans(
+ 0, style.length(), Object.class);
+ }
+ }
+ }
+ }
+
+ private void replaceText(CharSequence text, int newCursorPosition,
+ boolean composing) {
+ final Editable content = getEditable();
+ if (content == null) {
+ return;
+ }
+
+ beginBatchEdit();
+
+ // delete composing text set previously.
+ int a = getComposingSpanStart(content);
+ int b = getComposingSpanEnd(content);
+
+ if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
+
+ if (b < a) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ if (a != -1 && b != -1) {
+ removeComposingSpans(content);
+ } else {
+ a = Selection.getSelectionStart(content);
+ b = Selection.getSelectionEnd(content);
+ if (a < 0) a = 0;
+ if (b < 0) b = 0;
+ if (b < a) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+ }
+
+ if (composing) {
+ Spannable sp = null;
+ if (!(text instanceof Spannable)) {
+ sp = new SpannableStringBuilder(text);
+ text = sp;
+ ensureDefaultComposingSpans();
+ if (mDefaultComposingSpans != null) {
+ for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
+ sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+ }
+ }
+ } else {
+ sp = (Spannable)text;
+ }
+ setComposingSpans(sp);
+ }
+
+ if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
+ + text + "\", composing=" + composing
+ + ", type=" + text.getClass().getCanonicalName());
+
+ if (DEBUG) {
+ LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+ lp.println("Current text:");
+ TextUtils.dumpSpans(content, lp, " ");
+ lp.println("Composing text:");
+ TextUtils.dumpSpans(text, lp, " ");
+ }
+
+ // Position the cursor appropriately, so that after replacing the
+ // desired range of text it will be located in the correct spot.
+ // This allows us to deal with filters performing edits on the text
+ // we are providing here.
+ if (newCursorPosition > 0) {
+ newCursorPosition += b - 1;
+ } else {
+ newCursorPosition += a;
+ }
+ if (newCursorPosition < 0) newCursorPosition = 0;
+ if (newCursorPosition > content.length())
+ newCursorPosition = content.length();
+ Selection.setSelection(content, newCursorPosition);
+
+ content.replace(a, b, text);
+
+ if (DEBUG) {
+ LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+ lp.println("Final text:");
+ TextUtils.dumpSpans(content, lp, " ");
+ }
+
+ endBatchEdit();
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+ return false;
+ }
+}
diff --git a/android/view/inputmethod/CompletionInfo.java b/android/view/inputmethod/CompletionInfo.java
new file mode 100644
index 00000000..70b8059a
--- /dev/null
+++ b/android/view/inputmethod/CompletionInfo.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about a single text completion that an editor has reported to
+ * an input method.
+ *
+ * <p>This class encapsulates a completion offered by an application
+ * that wants it to be presented to the user by the IME. Usually, apps
+ * present their completions directly in a scrolling list for example
+ * (UI developers will usually use or extend
+ * {@see android.widget.AutoCompleteTextView} to implement this).
+ * However, in some cases, the editor may not be visible, as in the
+ * case in extract mode where the IME has taken over the full
+ * screen. In this case, the editor can choose to send their
+ * completions to the IME for display.
+ *
+ * <p>Most applications who want to send completions to an IME should use
+ * {@link android.widget.AutoCompleteTextView} as this class makes this
+ * process easy. In this case, the application would not have to deal directly
+ * with this class.
+ *
+ * <p>An application who implements its own editor and wants direct control
+ * over this would create an array of CompletionInfo objects, and send it to the IME using
+ * {@link InputMethodManager#displayCompletions(View, CompletionInfo[])}.
+ * The IME would present the completions however they see fit, and
+ * call back to the editor through
+ * {@link InputConnection#commitCompletion(CompletionInfo)}.
+ * The application can then pick up the commit event by overriding
+ * {@link android.widget.TextView#onCommitCompletion(CompletionInfo)}.
+ */
+public final class CompletionInfo implements Parcelable {
+ private final long mId;
+ private final int mPosition;
+ private final CharSequence mText;
+ private final CharSequence mLabel;
+
+ /**
+ * Create a simple completion with just text, no label.
+ *
+ * @param id An id that get passed as is (to the editor's discretion)
+ * @param index An index that get passed as is. Typically this is the
+ * index in the list of completions inside the editor.
+ * @param text The text that should be inserted into the editor when
+ * this completion is chosen.
+ */
+ public CompletionInfo(long id, int index, CharSequence text) {
+ mId = id;
+ mPosition = index;
+ mText = text;
+ mLabel = null;
+ }
+
+ /**
+ * Create a full completion with both text and label. The text is
+ * what will get inserted into the editor, while the label is what
+ * the IME should display. If they are the same, use the version
+ * of the constructor without a `label' argument.
+ *
+ * @param id An id that get passed as is (to the editor's discretion)
+ * @param index An index that get passed as is. Typically this is the
+ * index in the list of completions inside the editor.
+ * @param text The text that should be inserted into the editor when
+ * this completion is chosen.
+ * @param label The text that the IME should be showing among the
+ * completions list.
+ */
+ public CompletionInfo(long id, int index, CharSequence text, CharSequence label) {
+ mId = id;
+ mPosition = index;
+ mText = text;
+ mLabel = label;
+ }
+
+ private CompletionInfo(Parcel source) {
+ mId = source.readLong();
+ mPosition = source.readInt();
+ mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+
+ /**
+ * Return the abstract identifier for this completion, typically
+ * corresponding to the id associated with it in the original adapter.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Return the original position of this completion, typically
+ * corresponding to its position in the original adapter.
+ */
+ public int getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Return the actual text associated with this completion. This is the
+ * real text that will be inserted into the editor if the user selects it.
+ */
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Return the user-visible label for the completion, or null if the plain
+ * text should be shown. If non-null, this will be what the user sees as
+ * the completion option instead of the actual text.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public String toString() {
+ return "CompletionInfo{#" + mPosition + " \"" + mText
+ + "\" id=" + mId + " label=" + mLabel + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mId);
+ dest.writeInt(mPosition);
+ TextUtils.writeToParcel(mText, dest, flags);
+ TextUtils.writeToParcel(mLabel, dest, flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<CompletionInfo> CREATOR
+ = new Parcelable.Creator<CompletionInfo>() {
+ public CompletionInfo createFromParcel(Parcel source) {
+ return new CompletionInfo(source);
+ }
+
+ public CompletionInfo[] newArray(int size) {
+ return new CompletionInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/CorrectionInfo.java b/android/view/inputmethod/CorrectionInfo.java
new file mode 100644
index 00000000..a43dfe89
--- /dev/null
+++ b/android/view/inputmethod/CorrectionInfo.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007-2010 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.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about a single text correction that an editor has reported to
+ * an input method.
+ */
+public final class CorrectionInfo implements Parcelable {
+ private final int mOffset;
+ private final CharSequence mOldText;
+ private final CharSequence mNewText;
+
+ /**
+ * @param offset The offset in the edited text where the old and new text start.
+ * @param oldText The old text that has been replaced.
+ * @param newText The replacement text.
+ */
+ public CorrectionInfo(int offset, CharSequence oldText, CharSequence newText) {
+ mOffset = offset;
+ mOldText = oldText;
+ mNewText = newText;
+ }
+
+ private CorrectionInfo(Parcel source) {
+ mOffset = source.readInt();
+ mOldText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mNewText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+
+ /**
+ * Return the offset position of this correction in the text. Both the {@link #getOldText()} and
+ * {@link #getNewText()} start at this offset.
+ */
+ public int getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Return the text that has actually been typed by the user, and which has been corrected.
+ */
+ public CharSequence getOldText() {
+ return mOldText;
+ }
+
+ /**
+ * Return the new text that corrects what was typed by the user.
+ */
+ public CharSequence getNewText() {
+ return mNewText;
+ }
+
+ @Override
+ public String toString() {
+ return "CorrectionInfo{#" + mOffset + " \"" + mOldText + "\" -> \"" + mNewText + "\"}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mOffset);
+ TextUtils.writeToParcel(mOldText, dest, flags);
+ TextUtils.writeToParcel(mNewText, dest, flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<CorrectionInfo> CREATOR =
+ new Parcelable.Creator<CorrectionInfo>() {
+ public CorrectionInfo createFromParcel(Parcel source) {
+ return new CorrectionInfo(source);
+ }
+ public CorrectionInfo[] newArray(int size) {
+ return new CorrectionInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/CursorAnchorInfo.java b/android/view/inputmethod/CursorAnchorInfo.java
new file mode 100644
index 00000000..24739bf8
--- /dev/null
+++ b/android/view/inputmethod/CursorAnchorInfo.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2014 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Layout;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Positional information about the text insertion point and characters in the composition string.
+ *
+ * <p>This class encapsulates locations of the text insertion point and the composition string in
+ * the screen coordinates so that IMEs can render their UI components near where the text is
+ * actually inserted.</p>
+ */
+public final class CursorAnchorInfo implements Parcelable {
+ /**
+ * The pre-computed hash code.
+ */
+ private final int mHashCode;
+
+ /**
+ * The index of the first character of the selected text (inclusive). {@code -1} when there is
+ * no text selection.
+ */
+ private final int mSelectionStart;
+ /**
+ * The index of the first character of the selected text (exclusive). {@code -1} when there is
+ * no text selection.
+ */
+ private final int mSelectionEnd;
+
+ /**
+ * The index of the first character of the composing text (inclusive). {@code -1} when there is
+ * no composing text.
+ */
+ private final int mComposingTextStart;
+ /**
+ * The text, tracked as a composing region.
+ */
+ private final CharSequence mComposingText;
+
+ /**
+ * Flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for example.
+ */
+ private final int mInsertionMarkerFlags;
+ /**
+ * Horizontal position of the insertion marker, in the local coordinates that will be
+ * transformed with the transformation matrix when rendered on the screen. This should be
+ * calculated or compatible with {@link Layout#getPrimaryHorizontal(int)}. This can be
+ * {@code java.lang.Float.NaN} when no value is specified.
+ */
+ private final float mInsertionMarkerHorizontal;
+ /**
+ * Vertical position of the insertion marker, in the local coordinates that will be
+ * transformed with the transformation matrix when rendered on the screen. This should be
+ * calculated or compatible with {@link Layout#getLineTop(int)}. This can be
+ * {@code java.lang.Float.NaN} when no value is specified.
+ */
+ private final float mInsertionMarkerTop;
+ /**
+ * Vertical position of the insertion marker, in the local coordinates that will be
+ * transformed with the transformation matrix when rendered on the screen. This should be
+ * calculated or compatible with {@link Layout#getLineBaseline(int)}. This can be
+ * {@code java.lang.Float.NaN} when no value is specified.
+ */
+ private final float mInsertionMarkerBaseline;
+ /**
+ * Vertical position of the insertion marker, in the local coordinates that will be
+ * transformed with the transformation matrix when rendered on the screen. This should be
+ * calculated or compatible with {@link Layout#getLineBottom(int)}. This can be
+ * {@code java.lang.Float.NaN} when no value is specified.
+ */
+ private final float mInsertionMarkerBottom;
+
+ /**
+ * Container of rectangular position of characters, keyed with character index in a unit of
+ * Java chars, in the local coordinates that will be transformed with the transformation matrix
+ * when rendered on the screen.
+ */
+ private final SparseRectFArray mCharacterBoundsArray;
+
+ /**
+ * Transformation matrix that is applied to any positional information of this class to
+ * transform local coordinates into screen coordinates.
+ */
+ @NonNull
+ private final float[] mMatrixValues;
+
+ /**
+ * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
+ * insertion marker or character bounds have at least one visible region.
+ */
+ public static final int FLAG_HAS_VISIBLE_REGION = 0x01;
+
+ /**
+ * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
+ * insertion marker or character bounds have at least one invisible (clipped) region.
+ */
+ public static final int FLAG_HAS_INVISIBLE_REGION = 0x02;
+
+ /**
+ * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
+ * insertion marker or character bounds is placed at right-to-left (RTL) character.
+ */
+ public static final int FLAG_IS_RTL = 0x04;
+
+ public CursorAnchorInfo(final Parcel source) {
+ mHashCode = source.readInt();
+ mSelectionStart = source.readInt();
+ mSelectionEnd = source.readInt();
+ mComposingTextStart = source.readInt();
+ mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mInsertionMarkerFlags = source.readInt();
+ mInsertionMarkerHorizontal = source.readFloat();
+ mInsertionMarkerTop = source.readFloat();
+ mInsertionMarkerBaseline = source.readFloat();
+ mInsertionMarkerBottom = source.readFloat();
+ mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader());
+ mMatrixValues = source.createFloatArray();
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mHashCode);
+ dest.writeInt(mSelectionStart);
+ dest.writeInt(mSelectionEnd);
+ dest.writeInt(mComposingTextStart);
+ TextUtils.writeToParcel(mComposingText, dest, flags);
+ dest.writeInt(mInsertionMarkerFlags);
+ dest.writeFloat(mInsertionMarkerHorizontal);
+ dest.writeFloat(mInsertionMarkerTop);
+ dest.writeFloat(mInsertionMarkerBaseline);
+ dest.writeFloat(mInsertionMarkerBottom);
+ dest.writeParcelable(mCharacterBoundsArray, flags);
+ dest.writeFloatArray(mMatrixValues);
+ }
+
+ @Override
+ public int hashCode(){
+ return mHashCode;
+ }
+
+ /**
+ * Compares two float values. Returns {@code true} if {@code a} and {@code b} are
+ * {@link Float#NaN} at the same time.
+ */
+ private static boolean areSameFloatImpl(final float a, final float b) {
+ if (Float.isNaN(a) && Float.isNaN(b)) {
+ return true;
+ }
+ return a == b;
+ }
+
+ @Override
+ public boolean equals(Object obj){
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CursorAnchorInfo)) {
+ return false;
+ }
+ final CursorAnchorInfo that = (CursorAnchorInfo) obj;
+ if (hashCode() != that.hashCode()) {
+ return false;
+ }
+
+ // Check fields that are not covered by hashCode() first.
+
+ if (mSelectionStart != that.mSelectionStart || mSelectionEnd != that.mSelectionEnd) {
+ return false;
+ }
+
+ if (mInsertionMarkerFlags != that.mInsertionMarkerFlags
+ || !areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal)
+ || !areSameFloatImpl(mInsertionMarkerTop, that.mInsertionMarkerTop)
+ || !areSameFloatImpl(mInsertionMarkerBaseline, that.mInsertionMarkerBaseline)
+ || !areSameFloatImpl(mInsertionMarkerBottom, that.mInsertionMarkerBottom)) {
+ return false;
+ }
+
+ if (!Objects.equals(mCharacterBoundsArray, that.mCharacterBoundsArray)) {
+ return false;
+ }
+
+ // Following fields are (partially) covered by hashCode().
+
+ if (mComposingTextStart != that.mComposingTextStart
+ || !Objects.equals(mComposingText, that.mComposingText)) {
+ return false;
+ }
+
+ // We do not use Arrays.equals(float[], float[]) to keep the previous behavior regarding
+ // NaN, 0.0f, and -0.0f.
+ if (mMatrixValues.length != that.mMatrixValues.length) {
+ return false;
+ }
+ for (int i = 0; i < mMatrixValues.length; ++i) {
+ if (mMatrixValues[i] != that.mMatrixValues[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "CursorAnchorInfo{mHashCode=" + mHashCode
+ + " mSelection=" + mSelectionStart + "," + mSelectionEnd
+ + " mComposingTextStart=" + mComposingTextStart
+ + " mComposingText=" + Objects.toString(mComposingText)
+ + " mInsertionMarkerFlags=" + mInsertionMarkerFlags
+ + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
+ + " mInsertionMarkerTop=" + mInsertionMarkerTop
+ + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
+ + " mInsertionMarkerBottom=" + mInsertionMarkerBottom
+ + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray)
+ + " mMatrix=" + Arrays.toString(mMatrixValues)
+ + "}";
+ }
+
+ /**
+ * Builder for {@link CursorAnchorInfo}. This class is not designed to be thread-safe.
+ */
+ public static final class Builder {
+ private int mSelectionStart = -1;
+ private int mSelectionEnd = -1;
+ private int mComposingTextStart = -1;
+ private CharSequence mComposingText = null;
+ private float mInsertionMarkerHorizontal = Float.NaN;
+ private float mInsertionMarkerTop = Float.NaN;
+ private float mInsertionMarkerBaseline = Float.NaN;
+ private float mInsertionMarkerBottom = Float.NaN;
+ private int mInsertionMarkerFlags = 0;
+ private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null;
+ private float[] mMatrixValues = null;
+ private boolean mMatrixInitialized = false;
+
+ /**
+ * Sets the text range of the selection. Calling this can be skipped if there is no
+ * selection.
+ */
+ public Builder setSelectionRange(final int newStart, final int newEnd) {
+ mSelectionStart = newStart;
+ mSelectionEnd = newEnd;
+ return this;
+ }
+
+ /**
+ * Sets the text range of the composing text. Calling this can be skipped if there is
+ * no composing text.
+ * @param composingTextStart index where the composing text starts.
+ * @param composingText the entire composing text.
+ */
+ public Builder setComposingText(final int composingTextStart,
+ final CharSequence composingText) {
+ mComposingTextStart = composingTextStart;
+ if (composingText == null) {
+ mComposingText = null;
+ } else {
+ // Make a snapshot of the given char sequence.
+ mComposingText = new SpannedString(composingText);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the location of the text insertion point (zero width cursor) as a rectangle in
+ * local coordinates. Calling this can be skipped when there is no text insertion point;
+ * however if there is an insertion point, editors must call this method.
+ * @param horizontalPosition horizontal position of the insertion marker, in the local
+ * coordinates that will be transformed with the transformation matrix when rendered on the
+ * screen. This should be calculated or compatible with
+ * {@link Layout#getPrimaryHorizontal(int)}.
+ * @param lineTop vertical position of the insertion marker, in the local coordinates that
+ * will be transformed with the transformation matrix when rendered on the screen. This
+ * should be calculated or compatible with {@link Layout#getLineTop(int)}.
+ * @param lineBaseline vertical position of the insertion marker, in the local coordinates
+ * that will be transformed with the transformation matrix when rendered on the screen. This
+ * should be calculated or compatible with {@link Layout#getLineBaseline(int)}.
+ * @param lineBottom vertical position of the insertion marker, in the local coordinates
+ * that will be transformed with the transformation matrix when rendered on the screen. This
+ * should be calculated or compatible with {@link Layout#getLineBottom(int)}.
+ * @param flags flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for
+ * example.
+ */
+ public Builder setInsertionMarkerLocation(final float horizontalPosition,
+ final float lineTop, final float lineBaseline, final float lineBottom,
+ final int flags){
+ mInsertionMarkerHorizontal = horizontalPosition;
+ mInsertionMarkerTop = lineTop;
+ mInsertionMarkerBaseline = lineBaseline;
+ mInsertionMarkerBottom = lineBottom;
+ mInsertionMarkerFlags = flags;
+ return this;
+ }
+
+ /**
+ * Adds the bounding box of the character specified with the index.
+ *
+ * @param index index of the character in Java chars units. Must be specified in
+ * ascending order across successive calls.
+ * @param left x coordinate of the left edge of the character in local coordinates.
+ * @param top y coordinate of the top edge of the character in local coordinates.
+ * @param right x coordinate of the right edge of the character in local coordinates.
+ * @param bottom y coordinate of the bottom edge of the character in local coordinates.
+ * @param flags flags for this character bounds. See {@link #FLAG_HAS_VISIBLE_REGION},
+ * {@link #FLAG_HAS_INVISIBLE_REGION} and {@link #FLAG_IS_RTL}. These flags must be
+ * specified when necessary.
+ * @throws IllegalArgumentException If the index is a negative value, or not greater than
+ * all of the previously called indices.
+ */
+ public Builder addCharacterBounds(final int index, final float left, final float top,
+ final float right, final float bottom, final int flags) {
+ if (index < 0) {
+ throw new IllegalArgumentException("index must not be a negative integer.");
+ }
+ if (mCharacterBoundsArrayBuilder == null) {
+ mCharacterBoundsArrayBuilder = new SparseRectFArrayBuilder();
+ }
+ mCharacterBoundsArrayBuilder.append(index, left, top, right, bottom, flags);
+ return this;
+ }
+
+ /**
+ * Sets the matrix that transforms local coordinates into screen coordinates.
+ * @param matrix transformation matrix from local coordinates into screen coordinates. null
+ * is interpreted as an identity matrix.
+ */
+ public Builder setMatrix(final Matrix matrix) {
+ if (mMatrixValues == null) {
+ mMatrixValues = new float[9];
+ }
+ (matrix != null ? matrix : Matrix.IDENTITY_MATRIX).getValues(mMatrixValues);
+ mMatrixInitialized = true;
+ return this;
+ }
+
+ /**
+ * @return {@link CursorAnchorInfo} using parameters in this {@link Builder}.
+ * @throws IllegalArgumentException if one or more positional parameters are specified but
+ * the coordinate transformation matrix is not provided via {@link #setMatrix(Matrix)}.
+ */
+ public CursorAnchorInfo build() {
+ if (!mMatrixInitialized) {
+ // Coordinate transformation matrix is mandatory when at least one positional
+ // parameter is specified.
+ final boolean hasCharacterBounds = (mCharacterBoundsArrayBuilder != null
+ && !mCharacterBoundsArrayBuilder.isEmpty());
+ if (hasCharacterBounds
+ || !Float.isNaN(mInsertionMarkerHorizontal)
+ || !Float.isNaN(mInsertionMarkerTop)
+ || !Float.isNaN(mInsertionMarkerBaseline)
+ || !Float.isNaN(mInsertionMarkerBottom)) {
+ throw new IllegalArgumentException("Coordinate transformation matrix is " +
+ "required when positional parameters are specified.");
+ }
+ }
+ return new CursorAnchorInfo(this);
+ }
+
+ /**
+ * Resets the internal state so that this instance can be reused to build another
+ * instance of {@link CursorAnchorInfo}.
+ */
+ public void reset() {
+ mSelectionStart = -1;
+ mSelectionEnd = -1;
+ mComposingTextStart = -1;
+ mComposingText = null;
+ mInsertionMarkerFlags = 0;
+ mInsertionMarkerHorizontal = Float.NaN;
+ mInsertionMarkerTop = Float.NaN;
+ mInsertionMarkerBaseline = Float.NaN;
+ mInsertionMarkerBottom = Float.NaN;
+ mMatrixInitialized = false;
+ if (mCharacterBoundsArrayBuilder != null) {
+ mCharacterBoundsArrayBuilder.reset();
+ }
+ }
+ }
+
+ private CursorAnchorInfo(final Builder builder) {
+ mSelectionStart = builder.mSelectionStart;
+ mSelectionEnd = builder.mSelectionEnd;
+ mComposingTextStart = builder.mComposingTextStart;
+ mComposingText = builder.mComposingText;
+ mInsertionMarkerFlags = builder.mInsertionMarkerFlags;
+ mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal;
+ mInsertionMarkerTop = builder.mInsertionMarkerTop;
+ mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline;
+ mInsertionMarkerBottom = builder.mInsertionMarkerBottom;
+ mCharacterBoundsArray = builder.mCharacterBoundsArrayBuilder != null ?
+ builder.mCharacterBoundsArrayBuilder.build() : null;
+ mMatrixValues = new float[9];
+ if (builder.mMatrixInitialized) {
+ System.arraycopy(builder.mMatrixValues, 0, mMatrixValues, 0, 9);
+ } else {
+ Matrix.IDENTITY_MATRIX.getValues(mMatrixValues);
+ }
+
+ // To keep hash function simple, we only use some complex objects for hash.
+ int hash = Objects.hashCode(mComposingText);
+ hash *= 31;
+ hash += Arrays.hashCode(mMatrixValues);
+ mHashCode = hash;
+ }
+
+ /**
+ * Returns the index where the selection starts.
+ * @return {@code -1} if there is no selection.
+ */
+ public int getSelectionStart() {
+ return mSelectionStart;
+ }
+
+ /**
+ * Returns the index where the selection ends.
+ * @return {@code -1} if there is no selection.
+ */
+ public int getSelectionEnd() {
+ return mSelectionEnd;
+ }
+
+ /**
+ * Returns the index where the composing text starts.
+ * @return {@code -1} if there is no composing text.
+ */
+ public int getComposingTextStart() {
+ return mComposingTextStart;
+ }
+
+ /**
+ * Returns the entire composing text.
+ * @return {@code null} if there is no composition.
+ */
+ public CharSequence getComposingText() {
+ return mComposingText;
+ }
+
+ /**
+ * Returns the flag of the insertion marker.
+ * @return the flag of the insertion marker. {@code 0} if no flag is specified.
+ */
+ public int getInsertionMarkerFlags() {
+ return mInsertionMarkerFlags;
+ }
+
+ /**
+ * Returns the horizontal start of the insertion marker, in the local coordinates that will
+ * be transformed with {@link #getMatrix()} when rendered on the screen.
+ * @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}.
+ * Pay special care to RTL/LTR handling.
+ * {@code java.lang.Float.NaN} if not specified.
+ * @see Layout#getPrimaryHorizontal(int)
+ */
+ public float getInsertionMarkerHorizontal() {
+ return mInsertionMarkerHorizontal;
+ }
+
+ /**
+ * Returns the vertical top position of the insertion marker, in the local coordinates that
+ * will be transformed with {@link #getMatrix()} when rendered on the screen.
+ * @return y coordinate that is compatible with {@link Layout#getLineTop(int)}.
+ * {@code java.lang.Float.NaN} if not specified.
+ */
+ public float getInsertionMarkerTop() {
+ return mInsertionMarkerTop;
+ }
+
+ /**
+ * Returns the vertical baseline position of the insertion marker, in the local coordinates
+ * that will be transformed with {@link #getMatrix()} when rendered on the screen.
+ * @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}.
+ * {@code java.lang.Float.NaN} if not specified.
+ */
+ public float getInsertionMarkerBaseline() {
+ return mInsertionMarkerBaseline;
+ }
+
+ /**
+ * Returns the vertical bottom position of the insertion marker, in the local coordinates
+ * that will be transformed with {@link #getMatrix()} when rendered on the screen.
+ * @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}.
+ * {@code java.lang.Float.NaN} if not specified.
+ */
+ public float getInsertionMarkerBottom() {
+ return mInsertionMarkerBottom;
+ }
+
+ /**
+ * Returns a new instance of {@link RectF} that indicates the location of the character
+ * specified with the index.
+ * @param index index of the character in a Java chars.
+ * @return the character bounds in local coordinates as a new instance of {@link RectF}.
+ */
+ public RectF getCharacterBounds(final int index) {
+ if (mCharacterBoundsArray == null) {
+ return null;
+ }
+ return mCharacterBoundsArray.get(index);
+ }
+
+ /**
+ * Returns the flags associated with the character bounds specified with the index.
+ * @param index index of the character in a Java chars.
+ * @return {@code 0} if no flag is specified.
+ */
+ public int getCharacterBoundsFlags(final int index) {
+ if (mCharacterBoundsArray == null) {
+ return 0;
+ }
+ return mCharacterBoundsArray.getFlags(index, 0);
+ }
+
+ /**
+ * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
+ * matrix that is to be applied other positional data in this class.
+ * @return a new instance (copy) of the transformation matrix.
+ */
+ public Matrix getMatrix() {
+ final Matrix matrix = new Matrix();
+ matrix.setValues(mMatrixValues);
+ return matrix;
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<CursorAnchorInfo> CREATOR
+ = new Parcelable.Creator<CursorAnchorInfo>() {
+ @Override
+ public CursorAnchorInfo createFromParcel(Parcel source) {
+ return new CursorAnchorInfo(source);
+ }
+
+ @Override
+ public CursorAnchorInfo[] newArray(int size) {
+ return new CursorAnchorInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/EditorInfo.java b/android/view/inputmethod/EditorInfo.java
new file mode 100644
index 00000000..28d9fcf5
--- /dev/null
+++ b/android/view/inputmethod/EditorInfo.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2008 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.inputmethod;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.Printer;
+
+import java.util.Arrays;
+
+/**
+ * An EditorInfo describes several attributes of a text editing object
+ * that an input method is communicating with (typically an EditText), most
+ * importantly the type of text content it contains and the current cursor position.
+ */
+public class EditorInfo implements InputType, Parcelable {
+ /**
+ * Masks for {@link inputType}
+ *
+ * <pre>
+ * |-------|-------|-------|-------|
+ * 1111 TYPE_MASK_CLASS
+ * 11111111 TYPE_MASK_VARIATION
+ * 111111111111 TYPE_MASK_FLAGS
+ * |-------|-------|-------|-------|
+ * TYPE_NULL
+ * |-------|-------|-------|-------|
+ * 1 TYPE_CLASS_TEXT
+ * 1 TYPE_TEXT_VARIATION_URI
+ * 1 TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ * 11 TYPE_TEXT_VARIATION_EMAIL_SUBJECT
+ * 1 TYPE_TEXT_VARIATION_SHORT_MESSAGE
+ * 1 1 TYPE_TEXT_VARIATION_LONG_MESSAGE
+ * 11 TYPE_TEXT_VARIATION_PERSON_NAME
+ * 111 TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+ * 1 TYPE_TEXT_VARIATION_PASSWORD
+ * 1 1 TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ * 1 1 TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
+ * 1 11 TYPE_TEXT_VARIATION_FILTER
+ * 11 TYPE_TEXT_VARIATION_PHONETIC
+ * 11 1 TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
+ * 111 TYPE_TEXT_VARIATION_WEB_PASSWORD
+ * 1 TYPE_TEXT_FLAG_CAP_CHARACTERS
+ * 1 TYPE_TEXT_FLAG_CAP_WORDS
+ * 1 TYPE_TEXT_FLAG_CAP_SENTENCES
+ * 1 TYPE_TEXT_FLAG_AUTO_CORRECT
+ * 1 TYPE_TEXT_FLAG_AUTO_COMPLETE
+ * 1 TYPE_TEXT_FLAG_MULTI_LINE
+ * 1 TYPE_TEXT_FLAG_IME_MULTI_LINE
+ * 1 TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ * |-------|-------|-------|-------|
+ * 1 TYPE_CLASS_NUMBER
+ * 1 TYPE_NUMBER_VARIATION_PASSWORD
+ * 1 TYPE_NUMBER_FLAG_SIGNED
+ * 1 TYPE_NUMBER_FLAG_DECIMAL
+ * |-------|-------|-------|-------|
+ * 11 TYPE_CLASS_PHONE
+ * |-------|-------|-------|-------|
+ * 1 TYPE_CLASS_DATETIME
+ * 1 TYPE_DATETIME_VARIATION_DATE
+ * 1 TYPE_DATETIME_VARIATION_TIME
+ * |-------|-------|-------|-------|</pre>
+ */
+
+ /**
+ * The content type of the text box, whose bits are defined by
+ * {@link InputType}.
+ *
+ * @see InputType
+ * @see #TYPE_MASK_CLASS
+ * @see #TYPE_MASK_VARIATION
+ * @see #TYPE_MASK_FLAGS
+ */
+ public int inputType = TYPE_NULL;
+
+ /**
+ * Set of bits in {@link #imeOptions} that provide alternative actions
+ * associated with the "enter" key. This both helps the IME provide
+ * better feedback about what the enter key will do, and also allows it
+ * to provide alternative mechanisms for providing that command.
+ */
+ public static final int IME_MASK_ACTION = 0x000000ff;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: no specific action has been
+ * associated with this editor, let the editor come up with its own if
+ * it can.
+ */
+ public static final int IME_ACTION_UNSPECIFIED = 0x00000000;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: there is no available action.
+ */
+ public static final int IME_ACTION_NONE = 0x00000001;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
+ * operation to take the user to the target of the text they typed.
+ * Typically used, for example, when entering a URL.
+ */
+ public static final int IME_ACTION_GO = 0x00000002;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
+ * operation, taking the user to the results of searching for the text
+ * they have typed (in whatever context is appropriate).
+ */
+ public static final int IME_ACTION_SEARCH = 0x00000003;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
+ * operation, delivering the text to its target. This is typically used
+ * when composing a message in IM or SMS where sending is immediate.
+ */
+ public static final int IME_ACTION_SEND = 0x00000004;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
+ * operation, taking the user to the next field that will accept text.
+ */
+ public static final int IME_ACTION_NEXT = 0x00000005;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "done"
+ * operation, typically meaning there is nothing more to input and the
+ * IME will be closed.
+ */
+ public static final int IME_ACTION_DONE = 0x00000006;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: like {@link #IME_ACTION_NEXT}, but
+ * for moving to the previous field. This will normally not be used to
+ * specify an action (since it precludes {@link #IME_ACTION_NEXT}), but
+ * can be returned to the app if it sets {@link #IME_FLAG_NAVIGATE_PREVIOUS}.
+ */
+ public static final int IME_ACTION_PREVIOUS = 0x00000007;
+
+ /**
+ * Flag of {@link #imeOptions}: used to request that the IME should not update any personalized
+ * data such as typing history and personalized language model based on what the user typed on
+ * this text editing object. Typical use cases are:
+ * <ul>
+ * <li>When the application is in a special mode, where user's activities are expected to be
+ * not recorded in the application's history. Some web browsers and chat applications may
+ * have this kind of modes.</li>
+ * <li>When storing typing history does not make much sense. Specifying this flag in typing
+ * games may help to avoid typing history from being filled up with words that the user is
+ * less likely to type in their daily life. Another example is that when the application
+ * already knows that the expected input is not a valid word (e.g. a promotion code that is
+ * not a valid word in any natural language).</li>
+ * </ul>
+ *
+ * <p>Applications need to be aware that the flag is not a guarantee, and some IMEs may not
+ * respect it.</p>
+ */
+ public static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 0x1000000;
+
+ /**
+ * Flag of {@link #imeOptions}: used to request that the IME never go
+ * into fullscreen mode.
+ * By default, IMEs may go into full screen mode when they think
+ * it's appropriate, for example on small screens in landscape
+ * orientation where displaying a software keyboard may occlude
+ * such a large portion of the screen that the remaining part is
+ * too small to meaningfully display the application UI.
+ * If this flag is set, compliant IMEs will never go into full screen mode,
+ * and always leave some space to display the application UI.
+ * Applications need to be aware that the flag is not a guarantee, and
+ * some IMEs may ignore it.
+ */
+ public static final int IME_FLAG_NO_FULLSCREEN = 0x2000000;
+
+ /**
+ * Flag of {@link #imeOptions}: like {@link #IME_FLAG_NAVIGATE_NEXT}, but
+ * specifies there is something interesting that a backward navigation
+ * can focus on. If the user selects the IME's facility to backward
+ * navigate, this will show up in the application as an {@link #IME_ACTION_PREVIOUS}
+ * at {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction(int)}.
+ */
+ public static final int IME_FLAG_NAVIGATE_PREVIOUS = 0x4000000;
+
+ /**
+ * Flag of {@link #imeOptions}: used to specify that there is something
+ * interesting that a forward navigation can focus on. This is like using
+ * {@link #IME_ACTION_NEXT}, except allows the IME to be multiline (with
+ * an enter key) as well as provide forward navigation. Note that some
+ * IMEs may not be able to do this, especially when running on a small
+ * screen where there is little space. In that case it does not need to
+ * present a UI for this option. Like {@link #IME_ACTION_NEXT}, if the
+ * user selects the IME's facility to forward navigate, this will show up
+ * in the application at {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction(int)}.
+ */
+ public static final int IME_FLAG_NAVIGATE_NEXT = 0x8000000;
+
+ /**
+ * Flag of {@link #imeOptions}: used to specify that the IME does not need
+ * to show its extracted text UI. For input methods that may be fullscreen,
+ * often when in landscape mode, this allows them to be smaller and let part
+ * of the application be shown behind, through transparent UI parts in the
+ * fullscreen IME. The part of the UI visible to the user may not be responsive
+ * to touch because the IME will receive touch events, which may confuse the
+ * user; use {@link #IME_FLAG_NO_FULLSCREEN} instead for a better experience.
+ * Using this flag is discouraged and it may become deprecated in the future.
+ * Its meaning is unclear in some situations and it may not work appropriately
+ * on older versions of the platform.
+ */
+ public static final int IME_FLAG_NO_EXTRACT_UI = 0x10000000;
+
+ /**
+ * Flag of {@link #imeOptions}: used in conjunction with one of the actions
+ * masked by {@link #IME_MASK_ACTION}, this indicates that the action
+ * should not be available as an accessory button on the right of the extracted
+ * text when the input method is full-screen. Note that by setting this flag,
+ * there can be cases where the action is simply never available to the
+ * user. Setting this generally means that you think that in fullscreen mode,
+ * where there is little space to show the text, it's not worth taking some
+ * screen real estate to display the action and it should be used instead
+ * to show more text.
+ */
+ public static final int IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000;
+
+ /**
+ * Flag of {@link #imeOptions}: used in conjunction with one of the actions
+ * masked by {@link #IME_MASK_ACTION}. If this flag is not set, IMEs will
+ * normally replace the "enter" key with the action supplied. This flag
+ * indicates that the action should not be available in-line as a replacement
+ * for the "enter" key. Typically this is because the action has such a
+ * significant impact or is not recoverable enough that accidentally hitting
+ * it should be avoided, such as sending a message. Note that
+ * {@link android.widget.TextView} will automatically set this flag for you
+ * on multi-line text views.
+ */
+ public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000;
+
+ /**
+ * Flag of {@link #imeOptions}: used to request an IME that is capable of
+ * inputting ASCII characters. The intention of this flag is to ensure that
+ * the user can type Roman alphabet characters in a {@link android.widget.TextView}.
+ * It is typically used for an account ID or password input. A lot of the time,
+ * IMEs are already able to input ASCII even without being told so (such IMEs
+ * already respect this flag in a sense), but there are cases when this is not
+ * the default. For instance, users of languages using a different script like
+ * Arabic, Greek, Hebrew or Russian typically have a keyboard that can't
+ * input ASCII characters by default. Applications need to be
+ * aware that the flag is not a guarantee, and some IMEs may not respect it.
+ * However, it is strongly recommended for IME authors to respect this flag
+ * especially when their IME could end up with a state where only languages
+ * using non-ASCII are enabled.
+ */
+ public static final int IME_FLAG_FORCE_ASCII = 0x80000000;
+
+ /**
+ * Generic unspecified type for {@link #imeOptions}.
+ */
+ public static final int IME_NULL = 0x00000000;
+
+ /**
+ * Masks for {@link imeOptions}
+ *
+ * <pre>
+ * |-------|-------|-------|-------|
+ * 1111 IME_MASK_ACTION
+ * |-------|-------|-------|-------|
+ * IME_ACTION_UNSPECIFIED
+ * 1 IME_ACTION_NONE
+ * 1 IME_ACTION_GO
+ * 11 IME_ACTION_SEARCH
+ * 1 IME_ACTION_SEND
+ * 1 1 IME_ACTION_NEXT
+ * 11 IME_ACTION_DONE
+ * 111 IME_ACTION_PREVIOUS
+ * 1 IME_FLAG_NO_PERSONALIZED_LEARNING
+ * 1 IME_FLAG_NO_FULLSCREEN
+ * 1 IME_FLAG_NAVIGATE_PREVIOUS
+ * 1 IME_FLAG_NAVIGATE_NEXT
+ * 1 IME_FLAG_NO_EXTRACT_UI
+ * 1 IME_FLAG_NO_ACCESSORY_ACTION
+ * 1 IME_FLAG_NO_ENTER_ACTION
+ * 1 IME_FLAG_FORCE_ASCII
+ * |-------|-------|-------|-------|</pre>
+ */
+
+ /**
+ * Extended type information for the editor, to help the IME better
+ * integrate with it.
+ */
+ public int imeOptions = IME_NULL;
+
+ /**
+ * A string supplying additional information options that are
+ * private to a particular IME implementation. The string must be
+ * scoped to a package owned by the implementation, to ensure there are
+ * no conflicts between implementations, but other than that you can put
+ * whatever you want in it to communicate with the IME. For example,
+ * you could have a string that supplies an argument like
+ * <code>"com.example.myapp.SpecialMode=3"</code>. This field is can be
+ * filled in from the {@link android.R.attr#privateImeOptions}
+ * attribute of a TextView.
+ */
+ public String privateImeOptions = null;
+
+ /**
+ * In some cases an IME may be able to display an arbitrary label for
+ * a command the user can perform, which you can specify here. This is
+ * typically used as the label for the action to use in-line as a replacement
+ * for the "enter" key (see {@link #actionId}). Remember the key where
+ * this will be displayed is typically very small, and there are significant
+ * localization challenges to make this fit in all supported languages. Also
+ * you can not count absolutely on this being used, as some IMEs may
+ * ignore this.
+ */
+ public CharSequence actionLabel = null;
+
+ /**
+ * If {@link #actionLabel} has been given, this is the id for that command
+ * when the user presses its button that is delivered back with
+ * {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction()}.
+ */
+ public int actionId = 0;
+
+ /**
+ * The text offset of the start of the selection at the time editing
+ * begins; -1 if not known. Keep in mind that, without knowing the cursor
+ * position, many IMEs will not be able to offer their full feature set and
+ * may even behave in unpredictable ways: pass the actual cursor position
+ * here if possible at all.
+ *
+ * <p>Also, this needs to be the cursor position <strong>right now</strong>,
+ * not at some point in the past, even if input is starting in the same text field
+ * as before. When the app is filling this object, input is about to start by
+ * definition, and this value will override any value the app may have passed to
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}
+ * before.</p>
+ */
+ public int initialSelStart = -1;
+
+ /**
+ * <p>The text offset of the end of the selection at the time editing
+ * begins; -1 if not known. Keep in mind that, without knowing the cursor
+ * position, many IMEs will not be able to offer their full feature set and
+ * may behave in unpredictable ways: pass the actual cursor position
+ * here if possible at all.</p>
+ *
+ * <p>Also, this needs to be the cursor position <strong>right now</strong>,
+ * not at some point in the past, even if input is starting in the same text field
+ * as before. When the app is filling this object, input is about to start by
+ * definition, and this value will override any value the app may have passed to
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}
+ * before.</p>
+ */
+ public int initialSelEnd = -1;
+
+ /**
+ * The capitalization mode of the first character being edited in the
+ * text. Values may be any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS},
+ * {@link TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}, though
+ * you should generally just take a non-zero value to mean "start out in
+ * caps mode".
+ */
+ public int initialCapsMode = 0;
+
+ /**
+ * The "hint" text of the text view, typically shown in-line when the
+ * text is empty to tell the user what to enter.
+ */
+ public CharSequence hintText;
+
+ /**
+ * A label to show to the user describing the text they are writing.
+ */
+ public CharSequence label;
+
+ /**
+ * Name of the package that owns this editor.
+ *
+ * <p><strong>IME authors:</strong> In API level 22
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and prior, do not trust this package
+ * name. The system had not verified the consistency between the package name here and
+ * application's uid. Consider to use {@link InputBinding#getUid()}, which is trustworthy.
+ * Starting from {@link android.os.Build.VERSION_CODES#M}, the system verifies the consistency
+ * between this package name and application uid before {@link EditorInfo} is passed to the
+ * input method.</p>
+ *
+ * <p><strong>Editor authors:</strong> Starting from {@link android.os.Build.VERSION_CODES#M},
+ * the application is no longer
+ * able to establish input connections if the package name provided here is inconsistent with
+ * application's uid.</p>
+ */
+ public String packageName;
+
+ /**
+ * Identifier for the editor's field. This is optional, and may be
+ * 0. By default it is filled in with the result of
+ * {@link android.view.View#getId() View.getId()} on the View that
+ * is being edited.
+ */
+ public int fieldId;
+
+ /**
+ * Additional name for the editor's field. This can supply additional
+ * name information for the field. By default it is null. The actual
+ * contents have no meaning.
+ */
+ public String fieldName;
+
+ /**
+ * Any extra data to supply to the input method. This is for extended
+ * communication with specific input methods; the name fields in the
+ * bundle should be scoped (such as "com.mydomain.im.SOME_FIELD") so
+ * that they don't conflict with others. This field can be
+ * filled in from the {@link android.R.attr#editorExtras}
+ * attribute of a TextView.
+ */
+ public Bundle extras;
+
+ /**
+ * List of the languages that the user is supposed to switch to no matter what input method
+ * subtype is currently used. This special "hint" can be used mainly for, but not limited to,
+ * multilingual users who want IMEs to switch language context automatically.
+ *
+ * <p>{@code null} means that no special language "hint" is needed.</p>
+ *
+ * <p><strong>Editor authors:</strong> Specify this only when you are confident that the user
+ * will switch to certain languages in this context no matter what input method subtype is
+ * currently selected. Otherwise, keep this {@code null}. Explicit user actions and/or
+ * preferences would be good signals to specify this special "hint", For example, a chat
+ * application may be able to put the last used language at the top of {@link #hintLocales}
+ * based on whom the user is going to talk, by remembering what language is used in the last
+ * conversation. Do not specify {@link android.widget.TextView#getTextLocales()} only because
+ * it is used for text rendering.</p>
+ *
+ * @see android.widget.TextView#setImeHintLocales(LocaleList)
+ * @see android.widget.TextView#getImeHintLocales()
+ */
+ @Nullable
+ public LocaleList hintLocales = null;
+
+
+ /**
+ * List of acceptable MIME types for
+ * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)}.
+ *
+ * <p>{@code null} or an empty array means that
+ * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} is not supported in this
+ * editor.</p>
+ */
+ @Nullable
+ public String[] contentMimeTypes = null;
+
+ /**
+ * Ensure that the data in this EditorInfo is compatible with an application
+ * that was developed against the given target API version. This can
+ * impact the following input types:
+ * {@link InputType#TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS},
+ * {@link InputType#TYPE_TEXT_VARIATION_WEB_PASSWORD},
+ * {@link InputType#TYPE_NUMBER_VARIATION_NORMAL},
+ * {@link InputType#TYPE_NUMBER_VARIATION_PASSWORD}.
+ *
+ * <p>This is called by the framework for input method implementations;
+ * you should not generally need to call it yourself.
+ *
+ * @param targetSdkVersion The API version number that the compatible
+ * application was developed against.
+ */
+ public final void makeCompatible(int targetSdkVersion) {
+ if (targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
+ switch (inputType&(TYPE_MASK_CLASS|TYPE_MASK_VARIATION)) {
+ case TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS:
+ inputType = TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ | (inputType&TYPE_MASK_FLAGS);
+ break;
+ case TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_WEB_PASSWORD:
+ inputType = TYPE_CLASS_TEXT|TYPE_TEXT_VARIATION_PASSWORD
+ | (inputType&TYPE_MASK_FLAGS);
+ break;
+ case TYPE_CLASS_NUMBER|TYPE_NUMBER_VARIATION_NORMAL:
+ case TYPE_CLASS_NUMBER|TYPE_NUMBER_VARIATION_PASSWORD:
+ inputType = TYPE_CLASS_NUMBER
+ | (inputType&TYPE_MASK_FLAGS);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Write debug output of this object.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "inputType=0x" + Integer.toHexString(inputType)
+ + " imeOptions=0x" + Integer.toHexString(imeOptions)
+ + " privateImeOptions=" + privateImeOptions);
+ pw.println(prefix + "actionLabel=" + actionLabel
+ + " actionId=" + actionId);
+ pw.println(prefix + "initialSelStart=" + initialSelStart
+ + " initialSelEnd=" + initialSelEnd
+ + " initialCapsMode=0x"
+ + Integer.toHexString(initialCapsMode));
+ pw.println(prefix + "hintText=" + hintText
+ + " label=" + label);
+ pw.println(prefix + "packageName=" + packageName
+ + " fieldId=" + fieldId
+ + " fieldName=" + fieldName);
+ pw.println(prefix + "extras=" + extras);
+ pw.println(prefix + "hintLocales=" + hintLocales);
+ pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(inputType);
+ dest.writeInt(imeOptions);
+ dest.writeString(privateImeOptions);
+ TextUtils.writeToParcel(actionLabel, dest, flags);
+ dest.writeInt(actionId);
+ dest.writeInt(initialSelStart);
+ dest.writeInt(initialSelEnd);
+ dest.writeInt(initialCapsMode);
+ TextUtils.writeToParcel(hintText, dest, flags);
+ TextUtils.writeToParcel(label, dest, flags);
+ dest.writeString(packageName);
+ dest.writeInt(fieldId);
+ dest.writeString(fieldName);
+ dest.writeBundle(extras);
+ if (hintLocales != null) {
+ hintLocales.writeToParcel(dest, flags);
+ } else {
+ LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
+ }
+ dest.writeStringArray(contentMimeTypes);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<EditorInfo> CREATOR =
+ new Parcelable.Creator<EditorInfo>() {
+ public EditorInfo createFromParcel(Parcel source) {
+ EditorInfo res = new EditorInfo();
+ res.inputType = source.readInt();
+ res.imeOptions = source.readInt();
+ res.privateImeOptions = source.readString();
+ res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.actionId = source.readInt();
+ res.initialSelStart = source.readInt();
+ res.initialSelEnd = source.readInt();
+ res.initialCapsMode = source.readInt();
+ res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.packageName = source.readString();
+ res.fieldId = source.readInt();
+ res.fieldName = source.readString();
+ res.extras = source.readBundle();
+ LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
+ res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
+ res.contentMimeTypes = source.readStringArray();
+ return res;
+ }
+
+ public EditorInfo[] newArray(int size) {
+ return new EditorInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+}
diff --git a/android/view/inputmethod/ExtractedText.java b/android/view/inputmethod/ExtractedText.java
new file mode 100644
index 00000000..0c5d9e9f
--- /dev/null
+++ b/android/view/inputmethod/ExtractedText.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008 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.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about text that has been extracted for use by an input method.
+ *
+ * This contains information about a portion of the currently edited text,
+ * that the IME should display into its own interface while in extracted mode.
+ */
+public class ExtractedText implements Parcelable {
+ /**
+ * The text that has been extracted.
+ */
+ public CharSequence text;
+
+ /**
+ * The offset in the overall text at which the extracted text starts.
+ */
+ public int startOffset;
+
+ /**
+ * If the content is a report of a partial text change, this is the
+ * offset where the change starts and it runs until
+ * {@link #partialEndOffset}. If the content is the full text, this
+ * field is -1.
+ */
+ public int partialStartOffset;
+
+ /**
+ * If the content is a report of a partial text change, this is the offset
+ * where the change ends. Note that the actual text may be larger or
+ * smaller than the difference between this and {@link #partialStartOffset},
+ * meaning a reduction or increase, respectively, in the total text.
+ */
+ public int partialEndOffset;
+
+ /**
+ * The offset where the selection currently starts within the extracted
+ * text. The real selection start position is at
+ * <var>startOffset</var>+<var>selectionStart</var>.
+ */
+ public int selectionStart;
+
+ /**
+ * The offset where the selection currently ends within the extracted
+ * text. The real selection end position is at
+ * <var>startOffset</var>+<var>selectionEnd</var>.
+ */
+ public int selectionEnd;
+
+ /**
+ * Bit for {@link #flags}: set if the text being edited can only be on
+ * a single line.
+ */
+ public static final int FLAG_SINGLE_LINE = 0x0001;
+
+ /**
+ * Bit for {@link #flags}: set if the editor is currently in selection mode.
+ *
+ * This happens when a hardware keyboard with latched keys is attached and
+ * the shift key is currently latched.
+ */
+ public static final int FLAG_SELECTING = 0x0002;
+
+ /**
+ * Additional bit flags of information about the edited text.
+ */
+ public int flags;
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ TextUtils.writeToParcel(text, dest, flags);
+ dest.writeInt(startOffset);
+ dest.writeInt(partialStartOffset);
+ dest.writeInt(partialEndOffset);
+ dest.writeInt(selectionStart);
+ dest.writeInt(selectionEnd);
+ dest.writeInt(this.flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<ExtractedText> CREATOR
+ = new Parcelable.Creator<ExtractedText>() {
+ public ExtractedText createFromParcel(Parcel source) {
+ ExtractedText res = new ExtractedText();
+ res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.startOffset = source.readInt();
+ res.partialStartOffset = source.readInt();
+ res.partialEndOffset = source.readInt();
+ res.selectionStart = source.readInt();
+ res.selectionEnd = source.readInt();
+ res.flags = source.readInt();
+ return res;
+ }
+
+ public ExtractedText[] newArray(int size) {
+ return new ExtractedText[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/ExtractedTextRequest.java b/android/view/inputmethod/ExtractedTextRequest.java
new file mode 100644
index 00000000..bf0bef31
--- /dev/null
+++ b/android/view/inputmethod/ExtractedTextRequest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008 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.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Description of what an input method would like from an application when
+ * extract text from its input editor.
+ */
+public class ExtractedTextRequest implements Parcelable {
+ /**
+ * Arbitrary integer that can be supplied in the request, which will be
+ * delivered back when reporting updates.
+ */
+ public int token;
+
+ /**
+ * Additional request flags, having the same possible values as the
+ * flags parameter of {@link InputConnection#getTextBeforeCursor
+ * InputConnection.getTextBeforeCursor()}.
+ */
+ public int flags;
+
+ /**
+ * Hint for the maximum number of lines to return.
+ */
+ public int hintMaxLines;
+
+ /**
+ * Hint for the maximum number of characters to return.
+ */
+ public int hintMaxChars;
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(token);
+ dest.writeInt(this.flags);
+ dest.writeInt(hintMaxLines);
+ dest.writeInt(hintMaxChars);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<ExtractedTextRequest> CREATOR
+ = new Parcelable.Creator<ExtractedTextRequest>() {
+ public ExtractedTextRequest createFromParcel(Parcel source) {
+ ExtractedTextRequest res = new ExtractedTextRequest();
+ res.token = source.readInt();
+ res.flags = source.readInt();
+ res.hintMaxLines = source.readInt();
+ res.hintMaxChars = source.readInt();
+ return res;
+ }
+
+ public ExtractedTextRequest[] newArray(int size) {
+ return new ExtractedTextRequest[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/InputBinding.java b/android/view/inputmethod/InputBinding.java
new file mode 100644
index 00000000..bcd459e6
--- /dev/null
+++ b/android/view/inputmethod/InputBinding.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information given to an {@link InputMethod} about a client connecting
+ * to it.
+ */
+public final class InputBinding implements Parcelable {
+ static final String TAG = "InputBinding";
+
+ /**
+ * The connection back to the client.
+ */
+ final InputConnection mConnection;
+
+ /**
+ * A remotable token for the connection back to the client.
+ */
+ final IBinder mConnectionToken;
+
+ /**
+ * The UID where this binding came from.
+ */
+ final int mUid;
+
+ /**
+ * The PID where this binding came from.
+ */
+ final int mPid;
+
+ /**
+ * Constructor.
+ *
+ * @param conn The interface for communicating back with the application.
+ * @param connToken A remoteable token for communicating across processes.
+ * @param uid The user id of the client of this binding.
+ * @param pid The process id of where the binding came from.
+ */
+ public InputBinding(InputConnection conn, IBinder connToken,
+ int uid, int pid) {
+ mConnection = conn;
+ mConnectionToken = connToken;
+ mUid = uid;
+ mPid = pid;
+ }
+
+ /**
+ * Constructor from an existing InputBinding taking a new local input
+ * connection interface.
+ *
+ * @param conn The new connection interface.
+ * @param binding Existing binding to copy.
+ */
+ public InputBinding(InputConnection conn, InputBinding binding) {
+ mConnection = conn;
+ mConnectionToken = binding.getConnectionToken();
+ mUid = binding.getUid();
+ mPid = binding.getPid();
+ }
+
+ InputBinding(Parcel source) {
+ mConnection = null;
+ mConnectionToken = source.readStrongBinder();
+ mUid = source.readInt();
+ mPid = source.readInt();
+ }
+
+ /**
+ * Return the connection for interacting back with the application.
+ */
+ public InputConnection getConnection() {
+ return mConnection;
+ }
+
+ /**
+ * Return the token for the connection back to the application. You can
+ * not use this directly, it must be converted to a {@link InputConnection}
+ * for you.
+ */
+ public IBinder getConnectionToken() {
+ return mConnectionToken;
+ }
+
+ /**
+ * Return the user id of the client associated with this binding.
+ */
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Return the process id where this binding came from.
+ */
+ public int getPid() {
+ return mPid;
+ }
+
+ @Override
+ public String toString() {
+ return "InputBinding{" + mConnectionToken
+ + " / uid " + mUid + " / pid " + mPid + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mConnectionToken);
+ dest.writeInt(mUid);
+ dest.writeInt(mPid);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<InputBinding> CREATOR = new Parcelable.Creator<InputBinding>() {
+ public InputBinding createFromParcel(Parcel source) {
+ return new InputBinding(source);
+ }
+
+ public InputBinding[] newArray(int size) {
+ return new InputBinding[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/InputConnection.java b/android/view/inputmethod/InputConnection.java
new file mode 100644
index 00000000..57f9895f
--- /dev/null
+++ b/android/view/inputmethod/InputConnection.java
@@ -0,0 +1,902 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+/**
+ * The InputConnection interface is the communication channel from an
+ * {@link InputMethod} back to the application that is receiving its
+ * input. It is used to perform such things as reading text around the
+ * cursor, committing text to the text box, and sending raw key events
+ * to the application.
+ *
+ * <p>Starting from API Level {@link android.os.Build.VERSION_CODES#N},
+ * the system can deal with the situation where the application directly
+ * implements this class but one or more of the following methods are
+ * not implemented.</p>
+ * <ul>
+ * <li>{@link #getSelectedText(int)}, which was introduced in
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD}.</li>
+ * <li>{@link #setComposingRegion(int, int)}, which was introduced
+ * in {@link android.os.Build.VERSION_CODES#GINGERBREAD}.</li>
+ * <li>{@link #commitCorrection(CorrectionInfo)}, which was introduced
+ * in {@link android.os.Build.VERSION_CODES#HONEYCOMB}.</li>
+ * <li>{@link #requestCursorUpdates(int)}, which was introduced in
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.</li>
+ * <li>{@link #deleteSurroundingTextInCodePoints(int, int)}, which
+ * was introduced in {@link android.os.Build.VERSION_CODES#N}.</li>
+ * <li>{@link #getHandler()}, which was introduced in
+ * {@link android.os.Build.VERSION_CODES#N}.</li>
+ * <li>{@link #closeConnection()}, which was introduced in
+ * {@link android.os.Build.VERSION_CODES#N}.</li>
+ * <li>{@link #commitContent(InputContentInfo, int, Bundle)}, which was
+ * introduced in {@link android.os.Build.VERSION_CODES#N_MR1}.</li>
+ * </ul>
+ *
+ * <h3>Implementing an IME or an editor</h3>
+ * <p>Text input is the result of the synergy of two essential components:
+ * an Input Method Engine (IME) and an editor. The IME can be a
+ * software keyboard, a handwriting interface, an emoji palette, a
+ * speech-to-text engine, and so on. There are typically several IMEs
+ * installed on any given Android device. In Android, IMEs extend
+ * {@link android.inputmethodservice.InputMethodService}.
+ * For more information about how to create an IME, see the
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an input method</a> guide.
+ *
+ * The editor is the component that receives text and displays it.
+ * Typically, this is an {@link android.widget.EditText} instance, but
+ * some applications may choose to implement their own editor for
+ * various reasons. This is a large and complicated task, and an
+ * application that does this needs to make sure the behavior is
+ * consistent with standard EditText behavior in Android. An editor
+ * needs to interact with the IME, receiving commands through
+ * this InputConnection interface, and sending commands through
+ * {@link android.view.inputmethod.InputMethodManager}. An editor
+ * should start by implementing
+ * {@link android.view.View#onCreateInputConnection(EditorInfo)}
+ * to return its own input connection.</p>
+ *
+ * <p>If you are implementing your own IME, you will need to call the
+ * methods in this interface to interact with the application. Be sure
+ * to test your IME with a wide range of applications, including
+ * browsers and rich text editors, as some may have peculiarities you
+ * need to deal with. Remember your IME may not be the only source of
+ * changes on the text, and try to be as conservative as possible in
+ * the data you send and as liberal as possible in the data you
+ * receive.</p>
+ *
+ * <p>If you are implementing your own editor, you will probably need
+ * to provide your own subclass of {@link BaseInputConnection} to
+ * answer to the commands from IMEs. Please be sure to test your
+ * editor with as many IMEs as you can as their behavior can vary a
+ * lot. Also be sure to test with various languages, including CJK
+ * languages and right-to-left languages like Arabic, as these may
+ * have different input requirements. When in doubt about the
+ * behavior you should adopt for a particular call, please mimic the
+ * default TextView implementation in the latest Android version, and
+ * if you decide to drift from it, please consider carefully that
+ * inconsistencies in text editor behavior is almost universally felt
+ * as a bad thing by users.</p>
+ *
+ * <h3>Cursors, selections and compositions</h3>
+ * <p>In Android, the cursor and the selection are one and the same
+ * thing. A "cursor" is just the special case of a zero-sized
+ * selection. As such, this documentation uses them
+ * interchangeably. Any method acting "before the cursor" would act
+ * before the start of the selection if there is one, and any method
+ * acting "after the cursor" would act after the end of the
+ * selection.</p>
+ *
+ * <p>An editor needs to be able to keep track of a currently
+ * "composing" region, like the standard edition widgets do. The
+ * composition is marked in a specific style: see
+ * {@link android.text.Spanned#SPAN_COMPOSING}. IMEs use this to help
+ * the user keep track of what part of the text they are currently
+ * focusing on, and interact with the editor using
+ * {@link InputConnection#setComposingText(CharSequence, int)},
+ * {@link InputConnection#setComposingRegion(int, int)} and
+ * {@link InputConnection#finishComposingText()}.
+ * The composing region and the selection are completely independent
+ * of each other, and the IME may use them however they see fit.</p>
+ */
+public interface InputConnection {
+ /**
+ * Flag for use with {@link #getTextAfterCursor} and
+ * {@link #getTextBeforeCursor} to have style information returned
+ * along with the text. If not set, {@link #getTextAfterCursor}
+ * sends only the raw text, without style or other spans. If set,
+ * it may return a complex CharSequence of both text and style
+ * spans. <strong>Editor authors</strong>: you should strive to
+ * send text with styles if possible, but it is not required.
+ */
+ static final int GET_TEXT_WITH_STYLES = 0x0001;
+
+ /**
+ * Flag for use with {@link #getExtractedText} to indicate you
+ * would like to receive updates when the extracted text changes.
+ */
+ public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
+
+ /**
+ * Get <var>n</var> characters of text before the current cursor
+ * position.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the editor is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, null is returned. This
+ * method does not affect the text in the editor in any way, nor
+ * does it affect the selection or composing spans.</p>
+ *
+ * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+ * editor should return a {@link android.text.SpannableString}
+ * with all the spans set on the text.</p>
+ *
+ * <p><strong>IME authors:</strong> please consider this will
+ * trigger an IPC round-trip that will take some time. Assume this
+ * method consumes a lot of time. Also, please keep in mind the
+ * Editor may choose to return less characters than requested even
+ * if they are available for performance reasons.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text and use this method right away; you need to make
+ * sure the returned value is consistent with the result of the
+ * latest edits. Also, you may return less than n characters if performance
+ * dictates so, but keep in mind IMEs are relying on this for many
+ * functions: you should not, for example, limit the returned value to
+ * the current line, and specifically do not return 0 characters unless
+ * the cursor is really at the start of the text.</p>
+ *
+ * @param n The expected length of the text.
+ * @param flags Supplies additional options controlling how the text is
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+ * @return the text before the cursor position; the length of the
+ * returned text might be less than <var>n</var>.
+ */
+ public CharSequence getTextBeforeCursor(int n, int flags);
+
+ /**
+ * Get <var>n</var> characters of text after the current cursor
+ * position.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, null is returned.
+ *
+ * <p>This method does not affect the text in the editor in any
+ * way, nor does it affect the selection or composing spans.</p>
+ *
+ * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+ * editor should return a {@link android.text.SpannableString}
+ * with all the spans set on the text.</p>
+ *
+ * <p><strong>IME authors:</strong> please consider this will
+ * trigger an IPC round-trip that will take some time. Assume this
+ * method consumes a lot of time.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text and use this method right away; you need to make
+ * sure the returned value is consistent with the result of the
+ * latest edits. Also, you may return less than n characters if performance
+ * dictates so, but keep in mind IMEs are relying on this for many
+ * functions: you should not, for example, limit the returned value to
+ * the current line, and specifically do not return 0 characters unless
+ * the cursor is really at the end of the text.</p>
+ *
+ * @param n The expected length of the text.
+ * @param flags Supplies additional options controlling how the text is
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+ *
+ * @return the text after the cursor position; the length of the
+ * returned text might be less than <var>n</var>.
+ */
+ public CharSequence getTextAfterCursor(int n, int flags);
+
+ /**
+ * Gets the selected text, if any.
+ *
+ * <p>This method may fail if either the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * of seconds to return). In either case, null is returned.</p>
+ *
+ * <p>This method must not cause any changes in the editor's
+ * state.</p>
+ *
+ * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+ * editor should return a {@link android.text.SpannableString}
+ * with all the spans set on the text.</p>
+ *
+ * <p><strong>IME authors:</strong> please consider this will
+ * trigger an IPC round-trip that will take some time. Assume this
+ * method consumes a lot of time.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text or change the selection position and use this
+ * method right away; you need to make sure the returned value is
+ * consistent with the results of the latest edits.</p>
+ *
+ * @param flags Supplies additional options controlling how the text is
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+ * @return the text that is currently selected, if any, or null if
+ * no text is selected. In {@link android.os.Build.VERSION_CODES#N} and
+ * later, returns false when the target application does not implement
+ * this method.
+ */
+ public CharSequence getSelectedText(int flags);
+
+ /**
+ * Retrieve the current capitalization mode in effect at the
+ * current cursor position in the text. See
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}
+ * for more information.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, 0 is returned.</p>
+ *
+ * <p>This method does not affect the text in the editor in any
+ * way, nor does it affect the selection or composing spans.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can change the
+ * cursor position and use this method right away; you need to make
+ * sure the returned value is consistent with the results of the
+ * latest edits and changes to the cursor position.</p>
+ *
+ * @param reqModes The desired modes to retrieve, as defined by
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
+ * constants are defined so that you can simply pass the current
+ * {@link EditorInfo#inputType TextBoxAttribute.contentType} value
+ * directly in to here.
+ * @return the caps mode flags that are in effect at the current
+ * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
+ */
+ public int getCursorCapsMode(int reqModes);
+
+ /**
+ * Retrieve the current text in the input connection's editor, and
+ * monitor for any changes to it. This function returns with the
+ * current text, and optionally the input connection can send
+ * updates to the input method when its text changes.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, null is returned.</p>
+ *
+ * <p>Editor authors: as a general rule, try to comply with the
+ * fields in <code>request</code> for how many chars to return,
+ * but if performance or convenience dictates otherwise, please
+ * feel free to do what is most appropriate for your case. Also,
+ * if the
+ * {@link #GET_EXTRACTED_TEXT_MONITOR} flag is set, you should be
+ * calling
+ * {@link InputMethodManager#updateExtractedText(View, int, ExtractedText)}
+ * whenever you call
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)}.</p>
+ *
+ * @param request Description of how the text should be returned.
+ * {@link android.view.inputmethod.ExtractedTextRequest}
+ * @param flags Additional options to control the client, either 0 or
+ * {@link #GET_EXTRACTED_TEXT_MONITOR}.
+
+ * @return an {@link android.view.inputmethod.ExtractedText}
+ * object describing the state of the text view and containing the
+ * extracted text itself, or null if the input connection is no
+ * longer valid of the editor can't comply with the request for
+ * some reason.
+ */
+ public ExtractedText getExtractedText(ExtractedTextRequest request,
+ int flags);
+
+ /**
+ * Delete <var>beforeLength</var> characters of text before the
+ * current cursor position, and delete <var>afterLength</var>
+ * characters of text after the current cursor position, excluding
+ * the selection. Before and after refer to the order of the
+ * characters in the string, not to their visual representation:
+ * this means you don't have to figure out the direction of the
+ * text and can just use the indices as-is.
+ *
+ * <p>The lengths are supplied in Java chars, not in code points
+ * or in glyphs.</p>
+ *
+ * <p>Since this method only operates on text before and after the
+ * selection, it can't affect the contents of the selection. This
+ * may affect the composing span if the span includes characters
+ * that are to be deleted, but otherwise will not change it. If
+ * some characters in the composing span are deleted, the
+ * composing span will persist but get shortened by however many
+ * chars inside it have been removed.</p>
+ *
+ * <p><strong>IME authors:</strong> please be careful not to
+ * delete only half of a surrogate pair. Also take care not to
+ * delete more characters than are in the editor, as that may have
+ * ill effects on the application. Calling this method will cause
+ * the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on your service after the batch input is over.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text or change the selection position and use this
+ * method right away; you need to make sure the effects are
+ * consistent with the results of the latest edits. Also, although
+ * the IME should not send lengths bigger than the contents of the
+ * string, you should check the values for overflows and trim the
+ * indices to the size of the contents to avoid crashes. Since
+ * this changes the contents of the editor, you need to make the
+ * changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
+ * @return true on success, false if the input connection is no longer valid.
+ */
+ public boolean deleteSurroundingText(int beforeLength, int afterLength);
+
+ /**
+ * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are:
+ *
+ * <ul>
+ * <li>The lengths are supplied in code points, not in Java chars or in glyphs.</>
+ * <li>This method does nothing if there are one or more invalid surrogate pairs in the
+ * requested range.</li>
+ * </ul>
+ *
+ * <p><strong>Editor authors:</strong> In addition to the requirement in
+ * {@link #deleteSurroundingText(int, int)}, make sure to do nothing when one ore more invalid
+ * surrogate pairs are found in the requested range.</p>
+ *
+ * @see #deleteSurroundingText(int, int)
+ *
+ * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
+ * @return true on success, false if the input connection is no longer valid. Returns
+ * {@code false} when the target application does not implement this method.
+ */
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
+
+ /**
+ * Replace the currently composing text with the given text, and
+ * set the new cursor position. Any composing text set previously
+ * will be removed automatically.
+ *
+ * <p>If there is any composing span currently active, all
+ * characters that it comprises are removed. The passed text is
+ * added in its place, and a composing span is added to this
+ * text. If there is no composing span active, the passed text is
+ * added at the cursor position (removing selected characters
+ * first if any), and a composing span is added on the new text.
+ * Finally, the cursor is moved to the location specified by
+ * <code>newCursorPosition</code>.</p>
+ *
+ * <p>This is usually called by IMEs to add or remove or change
+ * characters in the composing span. Calling this method will
+ * cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.</p>
+ *
+ * <p><strong>Editor authors:</strong> please keep in mind the
+ * text may be very similar or completely different than what was
+ * in the composing span at call time, or there may not be a
+ * composing span at all. Please note that although it's not
+ * typical use, the string may be empty. Treat this normally,
+ * replacing the currently composing text with an empty string.
+ * Also, be careful with the cursor position. IMEs rely on this
+ * working exactly as described above. Since this changes the
+ * contents of the editor, you need to make the changes known to
+ * the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress. Note that this method can set the cursor position
+ * on either edge of the composing text or entirely outside it,
+ * but the IME may also go on to move the cursor position to
+ * within the composing text in a subsequent call so you should
+ * make no assumption at all: the composing text and the selection
+ * are entirely independent.</p>
+ *
+ * @param text The composing text with styles if necessary. If no style
+ * object attached to the text, the default style for composing text
+ * is used. See {@link android.text.Spanned} for how to attach style
+ * object to the text. {@link android.text.SpannableString} and
+ * {@link android.text.SpannableStringBuilder} are two
+ * implementations of the interface {@link android.text.Spanned}.
+ * @param newCursorPosition The new cursor position around the text. If
+ * > 0, this is relative to the end of the text - 1; if <= 0, this
+ * is relative to the start of the text. So a value of 1 will
+ * always advance you to the position after the full text being
+ * inserted. Note that this means you can't position the cursor
+ * within the text, because the editor can make modifications to
+ * the text you are providing so it is not possible to correctly
+ * specify locations there.
+ * @return true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean setComposingText(CharSequence text, int newCursorPosition);
+
+ /**
+ * Mark a certain region of text as composing text. If there was a
+ * composing region, the characters are left as they were and the
+ * composing span removed, as if {@link #finishComposingText()}
+ * has been called. The default style for composing text is used.
+ *
+ * <p>The passed indices are clipped to the contents bounds. If
+ * the resulting region is zero-sized, no region is marked and the
+ * effect is the same as that of calling {@link #finishComposingText()}.
+ * The order of start and end is not important. In effect, the
+ * region from start to end and the region from end to start is
+ * the same. Editor authors, be ready to accept a start that is
+ * greater than end.</p>
+ *
+ * <p>Since this does not change the contents of the text, editors should not call
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and
+ * IMEs should not receive
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}.
+ * </p>
+ *
+ * <p>This has no impact on the cursor/selection position. It may
+ * result in the cursor being anywhere inside or outside the
+ * composing region, including cases where the selection and the
+ * composing region overlap partially or entirely.</p>
+ *
+ * @param start the position in the text at which the composing region begins
+ * @param end the position in the text at which the composing region ends
+ * @return true on success, false if the input connection is no longer
+ * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the
+ * target application does not implement this method.
+ */
+ public boolean setComposingRegion(int start, int end);
+
+ /**
+ * Have the text editor finish whatever composing text is
+ * currently active. This simply leaves the text as-is, removing
+ * any special composing styling or other state that was around
+ * it. The cursor position remains unchanged.
+ *
+ * <p><strong>IME authors:</strong> be aware that this call may be
+ * expensive with some editors.</p>
+ *
+ * <p><strong>Editor authors:</strong> please note that the cursor
+ * may be anywhere in the contents when this is called, including
+ * in the middle of the composing span or in a completely
+ * unrelated place. It must not move.</p>
+ *
+ * @return true on success, false if the input connection
+ * is no longer valid.
+ */
+ public boolean finishComposingText();
+
+ /**
+ * Commit text to the text box and set the new cursor position.
+ *
+ * <p>This method removes the contents of the currently composing
+ * text and replaces it with the passed CharSequence, and then
+ * moves the cursor according to {@code newCursorPosition}. If there
+ * is no composing text when this method is called, the new text is
+ * inserted at the cursor position, removing text inside the selection
+ * if any. This behaves like calling
+ * {@link #setComposingText(CharSequence, int) setComposingText(text, newCursorPosition)}
+ * then {@link #finishComposingText()}.</p>
+ *
+ * <p>Calling this method will cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param text The text to commit. This may include styles.
+ * @param newCursorPosition The new cursor position around the text,
+ * in Java characters. If > 0, this is relative to the end
+ * of the text - 1; if <= 0, this is relative to the start
+ * of the text. So a value of 1 will always advance the cursor
+ * to the position after the full text being inserted. Note that
+ * this means you can't position the cursor within the text,
+ * because the editor can make modifications to the text
+ * you are providing so it is not possible to correctly specify
+ * locations there.
+ * @return true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean commitText(CharSequence text, int newCursorPosition);
+
+ /**
+ * Commit a completion the user has selected from the possible ones
+ * previously reported to {@link InputMethodSession#displayCompletions
+ * InputMethodSession#displayCompletions(CompletionInfo[])} or
+ * {@link InputMethodManager#displayCompletions
+ * InputMethodManager#displayCompletions(View, CompletionInfo[])}.
+ * This will result in the same behavior as if the user had
+ * selected the completion from the actual UI. In all other
+ * respects, this behaves like {@link #commitText(CharSequence, int)}.
+ *
+ * <p><strong>IME authors:</strong> please take care to send the
+ * same object that you received through
+ * {@link android.inputmethodservice.InputMethodService#onDisplayCompletions(CompletionInfo[])}.
+ * </p>
+ *
+ * <p><strong>Editor authors:</strong> if you never call
+ * {@link InputMethodSession#displayCompletions(CompletionInfo[])} or
+ * {@link InputMethodManager#displayCompletions(View, CompletionInfo[])} then
+ * a well-behaved IME should never call this on your input
+ * connection, but be ready to deal with misbehaving IMEs without
+ * crashing.</p>
+ *
+ * <p>Calling this method (with a valid {@link CompletionInfo} object)
+ * will cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param text The committed completion.
+ * @return true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean commitCompletion(CompletionInfo text);
+
+ /**
+ * Commit a correction automatically performed on the raw user's input. A
+ * typical example would be to correct typos using a dictionary.
+ *
+ * <p>Calling this method will cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param correctionInfo Detailed information about the correction.
+ * @return true on success, false if the input connection is no longer valid.
+ * In {@link android.os.Build.VERSION_CODES#N} and later, returns false
+ * when the target application does not implement this method.
+ */
+ public boolean commitCorrection(CorrectionInfo correctionInfo);
+
+ /**
+ * Set the selection of the text editor. To set the cursor
+ * position, start and end should have the same value.
+ *
+ * <p>Since this moves the cursor, calling this method will cause
+ * the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * <p>This has no effect on the composing region which must stay
+ * unchanged. The order of start and end is not important. In
+ * effect, the region from start to end and the region from end to
+ * start is the same. Editor authors, be ready to accept a start
+ * that is greater than end.</p>
+ *
+ * @param start the character index where the selection should start.
+ * @param end the character index where the selection should end.
+ * @return true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean setSelection(int start, int end);
+
+ /**
+ * Have the editor perform an action it has said it can do.
+ *
+ * <p>This is typically used by IMEs when the user presses the key
+ * associated with the action.</p>
+ *
+ * @param editorAction This must be one of the action constants for
+ * {@link EditorInfo#imeOptions EditorInfo.editorType}, such as
+ * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}.
+ * @return true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean performEditorAction(int editorAction);
+
+ /**
+ * Perform a context menu action on the field. The given id may be one of:
+ * {@link android.R.id#selectAll},
+ * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+ * {@link android.R.id#cut}, {@link android.R.id#copy},
+ * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+ * or {@link android.R.id#switchInputMethod}
+ */
+ public boolean performContextMenuAction(int id);
+
+ /**
+ * Tell the editor that you are starting a batch of editor
+ * operations. The editor will try to avoid sending you updates
+ * about its state until {@link #endBatchEdit} is called. Batch
+ * edits nest.
+ *
+ * <p><strong>IME authors:</strong> use this to avoid getting
+ * calls to
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * corresponding to intermediate state. Also, use this to avoid
+ * flickers that may arise from displaying intermediate state. Be
+ * sure to call {@link #endBatchEdit} for each call to this, or
+ * you may block updates in the editor.</p>
+ *
+ * <p><strong>Editor authors:</strong> while a batch edit is in
+ * progress, take care not to send updates to the input method and
+ * not to update the display. IMEs use this intensively to this
+ * effect. Also please note that batch edits need to nest
+ * correctly.</p>
+ *
+ * @return true if a batch edit is now in progress, false otherwise. Since
+ * this method starts a batch edit, that means it will always return true
+ * unless the input connection is no longer valid.
+ */
+ public boolean beginBatchEdit();
+
+ /**
+ * Tell the editor that you are done with a batch edit previously
+ * initiated with {@link #beginBatchEdit}. This ends the latest
+ * batch only.
+ *
+ * <p><strong>IME authors:</strong> make sure you call this
+ * exactly once for each call to {@link #beginBatchEdit}.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful about
+ * batch edit nesting. Updates still to be held back until the end
+ * of the last batch edit.</p>
+ *
+ * @return true if there is still a batch edit in progress after closing
+ * the latest one (in other words, if the nesting count is > 0), false
+ * otherwise or if the input connection is no longer valid.
+ */
+ public boolean endBatchEdit();
+
+ /**
+ * Send a key event to the process that is currently attached
+ * through this input connection. The event will be dispatched
+ * like a normal key event, to the currently focused view; this
+ * generally is the view that is providing this InputConnection,
+ * but due to the asynchronous nature of this protocol that can
+ * not be guaranteed and the focus may have changed by the time
+ * the event is received.
+ *
+ * <p>This method can be used to send key events to the
+ * application. For example, an on-screen keyboard may use this
+ * method to simulate a hardware keyboard. There are three types
+ * of standard keyboards, numeric (12-key), predictive (20-key)
+ * and ALPHA (QWERTY). You can specify the keyboard type by
+ * specify the device id of the key event.</p>
+ *
+ * <p>You will usually want to set the flag
+ * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+ * on all key event objects you give to this API; the flag will
+ * not be set for you.</p>
+ *
+ * <p>Note that it's discouraged to send such key events in normal
+ * operation; this is mainly for use with
+ * {@link android.text.InputType#TYPE_NULL} type text fields. Use
+ * the {@link #commitText} family of methods to send text to the
+ * application instead.</p>
+ *
+ * @param event The key event.
+ * @return true on success, false if the input connection is no longer
+ * valid.
+ *
+ * @see KeyEvent
+ * @see KeyCharacterMap#NUMERIC
+ * @see KeyCharacterMap#PREDICTIVE
+ * @see KeyCharacterMap#ALPHA
+ */
+ public boolean sendKeyEvent(KeyEvent event);
+
+ /**
+ * Clear the given meta key pressed states in the given input
+ * connection.
+ *
+ * <p>This can be used by the IME to clear the meta key states set
+ * by a hardware keyboard with latched meta keys, if the editor
+ * keeps track of these.</p>
+ *
+ * @param states The states to be cleared, may be one or more bits as
+ * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
+ * @return true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean clearMetaKeyStates(int states);
+
+ /**
+ * Called back when the connected IME switches between fullscreen and normal modes.
+ *
+ * <p>Note: On {@link android.os.Build.VERSION_CODES#O} and later devices, input methods are no
+ * longer allowed to directly call this method at any time. To signal this event in the target
+ * application, input methods should always call
+ * {@link InputMethodService#updateFullscreenMode()} instead. This approach should work on API
+ * {@link android.os.Build.VERSION_CODES#N_MR1} and prior devices.</p>
+ *
+ * @return For editor authors, the return value will always be ignored. For IME authors, this
+ * always returns {@code true} on {@link android.os.Build.VERSION_CODES#N_MR1} and prior
+ * devices and {@code false} on {@link android.os.Build.VERSION_CODES#O} and later
+ * devices.
+ * @see InputMethodManager#isFullscreenMode()
+ */
+ public boolean reportFullscreenMode(boolean enabled);
+
+ /**
+ * API to send private commands from an input method to its
+ * connected editor. This can be used to provide domain-specific
+ * features that are only known between certain input methods and
+ * their clients. Note that because the InputConnection protocol
+ * is asynchronous, you have no way to get a result back or know
+ * if the client understood the command; you can use the
+ * information in {@link EditorInfo} to determine if a client
+ * supports a particular command.
+ *
+ * @param action Name of the command to be performed. This <em>must</em>
+ * be a scoped name, i.e. prefixed with a package name you own, so that
+ * different developers will not create conflicting commands.
+ * @param data Any data to include with the command.
+ * @return true if the command was sent (whether or not the
+ * associated editor understood it), false if the input connection is no longer
+ * valid.
+ */
+ public boolean performPrivateCommand(String action, Bundle data);
+
+ /**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
+ * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
+ * used together with {@link #CURSOR_UPDATE_MONITOR}.
+ */
+ public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
+
+ /**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+ * whenever cursor/anchor position is changed. To disable monitoring, call
+ * {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
+ * <p>
+ * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}.
+ * </p>
+ */
+ public static final int CURSOR_UPDATE_MONITOR = 1 << 1;
+
+ /**
+ * Called by the input method to ask the editor for calling back
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to
+ * notify cursor/anchor locations.
+ *
+ * @param cursorUpdateMode {@link #CURSOR_UPDATE_IMMEDIATE} and/or
+ * {@link #CURSOR_UPDATE_MONITOR}. Pass {@code 0} to disable the effect of
+ * {@link #CURSOR_UPDATE_MONITOR}.
+ * @return {@code true} if the request is scheduled. {@code false} to indicate that when the
+ * application will not call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}.
+ * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the
+ * target application does not implement this method.
+ */
+ public boolean requestCursorUpdates(int cursorUpdateMode);
+
+ /**
+ * Called by the {@link InputMethodManager} to enable application developers to specify a
+ * dedicated {@link Handler} on which incoming IPC method calls from input methods will be
+ * dispatched.
+ *
+ * <p>Note: This does nothing when called from input methods.</p>
+ *
+ * @return {@code null} to use the default {@link Handler}.
+ */
+ public Handler getHandler();
+
+ /**
+ * Called by the system up to only once to notify that the system is about to invalidate
+ * connection between the input method and the application.
+ *
+ * <p><strong>Editor authors</strong>: You can clear all the nested batch edit right now and
+ * you no longer need to handle subsequent callbacks on this connection, including
+ * {@link #beginBatchEdit()}}. Note that although the system tries to call this method whenever
+ * possible, there may be a chance that this method is not called in some exceptional
+ * situations.</p>
+ *
+ * <p>Note: This does nothing when called from input methods.</p>
+ */
+ public void closeConnection();
+
+ /**
+ * When this flag is used, the editor will be able to request read access to the content URI
+ * contained in the {@link InputContentInfo} object.
+ *
+ * <p>Make sure that the content provider owning the Uri sets the
+ * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+ * grantUriPermissions} attribute in its manifest or included the
+ * {@link android.R.styleable#AndroidManifestGrantUriPermission
+ * &lt;grant-uri-permissions&gt;} tag. Otherwise {@link InputContentInfo#requestPermission()}
+ * can fail.</p>
+ *
+ * <p>Although calling this API is allowed only for the IME that is currently selected, the
+ * client is able to request a temporary read-only access even after the current IME is switched
+ * to any other IME as long as the client keeps {@link InputContentInfo} object.</p>
+ **/
+ public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
+ android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001
+
+ /**
+ * Called by the input method to commit content such as a PNG image to the editor.
+ *
+ * <p>In order to avoid a variety of compatibility issues, this focuses on a simple use case,
+ * where editors and IMEs are expected to work cooperatively as follows:</p>
+ * <ul>
+ * <li>Editor must keep {@link EditorInfo#contentMimeTypes} equal to {@code null} if it does
+ * not support this method at all.</li>
+ * <li>Editor can ignore this request when the MIME type specified in
+ * {@code inputContentInfo} does not match any of {@link EditorInfo#contentMimeTypes}.
+ * </li>
+ * <li>Editor can ignore the cursor position when inserting the provided content.</li>
+ * <li>Editor can return {@code true} asynchronously, even before it starts loading the
+ * content.</li>
+ * <li>Editor should provide a way to delete the content inserted by this method or to
+ * revert the effect caused by this method.</li>
+ * <li>IME should not call this method when there is any composing text, in case calling
+ * this method causes a focus change.</li>
+ * <li>IME should grant a permission for the editor to read the content. See
+ * {@link EditorInfo#packageName} about how to obtain the package name of the editor.</li>
+ * </ul>
+ *
+ * @param inputContentInfo Content to be inserted.
+ * @param flags {@link #INPUT_CONTENT_GRANT_READ_URI_PERMISSION} if the content provider
+ * allows {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
+ * grantUriPermissions} or {@code 0} if the application does not need to call
+ * {@link InputContentInfo#requestPermission()}.
+ * @param opts optional bundle data. This can be {@code null}.
+ * @return {@code true} if this request is accepted by the application, whether the request
+ * is already handled or still being handled in background, {@code false} otherwise.
+ */
+ public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
+ @Nullable Bundle opts);
+}
diff --git a/android/view/inputmethod/InputConnectionInspector.java b/android/view/inputmethod/InputConnectionInspector.java
new file mode 100644
index 00000000..5f25bf58
--- /dev/null
+++ b/android/view/inputmethod/InputConnectionInspector.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2016 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.inputmethod;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * @hide
+ */
+public final class InputConnectionInspector {
+
+ @Retention(SOURCE)
+ @IntDef({MissingMethodFlags.GET_SELECTED_TEXT,
+ MissingMethodFlags.SET_COMPOSING_REGION,
+ MissingMethodFlags.COMMIT_CORRECTION,
+ MissingMethodFlags.REQUEST_CURSOR_UPDATES,
+ MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
+ MissingMethodFlags.GET_HANDLER,
+ MissingMethodFlags.CLOSE_CONNECTION,
+ MissingMethodFlags.COMMIT_CONTENT,
+ })
+ public @interface MissingMethodFlags {
+ /**
+ * {@link InputConnection#getSelectedText(int)} is available in
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
+ */
+ int GET_SELECTED_TEXT = 1 << 0;
+ /**
+ * {@link InputConnection#setComposingRegion(int, int)} is available in
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
+ */
+ int SET_COMPOSING_REGION = 1 << 1;
+ /**
+ * {@link InputConnection#commitCorrection(CorrectionInfo)} is available in
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and later.
+ */
+ int COMMIT_CORRECTION = 1 << 2;
+ /**
+ * {@link InputConnection#requestCursorUpdates(int)} is available in
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
+ */
+ int REQUEST_CURSOR_UPDATES = 1 << 3;
+ /**
+ * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
+ * {@link android.os.Build.VERSION_CODES#N} and later.
+ */
+ int DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 1 << 4;
+ /**
+ * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
+ * {@link android.os.Build.VERSION_CODES#N} and later.
+ */
+ int GET_HANDLER = 1 << 5;
+ /**
+ * {@link InputConnection#closeConnection()}} is available in
+ * {@link android.os.Build.VERSION_CODES#N} and later.
+ */
+ int CLOSE_CONNECTION = 1 << 6;
+ /**
+ * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} is available in
+ * {@link android.os.Build.VERSION_CODES#N} MR-1 and later.
+ */
+ int COMMIT_CONTENT = 1 << 7;
+ }
+
+ private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
+ new WeakHashMap<>());
+
+ @MissingMethodFlags
+ public static int getMissingMethodFlags(@Nullable final InputConnection ic) {
+ if (ic == null) {
+ return 0;
+ }
+ // Optimization for a known class.
+ if (ic instanceof BaseInputConnection) {
+ return 0;
+ }
+ // Optimization for a known class.
+ if (ic instanceof InputConnectionWrapper) {
+ return ((InputConnectionWrapper) ic).getMissingMethodFlags();
+ }
+ return getMissingMethodFlagsInternal(ic.getClass());
+ }
+
+ @MissingMethodFlags
+ public static int getMissingMethodFlagsInternal(@NonNull final Class clazz) {
+ final Integer cachedFlags = sMissingMethodsMap.get(clazz);
+ if (cachedFlags != null) {
+ return cachedFlags;
+ }
+ int flags = 0;
+ if (!hasGetSelectedText(clazz)) {
+ flags |= MissingMethodFlags.GET_SELECTED_TEXT;
+ }
+ if (!hasSetComposingRegion(clazz)) {
+ flags |= MissingMethodFlags.SET_COMPOSING_REGION;
+ }
+ if (!hasCommitCorrection(clazz)) {
+ flags |= MissingMethodFlags.COMMIT_CORRECTION;
+ }
+ if (!hasRequestCursorUpdate(clazz)) {
+ flags |= MissingMethodFlags.REQUEST_CURSOR_UPDATES;
+ }
+ if (!hasDeleteSurroundingTextInCodePoints(clazz)) {
+ flags |= MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS;
+ }
+ if (!hasGetHandler(clazz)) {
+ flags |= MissingMethodFlags.GET_HANDLER;
+ }
+ if (!hasCloseConnection(clazz)) {
+ flags |= MissingMethodFlags.CLOSE_CONNECTION;
+ }
+ if (!hasCommitContent(clazz)) {
+ flags |= MissingMethodFlags.COMMIT_CONTENT;
+ }
+ sMissingMethodsMap.put(clazz, flags);
+ return flags;
+ }
+
+ private static boolean hasGetSelectedText(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("getSelectedText", int.class);
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasSetComposingRegion(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("setComposingRegion", int.class, int.class);
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasCommitCorrection(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("commitCorrection", CorrectionInfo.class);
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasRequestCursorUpdate(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("requestCursorUpdates", int.class);
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasDeleteSurroundingTextInCodePoints(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("deleteSurroundingTextInCodePoints", int.class,
+ int.class);
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasGetHandler(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("getHandler");
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasCloseConnection(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("closeConnection");
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ private static boolean hasCommitContent(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("commitContent", InputContentInfo.class,
+ int.class, Bundle.class);
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
+ final StringBuilder sb = new StringBuilder();
+ boolean isEmpty = true;
+ if ((flags & MissingMethodFlags.GET_SELECTED_TEXT) != 0) {
+ sb.append("getSelectedText(int)");
+ isEmpty = false;
+ }
+ if ((flags & MissingMethodFlags.SET_COMPOSING_REGION) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("setComposingRegion(int, int)");
+ isEmpty = false;
+ }
+ if ((flags & MissingMethodFlags.COMMIT_CORRECTION) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("commitCorrection(CorrectionInfo)");
+ isEmpty = false;
+ }
+ if ((flags & MissingMethodFlags.REQUEST_CURSOR_UPDATES) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("requestCursorUpdate(int)");
+ isEmpty = false;
+ }
+ if ((flags & MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("deleteSurroundingTextInCodePoints(int, int)");
+ isEmpty = false;
+ }
+ if ((flags & MissingMethodFlags.GET_HANDLER) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("getHandler()");
+ }
+ if ((flags & MissingMethodFlags.CLOSE_CONNECTION) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("closeConnection()");
+ }
+ if ((flags & MissingMethodFlags.COMMIT_CONTENT) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("commitContent(InputContentInfo, Bundle)");
+ }
+ return sb.toString();
+ }
+}
diff --git a/android/view/inputmethod/InputConnectionWrapper.java b/android/view/inputmethod/InputConnectionWrapper.java
new file mode 100644
index 00000000..317730ca
--- /dev/null
+++ b/android/view/inputmethod/InputConnectionWrapper.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.KeyEvent;
+
+/**
+ * <p>Wrapper class for proxying calls to another InputConnection. Subclass and have fun!
+ */
+public class InputConnectionWrapper implements InputConnection {
+ private InputConnection mTarget;
+ final boolean mMutable;
+ @InputConnectionInspector.MissingMethodFlags
+ private int mMissingMethodFlags;
+
+ /**
+ * Initializes a wrapper.
+ *
+ * <p><b>Caveat:</b> Although the system can accept {@code (InputConnection) null} in some
+ * places, you cannot emulate such a behavior by non-null {@link InputConnectionWrapper} that
+ * has {@code null} in {@code target}.</p>
+ * @param target the {@link InputConnection} to be proxied.
+ * @param mutable set {@code true} to protect this object from being reconfigured to target
+ * another {@link InputConnection}. Note that this is ignored while the target is {@code null}.
+ */
+ public InputConnectionWrapper(InputConnection target, boolean mutable) {
+ mMutable = mutable;
+ mTarget = target;
+ mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target);
+ }
+
+ /**
+ * Change the target of the input connection.
+ *
+ * <p><b>Caveat:</b> Although the system can accept {@code (InputConnection) null} in some
+ * places, you cannot emulate such a behavior by non-null {@link InputConnectionWrapper} that
+ * has {@code null} in {@code target}.</p>
+ * @param target the {@link InputConnection} to be proxied.
+ * @throws SecurityException when this wrapper has non-null target and is immutable.
+ */
+ public void setTarget(InputConnection target) {
+ if (mTarget != null && !mMutable) {
+ throw new SecurityException("not mutable");
+ }
+ mTarget = target;
+ mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target);
+ }
+
+ /**
+ * @hide
+ */
+ @InputConnectionInspector.MissingMethodFlags
+ public int getMissingMethodFlags() {
+ return mMissingMethodFlags;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ return mTarget.getTextBeforeCursor(n, flags);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ return mTarget.getTextAfterCursor(n, flags);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public CharSequence getSelectedText(int flags) {
+ return mTarget.getSelectedText(flags);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public int getCursorCapsMode(int reqModes) {
+ return mTarget.getCursorCapsMode(reqModes);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ return mTarget.getExtractedText(request, flags);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ return mTarget.deleteSurroundingText(beforeLength, afterLength);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ return mTarget.setComposingText(text, newCursorPosition);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean setComposingRegion(int start, int end) {
+ return mTarget.setComposingRegion(start, end);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean finishComposingText() {
+ return mTarget.finishComposingText();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ return mTarget.commitText(text, newCursorPosition);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean commitCompletion(CompletionInfo text) {
+ return mTarget.commitCompletion(text);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean commitCorrection(CorrectionInfo correctionInfo) {
+ return mTarget.commitCorrection(correctionInfo);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean setSelection(int start, int end) {
+ return mTarget.setSelection(start, end);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean performEditorAction(int editorAction) {
+ return mTarget.performEditorAction(editorAction);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean performContextMenuAction(int id) {
+ return mTarget.performContextMenuAction(id);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean beginBatchEdit() {
+ return mTarget.beginBatchEdit();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean endBatchEdit() {
+ return mTarget.endBatchEdit();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean sendKeyEvent(KeyEvent event) {
+ return mTarget.sendKeyEvent(event);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean clearMetaKeyStates(int states) {
+ return mTarget.clearMetaKeyStates(states);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean reportFullscreenMode(boolean enabled) {
+ return mTarget.reportFullscreenMode(enabled);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return mTarget.performPrivateCommand(action, data);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean requestCursorUpdates(int cursorUpdateMode) {
+ return mTarget.requestCursorUpdates(cursorUpdateMode);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public Handler getHandler() {
+ return mTarget.getHandler();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public void closeConnection() {
+ mTarget.closeConnection();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+ return mTarget.commitContent(inputContentInfo, flags, opts);
+ }
+}
diff --git a/android/view/inputmethod/InputContentInfo.java b/android/view/inputmethod/InputContentInfo.java
new file mode 100644
index 00000000..7104a287
--- /dev/null
+++ b/android/view/inputmethod/InputContentInfo.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2016 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ClipDescription;
+import android.content.ContentProvider;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.inputmethod.IInputContentUriToken;
+
+import java.security.InvalidParameterException;
+
+/**
+ * A container object with which input methods can send content files to the target application.
+ */
+public final class InputContentInfo implements Parcelable {
+
+ /**
+ * The content URI that may or may not have a user ID embedded by
+ * {@link ContentProvider#maybeAddUserId(Uri, int)}. This always preserves the exact value
+ * specified to a constructor. In other words, if it had user ID embedded when it was passed
+ * to the constructor, it still has the same user ID no matter if it is valid or not.
+ */
+ @NonNull
+ private final Uri mContentUri;
+ /**
+ * The user ID to which {@link #mContentUri} belongs to. If {@link #mContentUri} already
+ * embedded the user ID when it was specified then this fields has the same user ID. Otherwise
+ * the user ID is determined based on the process ID when the constructor is called.
+ *
+ * <p>CAUTION: If you received {@link InputContentInfo} from a different process, there is no
+ * guarantee that this value is correct and valid. Never use this for any security purpose</p>
+ */
+ @UserIdInt
+ private final int mContentUriOwnerUserId;
+ @NonNull
+ private final ClipDescription mDescription;
+ @Nullable
+ private final Uri mLinkUri;
+ @NonNull
+ private IInputContentUriToken mUriToken;
+
+ /**
+ * Constructs {@link InputContentInfo} object only with mandatory data.
+ *
+ * @param contentUri Content URI to be exported from the input method.
+ * This cannot be {@code null}.
+ * @param description A {@link ClipDescription} object that contains the metadata of
+ * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+ * {@link ClipDescription#getLabel()} should be describing the content specified by
+ * {@code contentUri} for accessibility reasons.
+ */
+ public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description) {
+ this(contentUri, description, null /* link Uri */);
+ }
+
+ /**
+ * Constructs {@link InputContentInfo} object with additional link URI.
+ *
+ * @param contentUri Content URI to be exported from the input method.
+ * This cannot be {@code null}.
+ * @param description A {@link ClipDescription} object that contains the metadata of
+ * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+ * {@link ClipDescription#getLabel()} should be describing the content specified by
+ * {@code contentUri} for accessibility reasons.
+ * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+ * a way to navigate the user to the specified web page if this is not {@code null}.
+ * @throws InvalidParameterException if any invalid parameter is specified.
+ */
+ public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description,
+ @Nullable Uri linkUri) {
+ validateInternal(contentUri, description, linkUri, true /* throwException */);
+ mContentUri = contentUri;
+ mContentUriOwnerUserId =
+ ContentProvider.getUserIdFromUri(mContentUri, UserHandle.myUserId());
+ mDescription = description;
+ mLinkUri = linkUri;
+ }
+
+ /**
+ * @return {@code true} if all the fields are valid.
+ * @hide
+ */
+ public boolean validate() {
+ return validateInternal(mContentUri, mDescription, mLinkUri, false /* throwException */);
+ }
+
+ /**
+ * Constructs {@link InputContentInfo} object with additional link URI.
+ *
+ * @param contentUri Content URI to be exported from the input method.
+ * This cannot be {@code null}.
+ * @param description A {@link ClipDescription} object that contains the metadata of
+ * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
+ * {@link ClipDescription#getLabel()} should be describing the content specified by
+ * {@code contentUri} for accessibility reasons.
+ * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
+ * a way to navigate the user to the specified web page if this is not {@code null}.
+ * @param throwException {@code true} if this method should throw an
+ * {@link InvalidParameterException}.
+ * @throws InvalidParameterException if any invalid parameter is specified.
+ */
+ private static boolean validateInternal(@NonNull Uri contentUri,
+ @NonNull ClipDescription description, @Nullable Uri linkUri, boolean throwException) {
+ if (contentUri == null) {
+ if (throwException) {
+ throw new NullPointerException("contentUri");
+ }
+ return false;
+ }
+ if (description == null) {
+ if (throwException) {
+ throw new NullPointerException("description");
+ }
+ return false;
+ }
+ final String contentUriScheme = contentUri.getScheme();
+ if (!"content".equals(contentUriScheme)) {
+ if (throwException) {
+ throw new InvalidParameterException("contentUri must have content scheme");
+ }
+ return false;
+ }
+ if (linkUri != null) {
+ final String scheme = linkUri.getScheme();
+ if (scheme == null ||
+ (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
+ if (throwException) {
+ throw new InvalidParameterException(
+ "linkUri must have either http or https scheme");
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return Content URI with which the content can be obtained.
+ */
+ @NonNull
+ public Uri getContentUri() {
+ // Fix up the content URI when and only when the caller's user ID does not match the owner's
+ // user ID.
+ if (mContentUriOwnerUserId != UserHandle.myUserId()) {
+ return ContentProvider.maybeAddUserId(mContentUri, mContentUriOwnerUserId);
+ }
+ return mContentUri;
+ }
+
+ /**
+ * @return {@link ClipDescription} object that contains the metadata of {@code #getContentUri()}
+ * such as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility
+ * purpose.
+ */
+ @NonNull
+ public ClipDescription getDescription() { return mDescription; }
+
+ /**
+ * @return An optional {@code http} or {@code https} URI that is related to this content.
+ */
+ @Nullable
+ public Uri getLinkUri() { return mLinkUri; }
+
+ void setUriToken(IInputContentUriToken token) {
+ if (mUriToken != null) {
+ throw new IllegalStateException("URI token is already set");
+ }
+ mUriToken = token;
+ }
+
+ /**
+ * Requests a temporary read-only access permission for content URI associated with this object.
+ *
+ * <p>Does nothing if the temporary permission is already granted.</p>
+ */
+ public void requestPermission() {
+ if (mUriToken == null) {
+ return;
+ }
+ try {
+ mUriToken.take();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Releases a temporary read-only access permission for content URI associated with this object.
+ *
+ * <p>Does nothing if the temporary permission is not granted.</p>
+ */
+ public void releasePermission() {
+ if (mUriToken == null) {
+ return;
+ }
+ try {
+ mUriToken.release();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Uri.writeToParcel(dest, mContentUri);
+ dest.writeInt(mContentUriOwnerUserId);
+ mDescription.writeToParcel(dest, flags);
+ Uri.writeToParcel(dest, mLinkUri);
+ if (mUriToken != null) {
+ dest.writeInt(1);
+ dest.writeStrongBinder(mUriToken.asBinder());
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ private InputContentInfo(@NonNull Parcel source) {
+ mContentUri = Uri.CREATOR.createFromParcel(source);
+ mContentUriOwnerUserId = source.readInt();
+ mDescription = ClipDescription.CREATOR.createFromParcel(source);
+ mLinkUri = Uri.CREATOR.createFromParcel(source);
+ if (source.readInt() == 1) {
+ mUriToken = IInputContentUriToken.Stub.asInterface(source.readStrongBinder());
+ } else {
+ mUriToken = null;
+ }
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<InputContentInfo> CREATOR
+ = new Parcelable.Creator<InputContentInfo>() {
+ @Override
+ public InputContentInfo createFromParcel(Parcel source) {
+ return new InputContentInfo(source);
+ }
+
+ @Override
+ public InputContentInfo[] newArray(int size) {
+ return new InputContentInfo[size];
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/InputMethod.java b/android/view/inputmethod/InputMethod.java
new file mode 100644
index 00000000..0922422c
--- /dev/null
+++ b/android/view/inputmethod/InputMethod.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+
+/**
+ * The InputMethod interface represents an input method which can generate key
+ * events and text, such as digital, email addresses, CJK characters, other
+ * language characters, and etc., while handling various input events, and send
+ * the text back to the application that requests text input. See
+ * {@link InputMethodManager} for more general information about the
+ * architecture.
+ *
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ *
+ * <p>Those implementing input methods should normally do so by deriving from
+ * {@link InputMethodService} or one of its subclasses. When implementing
+ * an input method, the service component containing it must also supply
+ * a {@link #SERVICE_META_DATA} meta-data field, referencing an XML resource
+ * providing details about the input method. All input methods also must
+ * require that clients hold the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} in order to interact
+ * with the service; if this is not required, the system will not use that
+ * input method, because it can not trust that it is not compromised.
+ *
+ * <p>The InputMethod interface is actually split into two parts: the interface
+ * here is the top-level interface to the input method, providing all
+ * access to it, which only the system can access (due to the BIND_INPUT_METHOD
+ * permission requirement). In addition its method
+ * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}
+ * can be called to instantate a secondary {@link InputMethodSession} interface
+ * which is what clients use to communicate with the input method.
+ */
+public interface InputMethod {
+ /**
+ * This is the interface name that a service implementing an input
+ * method should say that it supports -- that is, this is the action it
+ * uses for its intent filter.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.view.InputMethod";
+
+ /**
+ * Name under which an InputMethod service component publishes information
+ * about itself. This meta-data must reference an XML resource containing
+ * an
+ * <code>&lt;{@link android.R.styleable#InputMethod input-method}&gt;</code>
+ * tag.
+ */
+ public static final String SERVICE_META_DATA = "android.view.im";
+
+ public interface SessionCallback {
+ public void sessionCreated(InputMethodSession session);
+ }
+
+ /**
+ * Called first thing after an input method is created, this supplies a
+ * unique token for the session it has with the system service. It is
+ * needed to identify itself with the service to validate its operations.
+ * This token <strong>must not</strong> be passed to applications, since
+ * it grants special priviledges that should not be given to applications.
+ *
+ * <p>Note: to protect yourself from malicious clients, you should only
+ * accept the first token given to you. Any after that may come from the
+ * client.
+ */
+ public void attachToken(IBinder token);
+
+ /**
+ * Bind a new application environment in to the input method, so that it
+ * can later start and stop input processing.
+ * Typically this method is called when this input method is enabled in an
+ * application for the first time.
+ *
+ * @param binding Information about the application window that is binding
+ * to the input method.
+ *
+ * @see InputBinding
+ * @see #unbindInput()
+ */
+ public void bindInput(InputBinding binding);
+
+ /**
+ * Unbind an application environment, called when the information previously
+ * set by {@link #bindInput} is no longer valid for this input method.
+ *
+ * <p>
+ * Typically this method is called when the application changes to be
+ * non-foreground.
+ */
+ public void unbindInput();
+
+ /**
+ * This method is called when the application starts to receive text and it
+ * is ready for this input method to process received events and send result
+ * text back to the application.
+ *
+ * @param inputConnection Optional specific input connection for
+ * communicating with the text box; if null, you should use the generic
+ * bound input connection.
+ * @param info Information about the text box (typically, an EditText)
+ * that requests input.
+ *
+ * @see EditorInfo
+ */
+ public void startInput(InputConnection inputConnection, EditorInfo info);
+
+ /**
+ * This method is called when the state of this input method needs to be
+ * reset.
+ *
+ * <p>
+ * Typically, this method is called when the input focus is moved from one
+ * text box to another.
+ *
+ * @param inputConnection Optional specific input connection for
+ * communicating with the text box; if null, you should use the generic
+ * bound input connection.
+ * @param attribute The attribute of the text box (typically, a EditText)
+ * that requests input.
+ *
+ * @see EditorInfo
+ */
+ public void restartInput(InputConnection inputConnection, EditorInfo attribute);
+
+ /**
+ * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or
+ * {@code {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched.
+ *
+ * <p>Note: This method is hidden because the {@code startInputToken} that this method is
+ * dealing with is one of internal details, which should not be exposed to the IME developers.
+ * If you override this method, you are responsible for not breaking existing IMEs that expect
+ * {@link #startInput(InputConnection, EditorInfo)} to be still called back.</p>
+ *
+ * @param inputConnection optional specific input connection for communicating with the text
+ * box; if {@code null}, you should use the generic bound input
+ * connection
+ * @param editorInfo information about the text box (typically, an EditText) that requests input
+ * @param restarting {@code false} if this corresponds to
+ * {@link #startInput(InputConnection, EditorInfo)}. Otherwise this
+ * corresponds to {@link #restartInput(InputConnection, EditorInfo)}.
+ * @param startInputToken a token that identifies a logical session that starts with this method
+ * call. Some internal IPCs such as {@link
+ * InputMethodManager#setImeWindowStatus(IBinder, IBinder, int, int)}
+ * require this token to work, and you have to keep the token alive until
+ * the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
+ * long as your implementation of {@link InputMethod} relies on such
+ * IPCs
+ * @see #startInput(InputConnection, EditorInfo)
+ * @see #restartInput(InputConnection, EditorInfo)
+ * @see EditorInfo
+ * @hide
+ */
+ default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
+ @NonNull EditorInfo editorInfo, boolean restarting,
+ @NonNull IBinder startInputToken) {
+ if (restarting) {
+ restartInput(inputConnection, editorInfo);
+ } else {
+ startInput(inputConnection, editorInfo);
+ }
+ }
+
+ /**
+ * Create a new {@link InputMethodSession} that can be handed to client
+ * applications for interacting with the input method. You can later
+ * use {@link #revokeSession(InputMethodSession)} to destroy the session
+ * so that it can no longer be used by any clients.
+ *
+ * @param callback Interface that is called with the newly created session.
+ */
+ public void createSession(SessionCallback callback);
+
+ /**
+ * Control whether a particular input method session is active.
+ *
+ * @param session The {@link InputMethodSession} previously provided through
+ * SessionCallback.sessionCreated() that is to be changed.
+ */
+ public void setSessionEnabled(InputMethodSession session, boolean enabled);
+
+ /**
+ * Disable and destroy a session that was previously created with
+ * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}.
+ * After this call, the given session interface is no longer active and
+ * calls on it will fail.
+ *
+ * @param session The {@link InputMethodSession} previously provided through
+ * SessionCallback.sessionCreated() that is to be revoked.
+ */
+ public void revokeSession(InputMethodSession session);
+
+ /**
+ * Flag for {@link #showSoftInput}: this show has been explicitly
+ * requested by the user. If not set, the system has decided it may be
+ * a good idea to show the input method based on a navigation operation
+ * in the UI.
+ */
+ public static final int SHOW_EXPLICIT = 0x00001;
+
+ /**
+ * Flag for {@link #showSoftInput}: this show has been forced to
+ * happen by the user. If set, the input method should remain visible
+ * until deliberated dismissed by the user in its UI.
+ */
+ public static final int SHOW_FORCED = 0x00002;
+
+ /**
+ * Request that any soft input part of the input method be shown to the user.
+ *
+ * @param flags Provides additional information about the show request.
+ * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
+ * @param resultReceiver The client requesting the show may wish to
+ * be told the impact of their request, which should be supplied here.
+ * The result code should be
+ * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+ * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+ * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+ * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+ */
+ public void showSoftInput(int flags, ResultReceiver resultReceiver);
+
+ /**
+ * Request that any soft input part of the input method be hidden from the user.
+ * @param flags Provides additional information about the show request.
+ * Currently always 0.
+ * @param resultReceiver The client requesting the show may wish to
+ * be told the impact of their request, which should be supplied here.
+ * The result code should be
+ * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+ * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+ * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+ * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+ */
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver);
+
+ /**
+ * Notify that the input method subtype is being changed in the same input method.
+ * @param subtype New subtype of the notified input method
+ */
+ public void changeInputMethodSubtype(InputMethodSubtype subtype);
+}
diff --git a/android/view/inputmethod/InputMethodInfo.java b/android/view/inputmethod/InputMethodInfo.java
new file mode 100644
index 00000000..f0645b89
--- /dev/null
+++ b/android/view/inputmethod/InputMethodInfo.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Printer;
+import android.util.Slog;
+import android.util.Xml;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to specify meta information of an input method.
+ *
+ * <p>It should be defined in an XML resource file with an {@code <input-method>} element.
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an Input Method</a>.</p>
+ *
+ * @see InputMethodSubtype
+ *
+ * @attr ref android.R.styleable#InputMethod_settingsActivity
+ * @attr ref android.R.styleable#InputMethod_isDefault
+ * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod
+ */
+public final class InputMethodInfo implements Parcelable {
+ static final String TAG = "InputMethodInfo";
+
+ /**
+ * The Service that implements this input method component.
+ */
+ final ResolveInfo mService;
+
+ /**
+ * The unique string Id to identify the input method. This is generated
+ * from the input method component.
+ */
+ final String mId;
+
+ /**
+ * The input method setting activity's name, used by the system settings to
+ * launch the setting activity of this input method.
+ */
+ final String mSettingsActivityName;
+
+ /**
+ * The resource in the input method's .apk that holds a boolean indicating
+ * whether it should be considered the default input method for this
+ * system. This is a resource ID instead of the final value so that it
+ * can change based on the configuration (in particular locale).
+ */
+ final int mIsDefaultResId;
+
+ /**
+ * An array-like container of the subtypes.
+ */
+ private final InputMethodSubtypeArray mSubtypes;
+
+ private final boolean mIsAuxIme;
+
+ /**
+ * Caveat: mForceDefault must be false for production. This flag is only for test.
+ */
+ private final boolean mForceDefault;
+
+ /**
+ * The flag whether this IME supports ways to switch to a next input method (e.g. globe key.)
+ */
+ private final boolean mSupportsSwitchingToNextInputMethod;
+
+ /**
+ * @param service the {@link ResolveInfo} corresponds in which the IME is implemented.
+ * @return a unique ID to be returned by {@link #getId()}. We have used
+ * {@link ComponentName#flattenToShortString()} for this purpose (and it is already
+ * unrealistic to switch to a different scheme as it is already implicitly assumed in
+ * many places).
+ * @hide
+ */
+ public static String computeId(@NonNull ResolveInfo service) {
+ final ServiceInfo si = service.serviceInfo;
+ return new ComponentName(si.packageName, si.name).flattenToShortString();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context The Context in which we are parsing the input method.
+ * @param service The ResolveInfo returned from the package manager about
+ * this input method's component.
+ */
+ public InputMethodInfo(Context context, ResolveInfo service)
+ throws XmlPullParserException, IOException {
+ this(context, service, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context The Context in which we are parsing the input method.
+ * @param service The ResolveInfo returned from the package manager about
+ * this input method's component.
+ * @param additionalSubtypes additional subtypes being added to this InputMethodInfo
+ * @hide
+ */
+ public InputMethodInfo(Context context, ResolveInfo service,
+ List<InputMethodSubtype> additionalSubtypes)
+ throws XmlPullParserException, IOException {
+ mService = service;
+ ServiceInfo si = service.serviceInfo;
+ mId = computeId(service);
+ boolean isAuxIme = true;
+ boolean supportsSwitchingToNextInputMethod = false; // false as default
+ mForceDefault = false;
+
+ PackageManager pm = context.getPackageManager();
+ String settingsActivityComponent = null;
+ int isDefaultResId = 0;
+
+ XmlResourceParser parser = null;
+ final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ try {
+ parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No "
+ + InputMethod.SERVICE_META_DATA + " meta-data");
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"input-method".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with input-method tag");
+ }
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.InputMethod);
+ settingsActivityComponent = sa.getString(
+ com.android.internal.R.styleable.InputMethod_settingsActivity);
+ isDefaultResId = sa.getResourceId(
+ com.android.internal.R.styleable.InputMethod_isDefault, 0);
+ supportsSwitchingToNextInputMethod = sa.getBoolean(
+ com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod,
+ false);
+ sa.recycle();
+
+ final int depth = parser.getDepth();
+ // Parse all subtypes
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ nodeName = parser.getName();
+ if (!"subtype".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data in input-method does not start with subtype tag");
+ }
+ final TypedArray a = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.InputMethod_Subtype);
+ final InputMethodSubtype subtype = new InputMethodSubtypeBuilder()
+ .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable
+ .InputMethod_Subtype_label, 0))
+ .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
+ .InputMethod_Subtype_icon, 0))
+ .setLanguageTag(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_languageTag))
+ .setSubtypeLocale(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_imeSubtypeLocale))
+ .setSubtypeMode(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_imeSubtypeMode))
+ .setSubtypeExtraValue(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_imeSubtypeExtraValue))
+ .setIsAuxiliary(a.getBoolean(com.android.internal.R.styleable
+ .InputMethod_Subtype_isAuxiliary, false))
+ .setOverridesImplicitlyEnabledSubtype(a.getBoolean(
+ com.android.internal.R.styleable
+ .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false))
+ .setSubtypeId(a.getInt(com.android.internal.R.styleable
+ .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */))
+ .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable
+ .InputMethod_Subtype_isAsciiCapable, false)).build();
+ if (!subtype.isAuxiliary()) {
+ isAuxIme = false;
+ }
+ subtypes.add(subtype);
+ }
+ }
+ } catch (NameNotFoundException | IndexOutOfBoundsException | NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Unable to create context for: " + si.packageName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+
+ if (subtypes.size() == 0) {
+ isAuxIme = false;
+ }
+
+ if (additionalSubtypes != null) {
+ final int N = additionalSubtypes.size();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype subtype = additionalSubtypes.get(i);
+ if (!subtypes.contains(subtype)) {
+ subtypes.add(subtype);
+ } else {
+ Slog.w(TAG, "Duplicated subtype definition found: "
+ + subtype.getLocale() + ", " + subtype.getMode());
+ }
+ }
+ }
+ mSubtypes = new InputMethodSubtypeArray(subtypes);
+ mSettingsActivityName = settingsActivityComponent;
+ mIsDefaultResId = isDefaultResId;
+ mIsAuxIme = isAuxIme;
+ mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+ }
+
+ InputMethodInfo(Parcel source) {
+ mId = source.readString();
+ mSettingsActivityName = source.readString();
+ mIsDefaultResId = source.readInt();
+ mIsAuxIme = source.readInt() == 1;
+ mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
+ mService = ResolveInfo.CREATOR.createFromParcel(source);
+ mSubtypes = new InputMethodSubtypeArray(source);
+ mForceDefault = false;
+ }
+
+ /**
+ * Temporary API for creating a built-in input method for test.
+ */
+ public InputMethodInfo(String packageName, String className,
+ CharSequence label, String settingsActivity) {
+ this(buildDummyResolveInfo(packageName, className, label), false /* isAuxIme */,
+ settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
+ false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */);
+ }
+
+ /**
+ * Temporary API for creating a built-in input method for test.
+ * @hide
+ */
+ public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
+ String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
+ boolean forceDefault) {
+ this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
+ true /* supportsSwitchingToNextInputMethod */);
+ }
+
+ /**
+ * Temporary API for creating a built-in input method for test.
+ * @hide
+ */
+ public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
+ List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
+ boolean supportsSwitchingToNextInputMethod) {
+ final ServiceInfo si = ri.serviceInfo;
+ mService = ri;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+ mSettingsActivityName = settingsActivity;
+ mIsDefaultResId = isDefaultResId;
+ mIsAuxIme = isAuxIme;
+ mSubtypes = new InputMethodSubtypeArray(subtypes);
+ mForceDefault = forceDefault;
+ mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+ }
+
+ private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
+ CharSequence label) {
+ ResolveInfo ri = new ResolveInfo();
+ ServiceInfo si = new ServiceInfo();
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.enabled = true;
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = packageName;
+ si.name = className;
+ si.exported = true;
+ si.nonLocalizedLabel = label;
+ ri.serviceInfo = si;
+ return ri;
+ }
+
+ /**
+ * Return a unique ID for this input method. The ID is generated from
+ * the package and class name implementing the method.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Return the .apk package that implements this input method.
+ */
+ public String getPackageName() {
+ return mService.serviceInfo.packageName;
+ }
+
+ /**
+ * Return the class name of the service component that implements
+ * this input method.
+ */
+ public String getServiceName() {
+ return mService.serviceInfo.name;
+ }
+
+ /**
+ * Return the raw information about the Service implementing this
+ * input method. Do not modify the returned object.
+ */
+ public ServiceInfo getServiceInfo() {
+ return mService.serviceInfo;
+ }
+
+ /**
+ * Return the component of the service that implements this input
+ * method.
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName,
+ mService.serviceInfo.name);
+ }
+
+ /**
+ * Load the user-displayed label for this input method.
+ *
+ * @param pm Supply a PackageManager used to load the input method's
+ * resources.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ return mService.loadLabel(pm);
+ }
+
+ /**
+ * Load the user-displayed icon for this input method.
+ *
+ * @param pm Supply a PackageManager used to load the input method's
+ * resources.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ return mService.loadIcon(pm);
+ }
+
+ /**
+ * Return the class name of an activity that provides a settings UI for
+ * the input method. You can launch this activity be starting it with
+ * an {@link android.content.Intent} whose action is MAIN and with an
+ * explicit {@link android.content.ComponentName}
+ * composed of {@link #getPackageName} and the class name returned here.
+ *
+ * <p>A null will be returned if there is no settings activity associated
+ * with the input method.</p>
+ */
+ public String getSettingsActivity() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Return the count of the subtypes of Input Method.
+ */
+ public int getSubtypeCount() {
+ return mSubtypes.getCount();
+ }
+
+ /**
+ * Return the Input Method's subtype at the specified index.
+ *
+ * @param index the index of the subtype to return.
+ */
+ public InputMethodSubtype getSubtypeAt(int index) {
+ return mSubtypes.get(index);
+ }
+
+ /**
+ * Return the resource identifier of a resource inside of this input
+ * method's .apk that determines whether it should be considered a
+ * default input method for the system.
+ */
+ public int getIsDefaultResourceId() {
+ return mIsDefaultResId;
+ }
+
+ /**
+ * Return whether or not this ime is a default ime or not.
+ * @hide
+ */
+ public boolean isDefault(Context context) {
+ if (mForceDefault) {
+ return true;
+ }
+ try {
+ if (getIsDefaultResourceId() == 0) {
+ return false;
+ }
+ final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
+ return res.getBoolean(getIsDefaultResourceId());
+ } catch (NameNotFoundException | NotFoundException e) {
+ return false;
+ }
+ }
+
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "mId=" + mId
+ + " mSettingsActivityName=" + mSettingsActivityName
+ + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod);
+ pw.println(prefix + "mIsDefaultResId=0x"
+ + Integer.toHexString(mIsDefaultResId));
+ pw.println(prefix + "Service:");
+ mService.dump(pw, prefix + " ");
+ }
+
+ @Override
+ public String toString() {
+ return "InputMethodInfo{" + mId
+ + ", settings: "
+ + mSettingsActivityName + "}";
+ }
+
+ /**
+ * Used to test whether the given parameter object is an
+ * {@link InputMethodInfo} and its Id is the same to this one.
+ *
+ * @return true if the given parameter object is an
+ * {@link InputMethodInfo} and its Id is the same to this one.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (o == null) return false;
+
+ if (!(o instanceof InputMethodInfo)) return false;
+
+ InputMethodInfo obj = (InputMethodInfo) o;
+ return mId.equals(obj.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mId.hashCode();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isAuxiliaryIme() {
+ return mIsAuxIme;
+ }
+
+ /**
+ * @return true if this input method supports ways to switch to a next input method.
+ * @hide
+ */
+ public boolean supportsSwitchingToNextInputMethod() {
+ return mSupportsSwitchingToNextInputMethod;
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeString(mSettingsActivityName);
+ dest.writeInt(mIsDefaultResId);
+ dest.writeInt(mIsAuxIme ? 1 : 0);
+ dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
+ mService.writeToParcel(dest, flags);
+ mSubtypes.writeToParcel(dest);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<InputMethodInfo> CREATOR
+ = new Parcelable.Creator<InputMethodInfo>() {
+ @Override
+ public InputMethodInfo createFromParcel(Parcel source) {
+ return new InputMethodInfo(source);
+ }
+
+ @Override
+ public InputMethodInfo[] newArray(int size) {
+ return new InputMethodInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/inputmethod/InputMethodManager.java b/android/view/inputmethod/InputMethodManager.java
new file mode 100644
index 00000000..92d1de8e
--- /dev/null
+++ b/android/view/inputmethod/InputMethodManager.java
@@ -0,0 +1,2455 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.Trace;
+import android.text.style.SuggestionSpan;
+import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+
+import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputConnectionWrapper;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.InputBindResult;
+import com.android.internal.view.InputMethodClient;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Central system API to the overall input method framework (IMF) architecture,
+ * which arbitrates interaction between applications and the current input method.
+ *
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#ArchitectureOverview">Architecture Overview</a>
+ * <li><a href="#Applications">Applications</a>
+ * <li><a href="#InputMethods">Input Methods</a>
+ * <li><a href="#Security">Security</a>
+ * </ol>
+ *
+ * <a name="ArchitectureOverview"></a>
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the input method
+ * framework (IMF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>input method manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts. It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> An <strong>input method (IME)</strong> implements a particular
+ * interaction model allowing the user to generate text. The system binds
+ * to the current input method that is in use, causing it to be created and run,
+ * and tells it when to hide and show its UI. Only one IME is running at a time.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the input
+ * method manager for input focus and control over the state of the IME. Only
+ * one such client is ever active (working with the IME) at a time.
+ * </ul>
+ *
+ *
+ * <a name="Applications"></a>
+ * <h3>Applications</h3>
+ *
+ * <p>In most cases, applications that are using the standard
+ * {@link android.widget.TextView} or its subclasses will have little they need
+ * to do to work well with soft input methods. The main things you need to
+ * be aware of are:</p>
+ *
+ * <ul>
+ * <li> Properly set the {@link android.R.attr#inputType} in your editable
+ * text views, so that the input method will have enough context to help the
+ * user in entering text into them.
+ * <li> Deal well with losing screen space when the input method is
+ * displayed. Ideally an application should handle its window being resized
+ * smaller, but it can rely on the system performing panning of the window
+ * if needed. You should set the {@link android.R.attr#windowSoftInputMode}
+ * attribute on your activity or the corresponding values on windows you
+ * create to help the system determine whether to pan or resize (it will
+ * try to determine this automatically but may get it wrong).
+ * <li> You can also control the preferred soft input state (open, closed, etc)
+ * for your window using the same {@link android.R.attr#windowSoftInputMode}
+ * attribute.
+ * </ul>
+ *
+ * <p>More finer-grained control is available through the APIs here to directly
+ * interact with the IMF and its IME -- either showing or hiding the input
+ * area, letting the user pick an input method, etc.</p>
+ *
+ * <p>For the rare people amongst us writing their own text editors, you
+ * will need to implement {@link android.view.View#onCreateInputConnection}
+ * to return a new instance of your own {@link InputConnection} interface
+ * allowing the IME to interact with your editor.</p>
+ *
+ *
+ * <a name="InputMethods"></a>
+ * <h3>Input Methods</h3>
+ *
+ * <p>An input method (IME) is implemented
+ * as a {@link android.app.Service}, typically deriving from
+ * {@link android.inputmethodservice.InputMethodService}. It must provide
+ * the core {@link InputMethod} interface, though this is normally handled by
+ * {@link android.inputmethodservice.InputMethodService} and implementors will
+ * only need to deal with the higher-level API there.</p>
+ *
+ * See the {@link android.inputmethodservice.InputMethodService} class for
+ * more information on implementing IMEs.
+ *
+ *
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ *
+ * <p>There are a lot of security issues associated with input methods,
+ * since they essentially have freedom to completely drive the UI and monitor
+ * everything the user enters. The Android input method framework also allows
+ * arbitrary third party IMEs, so care must be taken to restrict their
+ * selection and interactions.</p>
+ *
+ * <p>Here are some key points about the security architecture behind the
+ * IMF:</p>
+ *
+ * <ul>
+ * <li> <p>Only the system is allowed to directly access an IME's
+ * {@link InputMethod} interface, via the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission. This is
+ * enforced in the system by not binding to an input method service that does
+ * not require this permission, so the system can guarantee no other untrusted
+ * clients are accessing the current input method outside of its control.</p>
+ *
+ * <li> <p>There may be many client processes of the IMF, but only one may
+ * be active at a time. The inactive clients can not interact with key
+ * parts of the IMF through the mechanisms described below.</p>
+ *
+ * <li> <p>Clients of an input method are only given access to its
+ * {@link InputMethodSession} interface. One instance of this interface is
+ * created for each client, and only calls from the session associated with
+ * the active client will be processed by the current IME. This is enforced
+ * by {@link android.inputmethodservice.AbstractInputMethodService} for normal
+ * IMEs, but must be explicitly handled by an IME that is customizing the
+ * raw {@link InputMethodSession} implementation.</p>
+ *
+ * <li> <p>Only the active client's {@link InputConnection} will accept
+ * operations. The IMF tells each client process whether it is active, and
+ * the framework enforces that in inactive processes calls on to the current
+ * InputConnection will be ignored. This ensures that the current IME can
+ * only deliver events and text edits to the UI that the user sees as
+ * being in focus.</p>
+ *
+ * <li> <p>An IME can never interact with an {@link InputConnection} while
+ * the screen is off. This is enforced by making all clients inactive while
+ * the screen is off, and prevents bad IMEs from driving the UI when the user
+ * can not be aware of its behavior.</p>
+ *
+ * <li> <p>A client application can ask that the system let the user pick a
+ * new IME, but can not programmatically switch to one itself. This avoids
+ * malicious applications from switching the user to their own IME, which
+ * remains running when the user navigates away to another application. An
+ * IME, on the other hand, <em>is</em> allowed to programmatically switch
+ * the system to another IME, since it already has full control of user
+ * input.</p>
+ *
+ * <li> <p>The user must explicitly enable a new IME in settings before
+ * they can switch to it, to confirm with the system that they know about it
+ * and want to make it available for use.</p>
+ * </ul>
+ */
+@SystemService(Context.INPUT_METHOD_SERVICE)
+public final class InputMethodManager {
+ static final boolean DEBUG = false;
+ static final String TAG = "InputMethodManager";
+
+ static final String PENDING_EVENT_COUNTER = "aq:imm";
+
+ static InputMethodManager sInstance;
+
+ /**
+ * @hide Flag for IInputMethodManager.windowGainedFocus: a view in
+ * the window has input focus.
+ */
+ public static final int CONTROL_WINDOW_VIEW_HAS_FOCUS = 1<<0;
+
+ /**
+ * @hide Flag for IInputMethodManager.windowGainedFocus: the focus
+ * is a text editor.
+ */
+ public static final int CONTROL_WINDOW_IS_TEXT_EDITOR = 1<<1;
+
+ /**
+ * @hide Flag for IInputMethodManager.windowGainedFocus: this is the first
+ * time the window has gotten focus.
+ */
+ public static final int CONTROL_WINDOW_FIRST = 1<<2;
+
+ /**
+ * @hide Flag for IInputMethodManager.startInput: this is the first
+ * time the window has gotten focus.
+ */
+ public static final int CONTROL_START_INITIAL = 1<<8;
+
+ /**
+ * Timeout in milliseconds for delivering a key to an IME.
+ */
+ static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;
+
+ /** @hide */
+ public static final int DISPATCH_IN_PROGRESS = -1;
+
+ /** @hide */
+ public static final int DISPATCH_NOT_HANDLED = 0;
+
+ /** @hide */
+ public static final int DISPATCH_HANDLED = 1;
+
+ /** @hide */
+ public static final int SHOW_IM_PICKER_MODE_AUTO = 0;
+ /** @hide */
+ public static final int SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES = 1;
+ /** @hide */
+ public static final int SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES = 2;
+
+ final IInputMethodManager mService;
+ final Looper mMainLooper;
+
+ // For scheduling work on the main thread. This also serves as our
+ // global lock.
+ final H mH;
+
+ // Our generic input connection if the current target does not have its own.
+ final IInputContext mIInputContext;
+
+ /**
+ * True if this input method client is active, initially false.
+ */
+ boolean mActive = false;
+
+ /**
+ * Set whenever this client becomes inactive, to know we need to reset
+ * state with the IME the next time we receive focus.
+ */
+ boolean mHasBeenInactive = true;
+
+ /**
+ * As reported by IME through InputConnection.
+ */
+ boolean mFullscreenMode;
+
+ // -----------------------------------------------------------
+
+ /**
+ * This is the root view of the overall window that currently has input
+ * method focus.
+ */
+ View mCurRootView;
+ /**
+ * This is the view that should currently be served by an input method,
+ * regardless of the state of setting that up.
+ */
+ View mServedView;
+ /**
+ * This is then next view that will be served by the input method, when
+ * we get around to updating things.
+ */
+ View mNextServedView;
+ /**
+ * This is set when we are in the process of connecting, to determine
+ * when we have actually finished.
+ */
+ boolean mServedConnecting;
+ /**
+ * This is non-null when we have connected the served view; it holds
+ * the attributes that were last retrieved from the served view and given
+ * to the input connection.
+ */
+ EditorInfo mCurrentTextBoxAttribute;
+ /**
+ * The InputConnection that was last retrieved from the served view.
+ */
+ ControlledInputConnectionWrapper mServedInputConnectionWrapper;
+ /**
+ * The completions that were last provided by the served view.
+ */
+ CompletionInfo[] mCompletions;
+
+ // Cursor position on the screen.
+ Rect mTmpCursorRect = new Rect();
+ Rect mCursorRect = new Rect();
+ int mCursorSelStart;
+ int mCursorSelEnd;
+ int mCursorCandStart;
+ int mCursorCandEnd;
+
+ /**
+ * Represents an invalid action notification sequence number. {@link InputMethodManagerService}
+ * always issues a positive integer for action notification sequence numbers. Thus -1 is
+ * guaranteed to be different from any valid sequence number.
+ */
+ private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1;
+ /**
+ * The next sequence number that is to be sent to {@link InputMethodManagerService} via
+ * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed.
+ */
+ private int mNextUserActionNotificationSequenceNumber =
+ NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
+
+ /**
+ * The last sequence number that is already sent to {@link InputMethodManagerService}.
+ */
+ private int mLastSentUserActionNotificationSequenceNumber =
+ NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
+
+ /**
+ * The instance that has previously been sent to the input method.
+ */
+ private CursorAnchorInfo mCursorAnchorInfo = null;
+
+ // -----------------------------------------------------------
+
+ /**
+ * Sequence number of this binding, as returned by the server.
+ */
+ int mBindSequence = -1;
+ /**
+ * ID of the method we are bound to.
+ */
+ String mCurId;
+ /**
+ * The actual instance of the method to make calls on it.
+ */
+ IInputMethodSession mCurMethod;
+ InputChannel mCurChannel;
+ ImeInputEventSender mCurSender;
+
+ private static final int REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE = 0x0;
+
+ /**
+ * The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ */
+ private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
+
+ final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
+ final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+
+ // -----------------------------------------------------------
+
+ static final int MSG_DUMP = 1;
+ static final int MSG_BIND = 2;
+ static final int MSG_UNBIND = 3;
+ static final int MSG_SET_ACTIVE = 4;
+ static final int MSG_SEND_INPUT_EVENT = 5;
+ static final int MSG_TIMEOUT_INPUT_EVENT = 6;
+ static final int MSG_FLUSH_INPUT_EVENT = 7;
+ static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9;
+ static final int MSG_REPORT_FULLSCREEN_MODE = 10;
+
+ class H extends Handler {
+ H(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DUMP: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ try {
+ doDump((FileDescriptor)args.arg1,
+ (PrintWriter)args.arg2, (String[])args.arg3);
+ } catch (RuntimeException e) {
+ ((PrintWriter)args.arg2).println("Exception: " + e);
+ }
+ synchronized (args.arg4) {
+ ((CountDownLatch)args.arg4).countDown();
+ }
+ args.recycle();
+ return;
+ }
+ case MSG_BIND: {
+ final InputBindResult res = (InputBindResult)msg.obj;
+ if (DEBUG) {
+ Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id);
+ }
+ synchronized (mH) {
+ if (mBindSequence < 0 || mBindSequence != res.sequence) {
+ Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+ + ", given seq=" + res.sequence);
+ if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
+ return;
+ }
+
+ mRequestUpdateCursorAnchorInfoMonitorMode =
+ REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
+
+ setInputChannelLocked(res.channel);
+ mCurMethod = res.method;
+ mCurId = res.id;
+ mBindSequence = res.sequence;
+ }
+ startInputInner(InputMethodClient.START_INPUT_REASON_BOUND_TO_IMMS,
+ null, 0, 0, 0);
+ return;
+ }
+ case MSG_UNBIND: {
+ final int sequence = msg.arg1;
+ @InputMethodClient.UnbindReason
+ final int reason = msg.arg2;
+ if (DEBUG) {
+ Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence +
+ " reason=" + InputMethodClient.getUnbindReason(reason));
+ }
+ final boolean startInput;
+ synchronized (mH) {
+ if (mBindSequence != sequence) {
+ return;
+ }
+ clearBindingLocked();
+ // If we were actively using the last input method, then
+ // we would like to re-connect to the next input method.
+ if (mServedView != null && mServedView.isFocused()) {
+ mServedConnecting = true;
+ }
+ startInput = mActive;
+ }
+ if (startInput) {
+ startInputInner(
+ InputMethodClient.START_INPUT_REASON_UNBOUND_FROM_IMMS, null, 0, 0,
+ 0);
+ }
+ return;
+ }
+ case MSG_SET_ACTIVE: {
+ final boolean active = msg.arg1 != 0;
+ final boolean fullscreen = msg.arg2 != 0;
+ if (DEBUG) {
+ Log.i(TAG, "handleMessage: MSG_SET_ACTIVE " + active + ", was " + mActive);
+ }
+ synchronized (mH) {
+ mActive = active;
+ mFullscreenMode = fullscreen;
+ if (!active) {
+ // Some other client has starting using the IME, so note
+ // that this happened and make sure our own editor's
+ // state is reset.
+ mHasBeenInactive = true;
+ try {
+ // Note that finishComposingText() is allowed to run
+ // even when we are not active.
+ mIInputContext.finishComposingText();
+ } catch (RemoteException e) {
+ }
+ }
+ // Check focus again in case that "onWindowFocus" is called before
+ // handling this message.
+ if (mServedView != null && mServedView.hasWindowFocus()) {
+ if (checkFocusNoStartInput(mHasBeenInactive)) {
+ final int reason = active ?
+ InputMethodClient.START_INPUT_REASON_ACTIVATED_BY_IMMS :
+ InputMethodClient.START_INPUT_REASON_DEACTIVATED_BY_IMMS;
+ startInputInner(reason, null, 0, 0, 0);
+ }
+ }
+ }
+ return;
+ }
+ case MSG_SEND_INPUT_EVENT: {
+ sendInputEventAndReportResultOnMainLooper((PendingEvent)msg.obj);
+ return;
+ }
+ case MSG_TIMEOUT_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, true);
+ return;
+ }
+ case MSG_FLUSH_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, false);
+ return;
+ }
+ case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
+ synchronized (mH) {
+ mNextUserActionNotificationSequenceNumber = msg.arg1;
+ }
+ return;
+ }
+ case MSG_REPORT_FULLSCREEN_MODE: {
+ final boolean fullscreen = msg.arg1 != 0;
+ InputConnection ic = null;
+ synchronized (mH) {
+ mFullscreenMode = fullscreen;
+ if (mServedInputConnectionWrapper != null) {
+ ic = mServedInputConnectionWrapper.getInputConnection();
+ }
+ }
+ if (ic != null) {
+ ic.reportFullscreenMode(fullscreen);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
+ private final InputMethodManager mParentInputMethodManager;
+
+ public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn,
+ final InputMethodManager inputMethodManager) {
+ super(mainLooper, conn);
+ mParentInputMethodManager = inputMethodManager;
+ }
+
+ @Override
+ public boolean isActive() {
+ return mParentInputMethodManager.mActive && !isFinished();
+ }
+
+ void deactivate() {
+ if (isFinished()) {
+ // This is a small performance optimization. Still only the 1st call of
+ // reportFinish() will take effect.
+ return;
+ }
+ closeConnection();
+ }
+
+ @Override
+ protected void onUserAction() {
+ mParentInputMethodManager.notifyUserAction();
+ }
+
+ @Override
+ public String toString() {
+ return "ControlledInputConnectionWrapper{"
+ + "connection=" + getInputConnection()
+ + " finished=" + isFinished()
+ + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive
+ + "}";
+ }
+ }
+
+ final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ // No need to check for dump permission, since we only give this
+ // interface to the system.
+ CountDownLatch latch = new CountDownLatch(1);
+ SomeArgs sargs = SomeArgs.obtain();
+ sargs.arg1 = fd;
+ sargs.arg2 = fout;
+ sargs.arg3 = args;
+ sargs.arg4 = latch;
+ mH.sendMessage(mH.obtainMessage(MSG_DUMP, sargs));
+ try {
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ fout.println("Timeout waiting for dump");
+ }
+ } catch (InterruptedException e) {
+ fout.println("Interrupted waiting for dump");
+ }
+ }
+
+ @Override
+ public void setUsingInputMethod(boolean state) {
+ }
+
+ @Override
+ public void onBindMethod(InputBindResult res) {
+ mH.obtainMessage(MSG_BIND, res).sendToTarget();
+ }
+
+ @Override
+ public void onUnbindMethod(int sequence, @InputMethodClient.UnbindReason int unbindReason) {
+ mH.obtainMessage(MSG_UNBIND, sequence, unbindReason).sendToTarget();
+ }
+
+ @Override
+ public void setActive(boolean active, boolean fullscreen) {
+ mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0).sendToTarget();
+ }
+
+ @Override
+ public void setUserActionNotificationSequenceNumber(int sequenceNumber) {
+ mH.obtainMessage(MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER, sequenceNumber, 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void reportFullscreenMode(boolean fullscreen) {
+ mH.obtainMessage(MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0)
+ .sendToTarget();
+ }
+
+ };
+
+ final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
+
+ InputMethodManager(Looper looper) throws ServiceNotFoundException {
+ this(IInputMethodManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper);
+ }
+
+ InputMethodManager(IInputMethodManager service, Looper looper) {
+ mService = service;
+ mMainLooper = looper;
+ mH = new H(looper);
+ mIInputContext = new ControlledInputConnectionWrapper(looper,
+ mDummyInputConnection, this);
+ }
+
+ /**
+ * Retrieve the global InputMethodManager instance, creating it if it
+ * doesn't already exist.
+ * @hide
+ */
+ public static InputMethodManager getInstance() {
+ synchronized (InputMethodManager.class) {
+ if (sInstance == null) {
+ try {
+ sInstance = new InputMethodManager(Looper.getMainLooper());
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Private optimization: retrieve the global InputMethodManager instance,
+ * if it exists.
+ * @hide
+ */
+ public static InputMethodManager peekInstance() {
+ return sInstance;
+ }
+
+ /** @hide */
+ public IInputMethodClient getClient() {
+ return mClient;
+ }
+
+ /** @hide */
+ public IInputContext getInputContext() {
+ return mIInputContext;
+ }
+
+ public List<InputMethodInfo> getInputMethodList() {
+ try {
+ return mService.getInputMethodList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public List<InputMethodInfo> getEnabledInputMethodList() {
+ try {
+ return mService.getEnabledInputMethodList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a list of enabled input method subtypes for the specified input method info.
+ * @param imi An input method info whose subtypes list will be returned.
+ * @param allowsImplicitlySelectedSubtypes A boolean flag to allow to return the implicitly
+ * selected subtypes. If an input method info doesn't have enabled subtypes, the framework
+ * will implicitly enable subtypes according to the current system language.
+ */
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
+ boolean allowsImplicitlySelectedSubtypes) {
+ try {
+ return mService.getEnabledInputMethodSubtypeList(
+ imi == null ? null : imi.getId(), allowsImplicitlySelectedSubtypes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public void showStatusIcon(IBinder imeToken, String packageName, int iconId) {
+ try {
+ mService.updateStatusIcon(imeToken, packageName, iconId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public void hideStatusIcon(IBinder imeToken) {
+ try {
+ mService.updateStatusIcon(imeToken, null, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void setImeWindowStatus(IBinder imeToken, IBinder startInputToken, int vis,
+ int backDisposition) {
+ try {
+ mService.setImeWindowStatus(imeToken, startInputToken, vis, backDisposition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+ try {
+ mService.registerSuggestionSpansForNotification(spans);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+ try {
+ mService.notifySuggestionPicked(span, originalString, index);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allows you to discover whether the attached input method is running
+ * in fullscreen mode. Return true if it is fullscreen, entirely covering
+ * your UI, else returns false.
+ */
+ public boolean isFullscreenMode() {
+ synchronized (mH) {
+ return mFullscreenMode;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void reportFullscreenMode(IBinder token, boolean fullscreen) {
+ try {
+ mService.reportFullscreenMode(token, fullscreen);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return true if the given view is the currently active view for the
+ * input method.
+ */
+ public boolean isActive(View view) {
+ checkFocus();
+ synchronized (mH) {
+ return (mServedView == view
+ || (mServedView != null
+ && mServedView.checkInputConnectionProxy(view)))
+ && mCurrentTextBoxAttribute != null;
+ }
+ }
+
+ /**
+ * Return true if any view is currently active in the input method.
+ */
+ public boolean isActive() {
+ checkFocus();
+ synchronized (mH) {
+ return mServedView != null && mCurrentTextBoxAttribute != null;
+ }
+ }
+
+ /**
+ * Return true if the currently served view is accepting full text edits.
+ * If false, it has no input connection, so can only handle raw key events.
+ */
+ public boolean isAcceptingText() {
+ checkFocus();
+ return mServedInputConnectionWrapper != null &&
+ mServedInputConnectionWrapper.getInputConnection() != null;
+ }
+
+ /**
+ * Reset all of the state associated with being bound to an input method.
+ */
+ void clearBindingLocked() {
+ if (DEBUG) Log.v(TAG, "Clearing binding!");
+ clearConnectionLocked();
+ setInputChannelLocked(null);
+ mBindSequence = -1;
+ mCurId = null;
+ mCurMethod = null;
+ }
+
+ void setInputChannelLocked(InputChannel channel) {
+ if (mCurChannel != channel) {
+ if (mCurSender != null) {
+ flushPendingEventsLocked();
+ mCurSender.dispose();
+ mCurSender = null;
+ }
+ if (mCurChannel != null) {
+ mCurChannel.dispose();
+ }
+ mCurChannel = channel;
+ }
+ }
+
+ /**
+ * Reset all of the state associated with a served view being connected
+ * to an input method
+ */
+ void clearConnectionLocked() {
+ mCurrentTextBoxAttribute = null;
+ if (mServedInputConnectionWrapper != null) {
+ mServedInputConnectionWrapper.deactivate();
+ mServedInputConnectionWrapper = null;
+ }
+ }
+
+ /**
+ * Disconnect any existing input connection, clearing the served view.
+ */
+ void finishInputLocked() {
+ mNextServedView = null;
+ if (mServedView != null) {
+ if (DEBUG) Log.v(TAG, "FINISH INPUT: mServedView=" + dumpViewInfo(mServedView));
+ if (mCurrentTextBoxAttribute != null) {
+ try {
+ mService.finishInput(mClient);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ mServedView = null;
+ mCompletions = null;
+ mServedConnecting = false;
+ clearConnectionLocked();
+ }
+ }
+
+ public void displayCompletions(View view, CompletionInfo[] completions) {
+ checkFocus();
+ synchronized (mH) {
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
+ return;
+ }
+
+ mCompletions = completions;
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.displayCompletions(mCompletions);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ public void updateExtractedText(View view, int token, ExtractedText text) {
+ checkFocus();
+ synchronized (mH) {
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
+ return;
+ }
+
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.updateExtractedText(token, text);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Flag for {@link #showSoftInput} to indicate that this is an implicit
+ * request to show the input window, not as the result of a direct request
+ * by the user. The window may not be shown in this case.
+ */
+ public static final int SHOW_IMPLICIT = 0x0001;
+
+ /**
+ * Flag for {@link #showSoftInput} to indicate that the user has forced
+ * the input method open (such as by long-pressing menu) so it should
+ * not be closed until they explicitly do so.
+ */
+ public static final int SHOW_FORCED = 0x0002;
+
+ /**
+ * Synonym for {@link #showSoftInput(View, int, ResultReceiver)} without
+ * a result receiver: explicitly request that the current input method's
+ * soft input area be shown to the user, if needed.
+ *
+ * @param view The currently focused view, which would like to receive
+ * soft keyboard input.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #SHOW_IMPLICIT} bit set.
+ */
+ public boolean showSoftInput(View view, int flags) {
+ return showSoftInput(view, flags, null);
+ }
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window was unchanged and remains shown.
+ */
+ public static final int RESULT_UNCHANGED_SHOWN = 0;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window was unchanged and remains hidden.
+ */
+ public static final int RESULT_UNCHANGED_HIDDEN = 1;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window changed from hidden to shown.
+ */
+ public static final int RESULT_SHOWN = 2;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window changed from shown to hidden.
+ */
+ public static final int RESULT_HIDDEN = 3;
+
+ /**
+ * Explicitly request that the current input method's soft input area be
+ * shown to the user, if needed. Call this if the user interacts with
+ * your view in such a way that they have expressed they would like to
+ * start performing input into it.
+ *
+ * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
+ * this method can be a long-lived object, because it may not be
+ * garbage-collected until all the corresponding {@link ResultReceiver}
+ * objects transferred to different processes get garbage-collected.
+ * Follow the general patterns to avoid memory leaks in Android.
+ * Consider to use {@link java.lang.ref.WeakReference} so that application
+ * logic objects such as {@link android.app.Activity} and {@link Context}
+ * can be garbage collected regardless of the lifetime of
+ * {@link ResultReceiver}.
+ *
+ * @param view The currently focused view, which would like to receive
+ * soft keyboard input.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #SHOW_IMPLICIT} bit set.
+ * @param resultReceiver If non-null, this will be called by the IME when
+ * it has processed your request to tell you what it has done. The result
+ * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+ * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+ * {@link #RESULT_HIDDEN}.
+ */
+ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
+ checkFocus();
+ synchronized (mH) {
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
+ return false;
+ }
+
+ try {
+ return mService.showSoftInput(mClient, flags, resultReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * This method is still kept for a while until android.support.v7.widget.SearchView ver. 26.0
+ * is publicly released because previous implementations of that class had relied on this method
+ * via reflection.
+ *
+ * @deprecated This is a hidden API. You should never use this.
+ * @hide
+ */
+ @Deprecated
+ public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
+ try {
+ Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be removed "
+ + "soon. If you are using android.support.v7.widget.SearchView, please update "
+ + "to version 26.0 or newer version.");
+ mService.showSoftInput(mClient, flags, resultReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
+ * input window should only be hidden if it was not explicitly shown
+ * by the user.
+ */
+ public static final int HIDE_IMPLICIT_ONLY = 0x0001;
+
+ /**
+ * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
+ * input window should normally be hidden, unless it was originally
+ * shown with {@link #SHOW_FORCED}.
+ */
+ public static final int HIDE_NOT_ALWAYS = 0x0002;
+
+ /**
+ * Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}
+ * without a result: request to hide the soft input window from the
+ * context of the window that is currently accepting input.
+ *
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ */
+ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+ return hideSoftInputFromWindow(windowToken, flags, null);
+ }
+
+ /**
+ * Request to hide the soft input window from the context of the window
+ * that is currently accepting input. This should be called as a result
+ * of the user doing some actually than fairly explicitly requests to
+ * have the input window hidden.
+ *
+ * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
+ * this method can be a long-lived object, because it may not be
+ * garbage-collected until all the corresponding {@link ResultReceiver}
+ * objects transferred to different processes get garbage-collected.
+ * Follow the general patterns to avoid memory leaks in Android.
+ * Consider to use {@link java.lang.ref.WeakReference} so that application
+ * logic objects such as {@link android.app.Activity} and {@link Context}
+ * can be garbage collected regardless of the lifetime of
+ * {@link ResultReceiver}.
+ *
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ * @param resultReceiver If non-null, this will be called by the IME when
+ * it has processed your request to tell you what it has done. The result
+ * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+ * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+ * {@link #RESULT_HIDDEN}.
+ */
+ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+ ResultReceiver resultReceiver) {
+ checkFocus();
+ synchronized (mH) {
+ if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ return false;
+ }
+
+ try {
+ return mService.hideSoftInput(mClient, flags, resultReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+
+ /**
+ * This method toggles the input method window display.
+ * If the input window is already displayed, it gets hidden.
+ * If not the input window will be displayed.
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link #SHOW_IMPLICIT},
+ * {@link #SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
+ **/
+ public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+ synchronized (mH) {
+ if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ return;
+ }
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.toggleSoftInput(showFlags, hideFlags);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ /*
+ * This method toggles the input method window display.
+ * If the input window is already displayed, it gets hidden.
+ * If not the input window will be displayed.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link #SHOW_IMPLICIT},
+ * {@link #SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
+ * @hide
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags) {
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.toggleSoftInput(showFlags, hideFlags);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * If the input method is currently connected to the given view,
+ * restart it with its new contents. You should call this when the text
+ * within your view changes outside of the normal input method or key
+ * input flow, such as when an application calls TextView.setText().
+ *
+ * @param view The view whose text has changed.
+ */
+ public void restartInput(View view) {
+ checkFocus();
+ synchronized (mH) {
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
+ return;
+ }
+
+ mServedConnecting = true;
+ }
+
+ startInputInner(InputMethodClient.START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API, null, 0,
+ 0, 0);
+ }
+
+ boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
+ IBinder windowGainingFocus, int controlFlags, int softInputMode,
+ int windowFlags) {
+ final View view;
+ synchronized (mH) {
+ view = mServedView;
+
+ // Make sure we have a window token for the served view.
+ if (DEBUG) {
+ Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) +
+ " reason=" + InputMethodClient.getStartInputReason(startInputReason));
+ }
+ if (view == null) {
+ if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
+ return false;
+ }
+ }
+
+ // Now we need to get an input connection from the served view.
+ // This is complicated in a couple ways: we can't be holding our lock
+ // when calling out to the view, and we need to make sure we call into
+ // the view on the same thread that is driving its view hierarchy.
+ Handler vh = view.getHandler();
+ if (vh == null) {
+ // If the view doesn't have a handler, something has changed out
+ // from under us, so just close the current input.
+ // If we don't close the current input, the current input method can remain on the
+ // screen without a connection.
+ if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");
+ closeCurrentInput();
+ return false;
+ }
+ if (vh.getLooper() != Looper.myLooper()) {
+ // The view is running on a different thread than our own, so
+ // we need to reschedule our work for over there.
+ if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
+ vh.post(new Runnable() {
+ @Override
+ public void run() {
+ startInputInner(startInputReason, null, 0, 0, 0);
+ }
+ });
+ return false;
+ }
+
+ // Okay we are now ready to call into the served view and have it
+ // do its stuff.
+ // Life is good: let's hook everything up!
+ EditorInfo tba = new EditorInfo();
+ // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
+ // system can verify the consistency between the uid of this process and package name passed
+ // from here. See comment of Context#getOpPackageName() for details.
+ tba.packageName = view.getContext().getOpPackageName();
+ tba.fieldId = view.getId();
+ InputConnection ic = view.onCreateInputConnection(tba);
+ if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
+
+ synchronized (mH) {
+ // Now that we are locked again, validate that our state hasn't
+ // changed.
+ if (mServedView != view || !mServedConnecting) {
+ // Something else happened, so abort.
+ if (DEBUG) Log.v(TAG,
+ "Starting input: finished by someone else. view=" + dumpViewInfo(view)
+ + " mServedView=" + dumpViewInfo(mServedView)
+ + " mServedConnecting=" + mServedConnecting);
+ return false;
+ }
+
+ // If we already have a text box, then this view is already
+ // connected so we want to restart it.
+ if (mCurrentTextBoxAttribute == null) {
+ controlFlags |= CONTROL_START_INITIAL;
+ }
+
+ // Hook 'em up and let 'er rip.
+ mCurrentTextBoxAttribute = tba;
+ mServedConnecting = false;
+ if (mServedInputConnectionWrapper != null) {
+ mServedInputConnectionWrapper.deactivate();
+ mServedInputConnectionWrapper = null;
+ }
+ ControlledInputConnectionWrapper servedContext;
+ final int missingMethodFlags;
+ if (ic != null) {
+ mCursorSelStart = tba.initialSelStart;
+ mCursorSelEnd = tba.initialSelEnd;
+ mCursorCandStart = -1;
+ mCursorCandEnd = -1;
+ mCursorRect.setEmpty();
+ mCursorAnchorInfo = null;
+ final Handler icHandler;
+ missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
+ if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
+ != 0) {
+ // InputConnection#getHandler() is not implemented.
+ icHandler = null;
+ } else {
+ icHandler = ic.getHandler();
+ }
+ servedContext = new ControlledInputConnectionWrapper(
+ icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
+ } else {
+ servedContext = null;
+ missingMethodFlags = 0;
+ }
+ mServedInputConnectionWrapper = servedContext;
+
+ try {
+ if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ + ic + " tba=" + tba + " controlFlags=#"
+ + Integer.toHexString(controlFlags));
+ final InputBindResult res = mService.startInputOrWindowGainedFocus(
+ startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
+ windowFlags, tba, servedContext, missingMethodFlags);
+ if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+ if (res != null) {
+ if (res.id != null) {
+ setInputChannelLocked(res.channel);
+ mBindSequence = res.sequence;
+ mCurMethod = res.method;
+ mCurId = res.id;
+ mNextUserActionNotificationSequenceNumber =
+ res.userActionNotificationSequenceNumber;
+ } else {
+ if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
+ if (mCurMethod == null) {
+ // This means there is no input method available.
+ if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
+ return true;
+ }
+ }
+ } else {
+ if (startInputReason
+ == InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN) {
+ // We are here probably because of an obsolete window-focus-in message sent
+ // to windowGainingFocus. Since IMMS determines whether a Window can have
+ // IME focus or not by using the latest window focus state maintained in the
+ // WMS, this kind of race condition cannot be avoided. One obvious example
+ // would be that we have already received a window-focus-out message but the
+ // UI thread is still handling previous window-focus-in message here.
+ // TODO: InputBindResult should have the error code.
+ if (DEBUG) Log.w(TAG, "startInputOrWindowGainedFocus failed. "
+ + "Window focus may have already been lost. "
+ + "win=" + windowGainingFocus + " view=" + dumpViewInfo(view));
+ if (!mActive) {
+ // mHasBeenInactive is a latch switch to forcefully refresh IME focus
+ // state when an inactive (mActive == false) client is gaining window
+ // focus. In case we have unnecessary disable the latch due to this
+ // spurious wakeup, we re-enable the latch here.
+ // TODO: Come up with more robust solution.
+ mHasBeenInactive = true;
+ }
+ }
+ }
+ if (mCurMethod != null && mCompletions != null) {
+ try {
+ mCurMethod.displayCompletions(mCompletions);
+ } catch (RemoteException e) {
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * When the focused window is dismissed, this method is called to finish the
+ * input method started before.
+ * @hide
+ */
+ public void windowDismissed(IBinder appWindowToken) {
+ checkFocus();
+ synchronized (mH) {
+ if (mServedView != null &&
+ mServedView.getWindowToken() == appWindowToken) {
+ finishInputLocked();
+ }
+ }
+ }
+
+ /**
+ * Call this when a view receives focus.
+ * @hide
+ */
+ public void focusIn(View view) {
+ synchronized (mH) {
+ focusInLocked(view);
+ }
+ }
+
+ void focusInLocked(View view) {
+ if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view));
+
+ if (view != null && view.isTemporarilyDetached()) {
+ // This is a request from a view that is temporarily detached from a window.
+ if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring");
+ return;
+ }
+
+ if (mCurRootView != view.getRootView()) {
+ // This is a request from a window that isn't in the window with
+ // IME focus, so ignore it.
+ if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
+ return;
+ }
+
+ mNextServedView = view;
+ scheduleCheckFocusLocked(view);
+ }
+
+ /**
+ * Call this when a view loses focus.
+ * @hide
+ */
+ public void focusOut(View view) {
+ synchronized (mH) {
+ if (DEBUG) Log.v(TAG, "focusOut: view=" + dumpViewInfo(view)
+ + " mServedView=" + dumpViewInfo(mServedView));
+ if (mServedView != view) {
+ // The following code would auto-hide the IME if we end up
+ // with no more views with focus. This can happen, however,
+ // whenever we go into touch mode, so it ends up hiding
+ // at times when we don't really want it to. For now it
+ // seems better to just turn it all off.
+ // TODO: Check view.isTemporarilyDetached() when re-enable the following code.
+ if (false && view.hasWindowFocus()) {
+ mNextServedView = null;
+ scheduleCheckFocusLocked(view);
+ }
+ }
+ }
+ }
+
+ /**
+ * Call this when a view is being detached from a {@link android.view.Window}.
+ * @hide
+ */
+ public void onViewDetachedFromWindow(View view) {
+ synchronized (mH) {
+ if (DEBUG) Log.v(TAG, "onViewDetachedFromWindow: view=" + dumpViewInfo(view)
+ + " mServedView=" + dumpViewInfo(mServedView));
+ if (mServedView == view) {
+ mNextServedView = null;
+ scheduleCheckFocusLocked(view);
+ }
+ }
+ }
+
+ static void scheduleCheckFocusLocked(View view) {
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchCheckFocus();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void checkFocus() {
+ if (checkFocusNoStartInput(false)) {
+ startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
+ }
+ }
+
+ private boolean checkFocusNoStartInput(boolean forceNewFocus) {
+ // This is called a lot, so short-circuit before locking.
+ if (mServedView == mNextServedView && !forceNewFocus) {
+ return false;
+ }
+
+ final ControlledInputConnectionWrapper ic;
+ synchronized (mH) {
+ if (mServedView == mNextServedView && !forceNewFocus) {
+ return false;
+ }
+ if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ + " next=" + mNextServedView
+ + " forceNewFocus=" + forceNewFocus
+ + " package="
+ + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
+
+ if (mNextServedView == null) {
+ finishInputLocked();
+ // In this case, we used to have a focused view on the window,
+ // but no longer do. We should make sure the input method is
+ // no longer shown, since it serves no purpose.
+ closeCurrentInput();
+ return false;
+ }
+
+ ic = mServedInputConnectionWrapper;
+
+ mServedView = mNextServedView;
+ mCurrentTextBoxAttribute = null;
+ mCompletions = null;
+ mServedConnecting = true;
+ }
+
+ if (ic != null) {
+ ic.finishComposingText();
+ }
+
+ return true;
+ }
+
+ void closeCurrentInput() {
+ try {
+ mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by ViewAncestor when its window gets input focus.
+ * @hide
+ */
+ public void onPostWindowFocus(View rootView, View focusedView,
+ @SoftInputModeFlags int softInputMode, boolean first, int windowFlags) {
+ boolean forceNewFocus = false;
+ synchronized (mH) {
+ if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
+ + " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode)
+ + " first=" + first + " flags=#"
+ + Integer.toHexString(windowFlags));
+ if (mHasBeenInactive) {
+ if (DEBUG) Log.v(TAG, "Has been inactive! Starting fresh");
+ mHasBeenInactive = false;
+ forceNewFocus = true;
+ }
+ focusInLocked(focusedView != null ? focusedView : rootView);
+ }
+
+ int controlFlags = 0;
+ if (focusedView != null) {
+ controlFlags |= CONTROL_WINDOW_VIEW_HAS_FOCUS;
+ if (focusedView.onCheckIsTextEditor()) {
+ controlFlags |= CONTROL_WINDOW_IS_TEXT_EDITOR;
+ }
+ }
+ if (first) {
+ controlFlags |= CONTROL_WINDOW_FIRST;
+ }
+
+ if (checkFocusNoStartInput(forceNewFocus)) {
+ // We need to restart input on the current focus view. This
+ // should be done in conjunction with telling the system service
+ // about the window gaining focus, to help make the transition
+ // smooth.
+ if (startInputInner(InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN,
+ rootView.getWindowToken(), controlFlags, softInputMode, windowFlags)) {
+ return;
+ }
+ }
+
+ // For some reason we didn't do a startInput + windowFocusGain, so
+ // we'll just do a window focus gain and call it a day.
+ synchronized (mH) {
+ try {
+ if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
+ mService.startInputOrWindowGainedFocus(
+ InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
+ rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null,
+ null, 0 /* missingMethodFlags */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /** @hide */
+ public void onPreWindowFocus(View rootView, boolean hasWindowFocus) {
+ synchronized (mH) {
+ if (rootView == null) {
+ mCurRootView = null;
+ } if (hasWindowFocus) {
+ mCurRootView = rootView;
+ } else if (rootView == mCurRootView) {
+ // If the mCurRootView is losing window focus, release the strong reference to it
+ // so as not to prevent it from being garbage-collected.
+ mCurRootView = null;
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "Ignoring onPreWindowFocus()."
+ + " mCurRootView=" + mCurRootView + " rootView=" + rootView);
+ }
+ }
+ }
+ }
+
+ /**
+ * Report the current selection range.
+ *
+ * <p><strong>Editor authors</strong>, you need to call this method whenever
+ * the cursor moves in your editor. Remember that in addition to doing this, your
+ * editor needs to always supply current cursor values in
+ * {@link EditorInfo#initialSelStart} and {@link EditorInfo#initialSelEnd} every
+ * time {@link android.view.View#onCreateInputConnection(EditorInfo)} is
+ * called, which happens whenever the keyboard shows up or the focus changes
+ * to a text field, among other cases.</p>
+ */
+ public void updateSelection(View view, int selStart, int selEnd,
+ int candidatesStart, int candidatesEnd) {
+ checkFocus();
+ synchronized (mH) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+
+ if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
+ || mCursorCandStart != candidatesStart
+ || mCursorCandEnd != candidatesEnd) {
+ if (DEBUG) Log.d(TAG, "updateSelection");
+
+ try {
+ if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod);
+ final int oldSelStart = mCursorSelStart;
+ final int oldSelEnd = mCursorSelEnd;
+ // Update internal values before sending updateSelection to the IME, because
+ // if it changes the text within its onUpdateSelection handler in a way that
+ // does not move the cursor we don't want to call it again with the same values.
+ mCursorSelStart = selStart;
+ mCursorSelEnd = selEnd;
+ mCursorCandStart = candidatesStart;
+ mCursorCandEnd = candidatesEnd;
+ mCurMethod.updateSelection(oldSelStart, oldSelEnd,
+ selStart, selEnd, candidatesStart, candidatesEnd);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Notify the event when the user tapped or clicked the text view.
+ */
+ public void viewClicked(View view) {
+ final boolean focusChanged = mServedView != mNextServedView;
+ checkFocus();
+ synchronized (mH) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+ try {
+ if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
+ mCurMethod.viewClicked(focusChanged);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
+ * Return true if the current input method wants to watch the location
+ * of the input editor's cursor in its window.
+ *
+ * @deprecated Use {@link InputConnection#requestCursorUpdates(int)} instead.
+ */
+ @Deprecated
+ public boolean isWatchingCursor(View view) {
+ return false;
+ }
+
+ /**
+ * Return true if the current input method wants to be notified when cursor/anchor location
+ * is changed.
+ *
+ * @hide
+ */
+ public boolean isCursorAnchorInfoEnabled() {
+ synchronized (mH) {
+ final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
+ InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+ final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode &
+ InputConnection.CURSOR_UPDATE_MONITOR) != 0;
+ return isImmediate || isMonitoring;
+ }
+ }
+
+ /**
+ * Set the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ *
+ * @hide
+ */
+ public void setUpdateCursorAnchorInfoMode(int flags) {
+ synchronized (mH) {
+ mRequestUpdateCursorAnchorInfoMonitorMode = flags;
+ }
+ }
+
+ /**
+ * Report the current cursor location in its window.
+ *
+ * @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead.
+ */
+ @Deprecated
+ public void updateCursor(View view, int left, int top, int right, int bottom) {
+ checkFocus();
+ synchronized (mH) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+
+ mTmpCursorRect.set(left, top, right, bottom);
+ if (!mCursorRect.equals(mTmpCursorRect)) {
+ if (DEBUG) Log.d(TAG, "updateCursor");
+
+ try {
+ if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
+ mCurMethod.updateCursor(mTmpCursorRect);
+ mCursorRect.set(mTmpCursorRect);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Report positional change of the text insertion point and/or characters in the composition
+ * string.
+ */
+ public void updateCursorAnchorInfo(View view, final CursorAnchorInfo cursorAnchorInfo) {
+ if (view == null || cursorAnchorInfo == null) {
+ return;
+ }
+ checkFocus();
+ synchronized (mH) {
+ if ((mServedView != view &&
+ (mServedView == null || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+ // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
+ // not been changed from the previous call.
+ final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
+ InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+ if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
+ // TODO: Consider always emitting this message once we have addressed redundant
+ // calls of this method from android.widget.Editor.
+ if (DEBUG) {
+ Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info="
+ + cursorAnchorInfo);
+ }
+ return;
+ }
+ if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo);
+ try {
+ mCurMethod.updateCursorAnchorInfo(cursorAnchorInfo);
+ mCursorAnchorInfo = cursorAnchorInfo;
+ // Clear immediate bit (if any).
+ mRequestUpdateCursorAnchorInfoMonitorMode &=
+ ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
+ * Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
+ * InputMethodSession.appPrivateCommand()} on the current Input Method.
+ * @param view Optional View that is sending the command, or null if
+ * you want to send the command regardless of the view that is attached
+ * to the input method.
+ * @param action Name of the command to be performed. This <em>must</em>
+ * be a scoped name, i.e. prefixed with a package name you own, so that
+ * different developers will not create conflicting commands.
+ * @param data Any data to include with the command.
+ */
+ public void sendAppPrivateCommand(View view, String action, Bundle data) {
+ checkFocus();
+ synchronized (mH) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+ try {
+ if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
+ mCurMethod.appPrivateCommand(action, data);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
+ * Force switch to a new input method component. This can only be called
+ * from an application or a service which has a token of the currently active input method.
+ * @param token Supplies the identifying token given to an input method
+ * when it was started, which allows it to perform this operation on
+ * itself.
+ * @param id The unique identifier for the new input method to be switched to.
+ */
+ public void setInputMethod(IBinder token, String id) {
+ try {
+ mService.setInputMethod(token, id);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force switch to a new input method and subtype. This can only be called
+ * from an application or a service which has a token of the currently active input method.
+ * @param token Supplies the identifying token given to an input method
+ * when it was started, which allows it to perform this operation on
+ * itself.
+ * @param id The unique identifier for the new input method to be switched to.
+ * @param subtype The new subtype of the new input method to be switched to.
+ */
+ public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+ try {
+ mService.setInputMethodAndSubtype(token, id, subtype);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Close/hide the input method's soft input area, so the user no longer
+ * sees it or can interact with it. This can only be called
+ * from the currently active input method, as validated by the given token.
+ *
+ * @param token Supplies the identifying token given to an input method
+ * when it was started, which allows it to perform this operation on
+ * itself.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
+ */
+ public void hideSoftInputFromInputMethod(IBinder token, int flags) {
+ try {
+ mService.hideMySoftInput(token, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Show the input method's soft input area, so the user
+ * sees the input method window and can interact with it.
+ * This can only be called from the currently active input method,
+ * as validated by the given token.
+ *
+ * @param token Supplies the identifying token given to an input method
+ * when it was started, which allows it to perform this operation on
+ * itself.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #SHOW_IMPLICIT} or
+ * {@link #SHOW_FORCED} bit set.
+ */
+ public void showSoftInputFromInputMethod(IBinder token, int flags) {
+ try {
+ mService.showMySoftInput(token, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Dispatches an input event to the IME.
+ *
+ * Returns {@link #DISPATCH_HANDLED} if the event was handled.
+ * Returns {@link #DISPATCH_NOT_HANDLED} if the event was not handled.
+ * Returns {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the
+ * callback will be invoked later.
+ *
+ * @hide
+ */
+ public int dispatchInputEvent(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ synchronized (mH) {
+ if (mCurMethod != null) {
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent)event;
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ && keyEvent.getKeyCode() == KeyEvent.KEYCODE_SYM
+ && keyEvent.getRepeatCount() == 0) {
+ showInputMethodPickerLocked();
+ return DISPATCH_HANDLED;
+ }
+ }
+
+ if (DEBUG) Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurMethod);
+
+ PendingEvent p = obtainPendingEventLocked(
+ event, token, mCurId, callback, handler);
+ if (mMainLooper.isCurrentThread()) {
+ // Already running on the IMM thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
+ }
+
+ // Post the event to the IMM thread.
+ Message msg = mH.obtainMessage(MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mH.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
+ }
+ }
+ return DISPATCH_NOT_HANDLED;
+ }
+
+ /**
+ * Provides the default implementation of {@link InputConnection#sendKeyEvent(KeyEvent)}, which
+ * is expected to dispatch an keyboard event sent from the IME to an appropriate event target
+ * depending on the given {@link View} and the current focus state.
+ *
+ * <p>CAUTION: This method is provided only for the situation where
+ * {@link InputConnection#sendKeyEvent(KeyEvent)} needs to be implemented without relying on
+ * {@link BaseInputConnection}. Do not use this API for anything else.</p>
+ *
+ * @param targetView the default target view. If {@code null} is specified, then this method
+ * tries to find a good event target based on the current focus state.
+ * @param event the key event to be dispatched.
+ */
+ public void dispatchKeyEventFromInputMethod(@Nullable View targetView,
+ @NonNull KeyEvent event) {
+ synchronized (mH) {
+ ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+ if (viewRootImpl == null) {
+ if (mServedView != null) {
+ viewRootImpl = mServedView.getViewRootImpl();
+ }
+ }
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchKeyFromIme(event);
+ }
+ }
+ }
+
+ // Must be called on the main looper
+ void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+ final boolean handled;
+ synchronized (mH) {
+ int result = sendInputEventOnMainLooperLocked(p);
+ if (result == DISPATCH_IN_PROGRESS) {
+ return;
+ }
+
+ handled = (result == DISPATCH_HANDLED);
+ }
+
+ invokeFinishedInputEventCallback(p, handled);
+ }
+
+ // Must be called on the main looper
+ int sendInputEventOnMainLooperLocked(PendingEvent p) {
+ if (mCurChannel != null) {
+ if (mCurSender == null) {
+ mCurSender = new ImeInputEventSender(mCurChannel, mH.getLooper());
+ }
+
+ final InputEvent event = p.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (mCurSender.sendInputEvent(seq, event)) {
+ mPendingEvents.put(seq, p);
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER,
+ mPendingEvents.size());
+
+ Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p);
+ msg.setAsynchronous(true);
+ mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
+ return DISPATCH_IN_PROGRESS;
+ }
+
+ Log.w(TAG, "Unable to send input event to IME: "
+ + mCurId + " dropping: " + event);
+ }
+ return DISPATCH_NOT_HANDLED;
+ }
+
+ void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+ final PendingEvent p;
+ synchronized (mH) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index < 0) {
+ return; // spurious, event already finished or timed out
+ }
+
+ p = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, mPendingEvents.size());
+
+ if (timeout) {
+ Log.w(TAG, "Timeout waiting for IME to handle input event after "
+ + INPUT_METHOD_NOT_RESPONDING_TIMEOUT + " ms: " + p.mInputMethodId);
+ } else {
+ mH.removeMessages(MSG_TIMEOUT_INPUT_EVENT, p);
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, handled);
+ }
+
+ // Assumes the event has already been removed from the queue.
+ void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+ p.mHandled = handled;
+ if (p.mHandler.getLooper().isCurrentThread()) {
+ // Already running on the callback handler thread so we can send the
+ // callback immediately.
+ p.run();
+ } else {
+ // Post the event to the callback handler thread.
+ // In this case, the callback will be responsible for recycling the event.
+ Message msg = Message.obtain(p.mHandler, p);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private void flushPendingEventsLocked() {
+ mH.removeMessages(MSG_FLUSH_INPUT_EVENT);
+
+ final int count = mPendingEvents.size();
+ for (int i = 0; i < count; i++) {
+ int seq = mPendingEvents.keyAt(i);
+ Message msg = mH.obtainMessage(MSG_FLUSH_INPUT_EVENT, seq, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ String inputMethodId, FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mToken = token;
+ p.mInputMethodId = inputMethodId;
+ p.mCallback = callback;
+ p.mHandler = handler;
+ return p;
+ }
+
+ private void recyclePendingEventLocked(PendingEvent p) {
+ p.recycle();
+ mPendingEventPool.release(p);
+ }
+
+ public void showInputMethodPicker() {
+ synchronized (mH) {
+ showInputMethodPickerLocked();
+ }
+ }
+
+ /**
+ * Shows the input method chooser dialog.
+ *
+ * @param showAuxiliarySubtypes Set true to show auxiliary input methods.
+ * @hide
+ */
+ public void showInputMethodPicker(boolean showAuxiliarySubtypes) {
+ synchronized (mH) {
+ try {
+ final int mode = showAuxiliarySubtypes ?
+ SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
+ SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
+ mService.showInputMethodPickerFromClient(mClient, mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private void showInputMethodPickerLocked() {
+ try {
+ mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Show the settings for enabling subtypes of the specified input method.
+ * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
+ * subtypes of all input methods will be shown.
+ */
+ public void showInputMethodAndSubtypeEnabler(String imiId) {
+ synchronized (mH) {
+ try {
+ mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the current input method subtype. This subtype is one of the subtypes in
+ * the current input method. This method returns null when the current input method doesn't
+ * have any input method subtype.
+ */
+ public InputMethodSubtype getCurrentInputMethodSubtype() {
+ try {
+ return mService.getCurrentInputMethodSubtype();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Switch to a new input method subtype of the current input method.
+ * @param subtype A new input method subtype to switch.
+ * @return true if the current subtype was successfully switched. When the specified subtype is
+ * null, this method returns false.
+ */
+ @RequiresPermission(WRITE_SECURE_SETTINGS)
+ public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
+ synchronized (mH) {
+ try {
+ return mService.setCurrentInputMethodSubtype(subtype);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Notify that a user took some action with this input method.
+ * @hide
+ */
+ public void notifyUserAction() {
+ synchronized (mH) {
+ if (mLastSentUserActionNotificationSequenceNumber ==
+ mNextUserActionNotificationSequenceNumber) {
+ if (DEBUG) {
+ Log.w(TAG, "Ignoring notifyUserAction as it has already been sent."
+ + " mLastSentUserActionNotificationSequenceNumber: "
+ + mLastSentUserActionNotificationSequenceNumber
+ + " mNextUserActionNotificationSequenceNumber: "
+ + mNextUserActionNotificationSequenceNumber);
+ }
+ return;
+ }
+ try {
+ if (DEBUG) {
+ Log.w(TAG, "notifyUserAction: "
+ + " mLastSentUserActionNotificationSequenceNumber: "
+ + mLastSentUserActionNotificationSequenceNumber
+ + " mNextUserActionNotificationSequenceNumber: "
+ + mNextUserActionNotificationSequenceNumber);
+ }
+ mService.notifyUserAction(mNextUserActionNotificationSequenceNumber);
+ mLastSentUserActionNotificationSequenceNumber =
+ mNextUserActionNotificationSequenceNumber;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns a map of all shortcut input method info and their subtypes.
+ */
+ public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
+ synchronized (mH) {
+ HashMap<InputMethodInfo, List<InputMethodSubtype>> ret = new HashMap<>();
+ try {
+ // TODO: We should change the return type from List<Object> to List<Parcelable>
+ List<Object> info = mService.getShortcutInputMethodsAndSubtypes();
+ // "info" has imi1, subtype1, subtype2, imi2, subtype2, imi3, subtype3..in the list
+ ArrayList<InputMethodSubtype> subtypes = null;
+ if (info != null && !info.isEmpty()) {
+ final int N = info.size();
+ for (int i = 0; i < N; ++i) {
+ Object o = info.get(i);
+ if (o instanceof InputMethodInfo) {
+ if (ret.containsKey(o)) {
+ Log.e(TAG, "IMI list already contains the same InputMethod.");
+ break;
+ }
+ subtypes = new ArrayList<>();
+ ret.put((InputMethodInfo)o, subtypes);
+ } else if (subtypes != null && o instanceof InputMethodSubtype) {
+ subtypes.add((InputMethodSubtype)o);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return ret;
+ }
+ }
+
+ /**
+ * @return The current height of the input method window.
+ * @hide
+ */
+ public int getInputMethodWindowVisibleHeight() {
+ synchronized (mH) {
+ try {
+ return mService.getInputMethodWindowVisibleHeight();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Tells the system that the IME decided to not show a window and the system no longer needs to
+ * use the previous IME's inset.
+ *
+ * <p>Caveat: {@link android.inputmethodservice.InputMethodService#clearInsetOfPreviousIme()}
+ * is the only expected caller of this method. Do not depend on this anywhere else.</p>
+ *
+ * <p>TODO: We probably need to reconsider how IME should be handled.</p>
+ * @hide
+ * @param token Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ */
+ public void clearLastInputMethodWindowForTransition(final IBinder token) {
+ synchronized (mH) {
+ try {
+ mService.clearLastInputMethodWindowForTransition(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Force switch to the last used input method and subtype. If the last input method didn't have
+ * any subtypes, the framework will simply switch to the last input method with no subtype
+ * specified.
+ * @param imeToken Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ * @return true if the current input method and subtype was successfully switched to the last
+ * used input method and subtype.
+ */
+ public boolean switchToLastInputMethod(IBinder imeToken) {
+ synchronized (mH) {
+ try {
+ return mService.switchToLastInputMethod(imeToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Force switch to the next input method and subtype. If there is no IME enabled except
+ * current IME and subtype, do nothing.
+ * @param imeToken Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ * @param onlyCurrentIme if true, the framework will find the next subtype which
+ * belongs to the current IME
+ * @return true if the current input method and subtype was successfully switched to the next
+ * input method and subtype.
+ */
+ public boolean switchToNextInputMethod(IBinder imeToken, boolean onlyCurrentIme) {
+ synchronized (mH) {
+ try {
+ return mService.switchToNextInputMethod(imeToken, onlyCurrentIme);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the current IME needs to offer the users ways to switch to a next input
+ * method (e.g. a globe key.).
+ * When an IME sets supportsSwitchingToNextInputMethod and this method returns true,
+ * the IME has to offer ways to to invoke {@link #switchToNextInputMethod} accordingly.
+ * <p> Note that the system determines the most appropriate next input method
+ * and subtype in order to provide the consistent user experience in switching
+ * between IMEs and subtypes.
+ * @param imeToken Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ */
+ public boolean shouldOfferSwitchingToNextInputMethod(IBinder imeToken) {
+ synchronized (mH) {
+ try {
+ return mService.shouldOfferSwitchingToNextInputMethod(imeToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Set additional input method subtypes. Only a process which shares the same uid with the IME
+ * can add additional input method subtypes to the IME.
+ * Please note that a subtype's status is stored in the system.
+ * For example, enabled subtypes are remembered by the framework even after they are removed
+ * by using this method. If you re-add the same subtypes again,
+ * they will just get enabled. If you want to avoid such conflicts, for instance, you may
+ * want to create a "different" new subtype even with the same locale and mode,
+ * by changing its extra value. The different subtype won't get affected by the stored past
+ * status. (You may want to take a look at {@link InputMethodSubtype#hashCode()} to refer
+ * to the current implementation.)
+ *
+ * <p>NOTE: If the same subtype exists in both the manifest XML file and additional subtypes
+ * specified by {@code subtypes}, those multiple instances are automatically merged into one
+ * instance.</p>
+ *
+ * <p>CAVEAT: In API Level 23 and prior, the system may do nothing if an empty
+ * {@link InputMethodSubtype} is specified in {@code subtypes}, which prevents you from removing
+ * the last one entry of additional subtypes. If your IME statically defines one or more
+ * subtypes in the manifest XML file, you may be able to work around this limitation by
+ * specifying one of those statically defined subtypes in {@code subtypes}.</p>
+ *
+ * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
+ * @param subtypes subtypes will be added as additional subtypes of the current input method.
+ */
+ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+ synchronized (mH) {
+ try {
+ mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ public InputMethodSubtype getLastInputMethodSubtype() {
+ synchronized (mH) {
+ try {
+ return mService.getLastInputMethodSubtype();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access
+ * permission to the content.
+ *
+ * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, EditorInfo)}
+ * for details.</p>
+ *
+ * @param token Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ * @param inputContentInfo Content to be temporarily exposed from the input method to the
+ * application.
+ * This cannot be {@code null}.
+ * @param editorInfo The editor that receives {@link InputContentInfo}.
+ * @hide
+ */
+ public void exposeContent(@NonNull IBinder token, @NonNull InputContentInfo inputContentInfo,
+ @NonNull EditorInfo editorInfo) {
+ final IInputContentUriToken uriToken;
+ final Uri contentUri = inputContentInfo.getContentUri();
+ try {
+ uriToken = mService.createInputContentUriToken(token, contentUri,
+ editorInfo.packageName);
+ if (uriToken == null) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "createInputContentAccessToken failed. contentUri=" + contentUri.toString()
+ + " packageName=" + editorInfo.packageName, e);
+ return;
+ }
+ inputContentInfo.setUriToken(uriToken);
+ return;
+ }
+
+ void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ final Printer p = new PrintWriterPrinter(fout);
+ p.println("Input method client state for " + this + ":");
+
+ p.println(" mService=" + mService);
+ p.println(" mMainLooper=" + mMainLooper);
+ p.println(" mIInputContext=" + mIInputContext);
+ p.println(" mActive=" + mActive
+ + " mHasBeenInactive=" + mHasBeenInactive
+ + " mBindSequence=" + mBindSequence
+ + " mCurId=" + mCurId);
+ p.println(" mFullscreenMode=" + mFullscreenMode);
+ p.println(" mCurMethod=" + mCurMethod);
+ p.println(" mCurRootView=" + mCurRootView);
+ p.println(" mServedView=" + mServedView);
+ p.println(" mNextServedView=" + mNextServedView);
+ p.println(" mServedConnecting=" + mServedConnecting);
+ if (mCurrentTextBoxAttribute != null) {
+ p.println(" mCurrentTextBoxAttribute:");
+ mCurrentTextBoxAttribute.dump(p, " ");
+ } else {
+ p.println(" mCurrentTextBoxAttribute: null");
+ }
+ p.println(" mServedInputConnectionWrapper=" + mServedInputConnectionWrapper);
+ p.println(" mCompletions=" + Arrays.toString(mCompletions));
+ p.println(" mCursorRect=" + mCursorRect);
+ p.println(" mCursorSelStart=" + mCursorSelStart
+ + " mCursorSelEnd=" + mCursorSelEnd
+ + " mCursorCandStart=" + mCursorCandStart
+ + " mCursorCandEnd=" + mCursorCandEnd);
+ p.println(" mNextUserActionNotificationSequenceNumber="
+ + mNextUserActionNotificationSequenceNumber
+ + " mLastSentUserActionNotificationSequenceNumber="
+ + mLastSentUserActionNotificationSequenceNumber);
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to
+ * the IME has been finished.
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ public void onFinishedInputEvent(Object token, boolean handled);
+ }
+
+ private final class ImeInputEventSender extends InputEventSender {
+ public ImeInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedInputEvent(seq, handled, false);
+ }
+ }
+
+ private final class PendingEvent implements Runnable {
+ public InputEvent mEvent;
+ public Object mToken;
+ public String mInputMethodId;
+ public FinishedInputEventCallback mCallback;
+ public Handler mHandler;
+ public boolean mHandled;
+
+ public void recycle() {
+ mEvent = null;
+ mToken = null;
+ mInputMethodId = null;
+ mCallback = null;
+ mHandler = null;
+ mHandled = false;
+ }
+
+ @Override
+ public void run() {
+ mCallback.onFinishedInputEvent(mToken, mHandled);
+
+ synchronized (mH) {
+ recyclePendingEventLocked(this);
+ }
+ }
+ }
+
+ private static String dumpViewInfo(@Nullable final View view) {
+ if (view == null) {
+ return "null";
+ }
+ final StringBuilder sb = new StringBuilder();
+ sb.append(view);
+ sb.append(",focus=" + view.hasFocus());
+ sb.append(",windowFocus=" + view.hasWindowFocus());
+ sb.append(",window=" + view.getWindowToken());
+ sb.append(",temporaryDetach=" + view.isTemporarilyDetached());
+ return sb.toString();
+ }
+}
diff --git a/android/view/inputmethod/InputMethodManagerInternal.java b/android/view/inputmethod/InputMethodManagerInternal.java
new file mode 100644
index 00000000..77df4e38
--- /dev/null
+++ b/android/view/inputmethod/InputMethodManagerInternal.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 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.inputmethod;
+
+/**
+ * Input method manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public interface InputMethodManagerInternal {
+ /**
+ * Called by the power manager to tell the input method manager whether it
+ * should start watching for wake events.
+ */
+ void setInteractive(boolean interactive);
+
+ /**
+ * Called by the window manager to let the input method manager rotate the input method.
+ */
+ void switchInputMethod(boolean forwardDirection);
+
+ /**
+ * Hides the current input method, if visible.
+ */
+ void hideCurrentInputMethod();
+}
diff --git a/android/view/inputmethod/InputMethodManager_Accessor.java b/android/view/inputmethod/InputMethodManager_Accessor.java
new file mode 100644
index 00000000..dc4f9c86
--- /dev/null
+++ b/android/view/inputmethod/InputMethodManager_Accessor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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.inputmethod;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class InputMethodManager_Accessor {
+
+ public static void resetInstance() {
+ InputMethodManager.sInstance = null;
+ }
+}
diff --git a/android/view/inputmethod/InputMethodManager_Delegate.java b/android/view/inputmethod/InputMethodManager_Delegate.java
new file mode 100644
index 00000000..7c98847a
--- /dev/null
+++ b/android/view/inputmethod/InputMethodManager_Delegate.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 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.inputmethod;
+
+import com.android.layoutlib.bridge.android.BridgeIInputMethodManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.os.Looper;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link InputMethodManager}
+ *
+ * Through the layoutlib_create tool, the original methods of InputMethodManager have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class InputMethodManager_Delegate {
+
+ // ---- Overridden methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static InputMethodManager getInstance() {
+ synchronized (InputMethodManager.class) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm == null) {
+ imm = new InputMethodManager(
+ new BridgeIInputMethodManager(), Looper.getMainLooper());
+ InputMethodManager.sInstance = imm;
+ }
+ return imm;
+ }
+ }
+}
diff --git a/android/view/inputmethod/InputMethodSession.java b/android/view/inputmethod/InputMethodSession.java
new file mode 100644
index 00000000..74fbbc7e
--- /dev/null
+++ b/android/view/inputmethod/InputMethodSession.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2007-2008 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.inputmethod;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The InputMethodSession interface provides the per-client functionality
+ * of {@link InputMethod} that is safe to expose to applications.
+ *
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ */
+public interface InputMethodSession {
+
+ public interface EventCallback {
+ void finishedEvent(int seq, boolean handled);
+ }
+
+ /**
+ * This method is called when the application would like to stop
+ * receiving text input.
+ */
+ public void finishInput();
+
+ /**
+ * This method is called when the selection or cursor in the current
+ * target input field has changed.
+ *
+ * @param oldSelStart The previous text offset of the cursor selection
+ * start position.
+ * @param oldSelEnd The previous text offset of the cursor selection
+ * end position.
+ * @param newSelStart The new text offset of the cursor selection
+ * start position.
+ * @param newSelEnd The new text offset of the cursor selection
+ * end position.
+ * @param candidatesStart The text offset of the current candidate
+ * text start position.
+ * @param candidatesEnd The text offset of the current candidate
+ * text end position.
+ */
+ public void updateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd,
+ int candidatesStart, int candidatesEnd);
+
+ /**
+ * This method is called when the user tapped a text view.
+ * IMEs can't rely on this method being called because this was not part of the original IME
+ * protocol, so applications with custom text editing written before this method appeared will
+ * not call to inform the IME of this interaction.
+ * @param focusChanged true if the user changed the focused view by this click.
+ */
+ public void viewClicked(boolean focusChanged);
+
+ /**
+ * This method is called when cursor location of the target input field
+ * has changed within its window. This is not normally called, but will
+ * only be reported if requested by the input method.
+ *
+ * @param newCursor The rectangle of the cursor currently being shown in
+ * the input field's window coordinates.
+ */
+ public void updateCursor(Rect newCursor);
+
+ /**
+ * Called by a text editor that performs auto completion, to tell the
+ * input method about the completions it has available. This can be used
+ * by the input method to display them to the user to select the text to
+ * be inserted.
+ *
+ * @param completions Array of text completions that are available, starting with
+ * the best. If this array is null, any existing completions will be
+ * removed.
+ */
+ public void displayCompletions(CompletionInfo[] completions);
+
+ /**
+ * Called by a text editor to report its new extracted text when its
+ * contents change. This will only be called if the input method
+ * calls {@link InputConnection#getExtractedText(ExtractedTextRequest, int)
+ * InputConnection.getExtractedText()} with the option to report updates.
+ *
+ * @param token The input method supplied token for identifying its request.
+ * @param text The new extracted text.
+ */
+ public void updateExtractedText(int token, ExtractedText text);
+
+ /**
+ * This method is called when a key is pressed. When done with the event,
+ * the implementation must call back on <var>callback</var> with its
+ * result.
+ *
+ * <p>
+ * If the input method wants to handle this event, return true, otherwise
+ * return false and the caller (i.e. the application) will handle the event.
+ *
+ * @param event The key event.
+ *
+ * @return Whether the input method wants to handle this event.
+ *
+ * @see #dispatchKeyUp
+ * @see android.view.KeyEvent
+ */
+ public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback);
+
+ /**
+ * This method is called when there is a track ball event.
+ *
+ * <p>
+ * If the input method wants to handle this event, return true, otherwise
+ * return false and the caller (i.e. the application) will handle the event.
+ *
+ * @param event The motion event.
+ *
+ * @return Whether the input method wants to handle this event.
+ *
+ * @see android.view.MotionEvent
+ */
+ public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback);
+
+ /**
+ * This method is called when there is a generic motion event.
+ *
+ * <p>
+ * If the input method wants to handle this event, return true, otherwise
+ * return false and the caller (i.e. the application) will handle the event.
+ *
+ * @param event The motion event.
+ *
+ * @return Whether the input method wants to handle this event.
+ *
+ * @see android.view.MotionEvent
+ */
+ public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback);
+
+ /**
+ * Process a private command sent from the application to the input method.
+ * This can be used to provide domain-specific features that are
+ * only known between certain input methods and their clients.
+ *
+ * @param action Name of the command to be performed. This <em>must</em>
+ * be a scoped name, i.e. prefixed with a package name you own, so that
+ * different developers will not create conflicting commands.
+ * @param data Any data to include with the command.
+ */
+ public void appPrivateCommand(String action, Bundle data);
+
+ /**
+ * Toggle the soft input window.
+ * Applications can toggle the state of the soft input window.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
+ * {@link InputMethodManager#SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
+ * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags);
+
+ /**
+ * This method is called when the cursor and/or the character position relevant to text input
+ * is changed on the screen. This is not called by default. It will only be reported if
+ * requested by the input method.
+ *
+ * @param cursorAnchorInfo Positional information relevant to text input, such as text
+ * insertion point and composition string.
+ */
+ public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo);
+}
diff --git a/android/view/inputmethod/InputMethodSubtype.java b/android/view/inputmethod/InputMethodSubtype.java
new file mode 100644
index 00000000..a7d7a8d6
--- /dev/null
+++ b/android/view/inputmethod/InputMethodSubtype.java
@@ -0,0 +1,696 @@
+/*
+ * Copyright (C) 2010 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
+import android.icu.text.DisplayContext;
+import android.icu.text.LocaleDisplayNames;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.inputmethod.InputMethodUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IllegalFormatException;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is used to specify meta information of a subtype contained in an input method editor
+ * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...),
+ * and is used for IME switch and settings. The input method subtype allows the system to bring up
+ * the specified subtype of the designated IME directly.
+ *
+ * <p>It should be defined in an XML resource file of the input method with the
+ * <code>&lt;subtype&gt;</code> element, which resides within an {@code <input-method>} element.
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an Input Method</a>.</p>
+ *
+ * @see InputMethodInfo
+ *
+ * @attr ref android.R.styleable#InputMethod_Subtype_label
+ * @attr ref android.R.styleable#InputMethod_Subtype_icon
+ * @attr ref android.R.styleable#InputMethod_Subtype_languageTag
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
+ * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary
+ * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype
+ * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId
+ * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable
+ */
+public final class InputMethodSubtype implements Parcelable {
+ private static final String TAG = InputMethodSubtype.class.getSimpleName();
+ private static final String LANGUAGE_TAG_NONE = "";
+ private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
+ private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
+ // TODO: remove this
+ private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
+ "UntranslatableReplacementStringInSubtypeName";
+ private static final int SUBTYPE_ID_NONE = 0;
+
+ private final boolean mIsAuxiliary;
+ private final boolean mOverridesImplicitlyEnabledSubtype;
+ private final boolean mIsAsciiCapable;
+ private final int mSubtypeHashCode;
+ private final int mSubtypeIconResId;
+ private final int mSubtypeNameResId;
+ private final int mSubtypeId;
+ private final String mSubtypeLocale;
+ private final String mSubtypeLanguageTag;
+ private final String mSubtypeMode;
+ private final String mSubtypeExtraValue;
+ private final Object mLock = new Object();
+ private volatile Locale mCachedLocaleObj;
+ private volatile HashMap<String, String> mExtraValueHashMapCache;
+
+ /**
+ * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
+ * This class is designed to be used with
+ * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
+ * The developer needs to be aware of what each parameter means.
+ */
+ public static class InputMethodSubtypeBuilder {
+ /**
+ * @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
+ * An auxiliary subtype has the following differences with a regular subtype:
+ * - An auxiliary subtype cannot be chosen as the default IME in Settings.
+ * - The framework will never switch to this subtype through
+ * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+ * Note that the subtype will still be available in the IME switcher.
+ * The intent is to allow for IMEs to specify they are meant to be invoked temporarily
+ * in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
+ */
+ public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
+ mIsAuxiliary = isAuxiliary;
+ return this;
+ }
+ private boolean mIsAuxiliary = false;
+
+ /**
+ * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
+ * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
+ * subtype with this parameter set will not be shown in the list of subtypes in each IME's
+ * subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
+ * subtype that adapts to the current system language.
+ */
+ public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
+ boolean overridesImplicitlyEnabledSubtype) {
+ mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+ return this;
+ }
+ private boolean mOverridesImplicitlyEnabledSubtype = false;
+
+ /**
+ * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
+ * is ASCII capable, it should guarantee that the user can input ASCII characters with
+ * this subtype. This is important because many password fields only allow
+ * ASCII-characters.
+ */
+ public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
+ mIsAsciiCapable = isAsciiCapable;
+ return this;
+ }
+ private boolean mIsAsciiCapable = false;
+
+ /**
+ * @param subtypeIconResId is a resource ID of the subtype icon drawable.
+ */
+ public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
+ mSubtypeIconResId = subtypeIconResId;
+ return this;
+ }
+ private int mSubtypeIconResId = 0;
+
+ /**
+ * @param subtypeNameResId is the resource ID of the subtype name string.
+ * The string resource may have exactly one %s in it. If present,
+ * the %s part will be replaced with the locale's display name by
+ * the formatter. Please refer to {@link #getDisplayName} for details.
+ */
+ public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
+ mSubtypeNameResId = subtypeNameResId;
+ return this;
+ }
+ private int mSubtypeNameResId = 0;
+
+ /**
+ * @param subtypeId is the unique ID for this subtype. The input method framework keeps
+ * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
+ * stay enabled even if other attributes are different. If the ID is unspecified or 0,
+ * Arrays.hashCode(new Object[] {locale, mode, extraValue,
+ * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
+ */
+ public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
+ mSubtypeId = subtypeId;
+ return this;
+ }
+ private int mSubtypeId = SUBTYPE_ID_NONE;
+
+ /**
+ * @param subtypeLocale is the locale supported by this subtype.
+ */
+ public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
+ mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
+ return this;
+ }
+ private String mSubtypeLocale = "";
+
+ /**
+ * @param languageTag is the BCP-47 Language Tag supported by this subtype.
+ */
+ public InputMethodSubtypeBuilder setLanguageTag(String languageTag) {
+ mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag;
+ return this;
+ }
+ private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE;
+
+ /**
+ * @param subtypeMode is the mode supported by this subtype.
+ */
+ public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
+ mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
+ return this;
+ }
+ private String mSubtypeMode = "";
+ /**
+ * @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
+ * but the API supplies tools to deal with a key-value comma-separated list; see
+ * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
+ */
+ public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
+ mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
+ return this;
+ }
+ private String mSubtypeExtraValue = "";
+
+ /**
+ * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
+ */
+ public InputMethodSubtype build() {
+ return new InputMethodSubtype(this);
+ }
+ }
+
+ private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
+ String mode, String extraValue, boolean isAuxiliary,
+ boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
+ final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
+ builder.mSubtypeNameResId = nameId;
+ builder.mSubtypeIconResId = iconId;
+ builder.mSubtypeLocale = locale;
+ builder.mSubtypeMode = mode;
+ builder.mSubtypeExtraValue = extraValue;
+ builder.mIsAuxiliary = isAuxiliary;
+ builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+ builder.mSubtypeId = id;
+ builder.mIsAsciiCapable = isAsciiCapable;
+ return builder;
+ }
+
+ /**
+ * Constructor with no subtype ID specified.
+ * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+ * Arguments for this constructor have the same meanings as
+ * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
+ * boolean, int)} except "id".
+ */
+ @Deprecated
+ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
+ boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
+ this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype, 0);
+ }
+
+ /**
+ * Constructor.
+ * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+ * "isAsciiCapable" is "false" in this constructor.
+ * @param nameId Resource ID of the subtype name string. The string resource may have exactly
+ * one %s in it. If there is, the %s part will be replaced with the locale's display name by
+ * the formatter. Please refer to {@link #getDisplayName} for details.
+ * @param iconId Resource ID of the subtype icon drawable.
+ * @param locale The locale supported by the subtype
+ * @param mode The mode supported by the subtype
+ * @param extraValue The extra value of the subtype. This string is free-form, but the API
+ * supplies tools to deal with a key-value comma-separated list; see
+ * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
+ * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
+ * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
+ * the Settings even when this subtype is enabled. Please note that this subtype will still
+ * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
+ * to this subtype while an IME is shown. The framework will never switch the current IME to
+ * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+ * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
+ * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
+ * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
+ * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
+ * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
+ * Having an "automatic" subtype is an example use of this flag.
+ * @param id The unique ID for the subtype. The input method framework keeps track of enabled
+ * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if
+ * other attributes are different. If the ID is unspecified or 0,
+ * Arrays.hashCode(new Object[] {locale, mode, extraValue,
+ * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
+ */
+ @Deprecated
+ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
+ boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
+ this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype, id, false));
+ }
+
+ /**
+ * Constructor.
+ * @param builder Builder for InputMethodSubtype
+ */
+ private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
+ mSubtypeNameResId = builder.mSubtypeNameResId;
+ mSubtypeIconResId = builder.mSubtypeIconResId;
+ mSubtypeLocale = builder.mSubtypeLocale;
+ mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
+ mSubtypeMode = builder.mSubtypeMode;
+ mSubtypeExtraValue = builder.mSubtypeExtraValue;
+ mIsAuxiliary = builder.mIsAuxiliary;
+ mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
+ mSubtypeId = builder.mSubtypeId;
+ mIsAsciiCapable = builder.mIsAsciiCapable;
+ // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
+ // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
+ if (mSubtypeId != SUBTYPE_ID_NONE) {
+ mSubtypeHashCode = mSubtypeId;
+ } else {
+ mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
+ mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, mIsAsciiCapable);
+ }
+ }
+
+ InputMethodSubtype(Parcel source) {
+ String s;
+ mSubtypeNameResId = source.readInt();
+ mSubtypeIconResId = source.readInt();
+ s = source.readString();
+ mSubtypeLocale = s != null ? s : "";
+ s = source.readString();
+ mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
+ s = source.readString();
+ mSubtypeMode = s != null ? s : "";
+ s = source.readString();
+ mSubtypeExtraValue = s != null ? s : "";
+ mIsAuxiliary = (source.readInt() == 1);
+ mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
+ mSubtypeHashCode = source.readInt();
+ mSubtypeId = source.readInt();
+ mIsAsciiCapable = (source.readInt() == 1);
+ }
+
+ /**
+ * @return Resource ID of the subtype name string.
+ */
+ public int getNameResId() {
+ return mSubtypeNameResId;
+ }
+
+ /**
+ * @return Resource ID of the subtype icon drawable.
+ */
+ public int getIconResId() {
+ return mSubtypeIconResId;
+ }
+
+ /**
+ * @return The locale of the subtype. This method returns the "locale" string parameter passed
+ * to the constructor.
+ *
+ * @deprecated Use {@link #getLanguageTag()} instead.
+ */
+ @Deprecated
+ @NonNull
+ public String getLocale() {
+ return mSubtypeLocale;
+ }
+
+ /**
+ * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag
+ * is specified.
+ *
+ * @see Locale#forLanguageTag(String)
+ */
+ @NonNull
+ public String getLanguageTag() {
+ return mSubtypeLanguageTag;
+ }
+
+ /**
+ * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+ * specified, then try to construct from {@link #getLocale()}
+ *
+ * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
+ * @hide
+ */
+ @Nullable
+ public Locale getLocaleObject() {
+ if (mCachedLocaleObj != null) {
+ return mCachedLocaleObj;
+ }
+ synchronized (mLock) {
+ if (mCachedLocaleObj != null) {
+ return mCachedLocaleObj;
+ }
+ if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+ mCachedLocaleObj = Locale.forLanguageTag(mSubtypeLanguageTag);
+ } else {
+ mCachedLocaleObj = InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
+ }
+ return mCachedLocaleObj;
+ }
+ }
+
+ /**
+ * @return The mode of the subtype.
+ */
+ public String getMode() {
+ return mSubtypeMode;
+ }
+
+ /**
+ * @return The extra value of the subtype.
+ */
+ public String getExtraValue() {
+ return mSubtypeExtraValue;
+ }
+
+ /**
+ * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be
+ * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this
+ * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in
+ * the IME switcher to allow the user to tentatively switch to this subtype while an IME is
+ * shown. The framework will never switch the current IME to this subtype by
+ * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+ * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
+ * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
+ */
+ public boolean isAuxiliary() {
+ return mIsAuxiliary;
+ }
+
+ /**
+ * @return true when this subtype will be enabled by default if no other subtypes in the IME
+ * are enabled explicitly, false otherwise. Note that a subtype with this method returning true
+ * will not be shown in the list of subtypes in each IME's subtype enabler. Having an
+ * "automatic" subtype is an example use of this flag.
+ */
+ public boolean overridesImplicitlyEnabledSubtype() {
+ return mOverridesImplicitlyEnabledSubtype;
+ }
+
+ /**
+ * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
+ * capable, it should guarantee that the user can input ASCII characters with this subtype.
+ * This is important because many password fields only allow ASCII-characters.
+ */
+ public boolean isAsciiCapable() {
+ return mIsAsciiCapable;
+ }
+
+ /**
+ * Returns a display name for this subtype.
+ *
+ * <p>If {@code subtypeNameResId} is specified (!= 0) text generated from that resource will
+ * be returned. The localized string resource of the label should be capitalized for inclusion
+ * in UI lists. The string resource may contain at most one {@code %s}. If present, the
+ * {@code %s} will be replaced with the display name of the subtype locale in the user's locale.
+ *
+ * <p>If {@code subtypeNameResId} is not specified (== 0) the framework returns the display name
+ * of the subtype locale, as capitalized for use in UI lists, in the user's locale.
+ *
+ * @param context {@link Context} will be used for getting {@link Locale} and
+ * {@link android.content.pm.PackageManager}.
+ * @param packageName The package name of the input method.
+ * @param appInfo The {@link ApplicationInfo} of the input method.
+ * @return a display name for this subtype.
+ */
+ @NonNull
+ public CharSequence getDisplayName(
+ Context context, String packageName, ApplicationInfo appInfo) {
+ if (mSubtypeNameResId == 0) {
+ return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(),
+ DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
+ }
+
+ final CharSequence subtypeName = context.getPackageManager().getText(
+ packageName, mSubtypeNameResId, appInfo);
+ if (TextUtils.isEmpty(subtypeName)) {
+ return "";
+ }
+ final String subtypeNameString = subtypeName.toString();
+ String replacementString;
+ if (containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
+ replacementString = getExtraValueOf(
+ EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
+ } else {
+ final DisplayContext displayContext;
+ if (TextUtils.equals(subtypeNameString, "%s")) {
+ displayContext = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU;
+ } else if (subtypeNameString.startsWith("%s")) {
+ displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
+ } else {
+ displayContext = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE;
+ }
+ replacementString = getLocaleDisplayName(getLocaleFromContext(context),
+ getLocaleObject(), displayContext);
+ }
+ if (replacementString == null) {
+ replacementString = "";
+ }
+ try {
+ return String.format(subtypeNameString, replacementString);
+ } catch (IllegalFormatException e) {
+ Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
+ return "";
+ }
+ }
+
+ @Nullable
+ private static Locale getLocaleFromContext(@Nullable final Context context) {
+ if (context == null) {
+ return null;
+ }
+ if (context.getResources() == null) {
+ return null;
+ }
+ final Configuration configuration = context.getResources().getConfiguration();
+ if (configuration == null) {
+ return null;
+ }
+ return configuration.getLocales().get(0);
+ }
+
+ /**
+ * @param displayLocale {@link Locale} to be used to display {@code localeToDisplay}
+ * @param localeToDisplay {@link Locale} to be displayed in {@code displayLocale}
+ * @param displayContext context parameter to be used to display {@code localeToDisplay} in
+ * {@code displayLocale}
+ * @return Returns the name of the {@code localeToDisplay} in the user's current locale.
+ */
+ @NonNull
+ private static String getLocaleDisplayName(
+ @Nullable Locale displayLocale, @Nullable Locale localeToDisplay,
+ final DisplayContext displayContext) {
+ if (localeToDisplay == null) {
+ return "";
+ }
+ final Locale nonNullDisplayLocale =
+ displayLocale != null ? displayLocale : Locale.getDefault();
+ return LocaleDisplayNames
+ .getInstance(nonNullDisplayLocale, displayContext)
+ .localeDisplayName(localeToDisplay);
+ }
+
+ private HashMap<String, String> getExtraValueHashMap() {
+ synchronized (this) {
+ HashMap<String, String> extraValueMap = mExtraValueHashMapCache;
+ if (extraValueMap != null) {
+ return extraValueMap;
+ }
+ extraValueMap = new HashMap<>();
+ final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
+ for (int i = 0; i < pairs.length; ++i) {
+ final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
+ if (pair.length == 1) {
+ extraValueMap.put(pair[0], null);
+ } else if (pair.length > 1) {
+ if (pair.length > 2) {
+ Slog.w(TAG, "ExtraValue has two or more '='s");
+ }
+ extraValueMap.put(pair[0], pair[1]);
+ }
+ }
+ mExtraValueHashMapCache = extraValueMap;
+ return extraValueMap;
+ }
+ }
+
+ /**
+ * The string of ExtraValue in subtype should be defined as follows:
+ * example: key0,key1=value1,key2,key3,key4=value4
+ * @param key The key of extra value
+ * @return The subtype contains specified the extra value
+ */
+ public boolean containsExtraValueKey(String key) {
+ return getExtraValueHashMap().containsKey(key);
+ }
+
+ /**
+ * The string of ExtraValue in subtype should be defined as follows:
+ * example: key0,key1=value1,key2,key3,key4=value4
+ * @param key The key of extra value
+ * @return The value of the specified key
+ */
+ public String getExtraValueOf(String key) {
+ return getExtraValueHashMap().get(key);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubtypeHashCode;
+ }
+
+ /**
+ * @hide
+ * @return {@code true} if a valid subtype ID exists.
+ */
+ public final boolean hasSubtypeId() {
+ return mSubtypeId != SUBTYPE_ID_NONE;
+ }
+
+ /**
+ * @hide
+ * @return subtype ID. {@code 0} means that not subtype ID is specified.
+ */
+ public final int getSubtypeId() {
+ return mSubtypeId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof InputMethodSubtype) {
+ InputMethodSubtype subtype = (InputMethodSubtype) o;
+ if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
+ return (subtype.hashCode() == hashCode());
+ }
+ return (subtype.hashCode() == hashCode())
+ && (subtype.getLocale().equals(getLocale()))
+ && (subtype.getLanguageTag().equals(getLanguageTag()))
+ && (subtype.getMode().equals(getMode()))
+ && (subtype.getExtraValue().equals(getExtraValue()))
+ && (subtype.isAuxiliary() == isAuxiliary())
+ && (subtype.overridesImplicitlyEnabledSubtype()
+ == overridesImplicitlyEnabledSubtype())
+ && (subtype.isAsciiCapable() == isAsciiCapable());
+ }
+ return false;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(mSubtypeNameResId);
+ dest.writeInt(mSubtypeIconResId);
+ dest.writeString(mSubtypeLocale);
+ dest.writeString(mSubtypeLanguageTag);
+ dest.writeString(mSubtypeMode);
+ dest.writeString(mSubtypeExtraValue);
+ dest.writeInt(mIsAuxiliary ? 1 : 0);
+ dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
+ dest.writeInt(mSubtypeHashCode);
+ dest.writeInt(mSubtypeId);
+ dest.writeInt(mIsAsciiCapable ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<InputMethodSubtype> CREATOR
+ = new Parcelable.Creator<InputMethodSubtype>() {
+ @Override
+ public InputMethodSubtype createFromParcel(Parcel source) {
+ return new InputMethodSubtype(source);
+ }
+
+ @Override
+ public InputMethodSubtype[] newArray(int size) {
+ return new InputMethodSubtype[size];
+ }
+ };
+
+ private static int hashCodeInternal(String locale, String mode, String extraValue,
+ boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
+ boolean isAsciiCapable) {
+ // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
+ // attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
+ final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
+ if (needsToCalculateCompatibleHashCode) {
+ return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype});
+ }
+ return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype, isAsciiCapable});
+ }
+
+ /**
+ * Sort the list of InputMethodSubtype
+ * @param context Context will be used for getting localized strings from IME
+ * @param flags Flags for the sort order
+ * @param imi InputMethodInfo of which subtypes are subject to be sorted
+ * @param subtypeList List of InputMethodSubtype which will be sorted
+ * @return Sorted list of subtypes
+ * @hide
+ */
+ public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi,
+ List<InputMethodSubtype> subtypeList) {
+ if (imi == null) return subtypeList;
+ final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
+ subtypeList);
+ final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>();
+ int N = imi.getSubtypeCount();
+ for (int i = 0; i < N; ++i) {
+ InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (inputSubtypesSet.contains(subtype)) {
+ sortedList.add(subtype);
+ inputSubtypesSet.remove(subtype);
+ }
+ }
+ // If subtypes in inputSubtypesSet remain, that means these subtypes are not
+ // contained in imi, so the remaining subtypes will be appended.
+ for (InputMethodSubtype subtype: inputSubtypesSet) {
+ sortedList.add(subtype);
+ }
+ return sortedList;
+ }
+} \ No newline at end of file
diff --git a/android/view/inputmethod/InputMethodSubtypeArray.java b/android/view/inputmethod/InputMethodSubtypeArray.java
new file mode 100644
index 00000000..6a748ced
--- /dev/null
+++ b/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2007-2014 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.inputmethod;
+
+import android.os.Parcel;
+import android.util.Slog;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * An array-like container that stores multiple instances of {@link InputMethodSubtype}.
+ *
+ * <p>This container is designed to reduce the risk of {@link TransactionTooLargeException}
+ * when one or more instancess of {@link InputMethodInfo} are transferred through IPC.
+ * Basically this class does following three tasks.</p>
+ * <ul>
+ * <li>Applying compression for the marshalled data</li>
+ * <li>Lazily unmarshalling objects</li>
+ * <li>Caching the marshalled data when appropriate</li>
+ * </ul>
+ *
+ * @hide
+ */
+public class InputMethodSubtypeArray {
+ private final static String TAG = "InputMethodSubtypeArray";
+
+ /**
+ * Create a new instance of {@link InputMethodSubtypeArray} from an existing list of
+ * {@link InputMethodSubtype}.
+ *
+ * @param subtypes A list of {@link InputMethodSubtype} from which
+ * {@link InputMethodSubtypeArray} will be created.
+ */
+ public InputMethodSubtypeArray(final List<InputMethodSubtype> subtypes) {
+ if (subtypes == null) {
+ mCount = 0;
+ return;
+ }
+ mCount = subtypes.size();
+ mInstance = subtypes.toArray(new InputMethodSubtype[mCount]);
+ }
+
+ /**
+ * Unmarshall an instance of {@link InputMethodSubtypeArray} from a given {@link Parcel}
+ * object.
+ *
+ * @param source A {@link Parcel} object from which {@link InputMethodSubtypeArray} will be
+ * unmarshalled.
+ */
+ public InputMethodSubtypeArray(final Parcel source) {
+ mCount = source.readInt();
+ if (mCount > 0) {
+ mDecompressedSize = source.readInt();
+ mCompressedData = source.createByteArray();
+ }
+ }
+
+ /**
+ * Marshall the instance into a given {@link Parcel} object.
+ *
+ * <p>This methods may take a bit additional time to compress data lazily when called
+ * first time.</p>
+ *
+ * @param source A {@link Parcel} object to which {@link InputMethodSubtypeArray} will be
+ * marshalled.
+ */
+ public void writeToParcel(final Parcel dest) {
+ if (mCount == 0) {
+ dest.writeInt(mCount);
+ return;
+ }
+
+ byte[] compressedData = mCompressedData;
+ int decompressedSize = mDecompressedSize;
+ if (compressedData == null && decompressedSize == 0) {
+ synchronized (mLockObject) {
+ compressedData = mCompressedData;
+ decompressedSize = mDecompressedSize;
+ if (compressedData == null && decompressedSize == 0) {
+ final byte[] decompressedData = marshall(mInstance);
+ compressedData = compress(decompressedData);
+ if (compressedData == null) {
+ decompressedSize = -1;
+ Slog.i(TAG, "Failed to compress data.");
+ } else {
+ decompressedSize = decompressedData.length;
+ }
+ mDecompressedSize = decompressedSize;
+ mCompressedData = compressedData;
+ }
+ }
+ }
+
+ if (compressedData != null && decompressedSize > 0) {
+ dest.writeInt(mCount);
+ dest.writeInt(decompressedSize);
+ dest.writeByteArray(compressedData);
+ } else {
+ Slog.i(TAG, "Unexpected state. Behaving as an empty array.");
+ dest.writeInt(0);
+ }
+ }
+
+ /**
+ * Return {@link InputMethodSubtype} specified with the given index.
+ *
+ * <p>This methods may take a bit additional time to decompress data lazily when called
+ * first time.</p>
+ *
+ * @param index The index of {@link InputMethodSubtype}.
+ */
+ public InputMethodSubtype get(final int index) {
+ if (index < 0 || mCount <= index) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ InputMethodSubtype[] instance = mInstance;
+ if (instance == null) {
+ synchronized (mLockObject) {
+ instance = mInstance;
+ if (instance == null) {
+ final byte[] decompressedData =
+ decompress(mCompressedData, mDecompressedSize);
+ // Clear the compressed data until {@link #getMarshalled()} is called.
+ mCompressedData = null;
+ mDecompressedSize = 0;
+ if (decompressedData != null) {
+ instance = unmarshall(decompressedData);
+ } else {
+ Slog.e(TAG, "Failed to decompress data. Returns null as fallback.");
+ instance = new InputMethodSubtype[mCount];
+ }
+ mInstance = instance;
+ }
+ }
+ }
+ return instance[index];
+ }
+
+ /**
+ * Return the number of {@link InputMethodSubtype} objects.
+ */
+ public int getCount() {
+ return mCount;
+ }
+
+ private final Object mLockObject = new Object();
+ private final int mCount;
+
+ private volatile InputMethodSubtype[] mInstance;
+ private volatile byte[] mCompressedData;
+ private volatile int mDecompressedSize;
+
+ private static byte[] marshall(final InputMethodSubtype[] array) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.writeTypedArray(array, 0);
+ return parcel.marshall();
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ parcel = null;
+ }
+ }
+ }
+
+ private static InputMethodSubtype[] unmarshall(final byte[] data) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.unmarshall(data, 0, data.length);
+ parcel.setDataPosition(0);
+ return parcel.createTypedArray(InputMethodSubtype.CREATOR);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ parcel = null;
+ }
+ }
+ }
+
+ private static byte[] compress(final byte[] data) {
+ try (final ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
+ final GZIPOutputStream zipper = new GZIPOutputStream(resultStream)) {
+ zipper.write(data);
+ zipper.finish();
+ return resultStream.toByteArray();
+ } catch(Exception e) {
+ Slog.e(TAG, "Failed to compress the data.", e);
+ return null;
+ }
+ }
+
+ private static byte[] decompress(final byte[] data, final int expectedSize) {
+ try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ final GZIPInputStream unzipper = new GZIPInputStream(inputStream)) {
+ final byte [] result = new byte[expectedSize];
+ int totalReadBytes = 0;
+ while (totalReadBytes < result.length) {
+ final int restBytes = result.length - totalReadBytes;
+ final int readBytes = unzipper.read(result, totalReadBytes, restBytes);
+ if (readBytes < 0) {
+ break;
+ }
+ totalReadBytes += readBytes;
+ }
+ if (expectedSize != totalReadBytes) {
+ return null;
+ }
+ return result;
+ } catch(Exception e) {
+ Slog.e(TAG, "Failed to decompress the data.", e);
+ return null;
+ }
+ }
+}
diff --git a/android/view/inputmethod/SparseRectFArray.java b/android/view/inputmethod/SparseRectFArray.java
new file mode 100644
index 00000000..484fe46f
--- /dev/null
+++ b/android/view/inputmethod/SparseRectFArray.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2014 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.inputmethod;
+
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * An implementation of SparseArray specialized for {@link android.graphics.RectF}.
+ * <p>
+ * As this is a sparse array, it represents an array of {@link RectF} most of which are null. This
+ * class could be in some other packages like android.graphics or android.util but currently
+ * belong to android.view.inputmethod because this class is hidden and used only in input method
+ * framework.
+ * </p>
+ * @hide
+ */
+public final class SparseRectFArray implements Parcelable {
+ /**
+ * The keys, in ascending order, of those {@link RectF} that are not null. For example,
+ * {@code [null, null, null, Rect1, null, Rect2]} would be represented by {@code [3,5]}.
+ * @see #mCoordinates
+ */
+ private final int[] mKeys;
+
+ /**
+ * Stores coordinates of the rectangles, in the order of
+ * {@code rects[mKeys[0]].left}, {@code rects[mKeys[0]].top},
+ * {@code rects[mKeys[0]].right}, {@code rects[mKeys[0]].bottom},
+ * {@code rects[mKeys[1]].left}, {@code rects[mKeys[1]].top},
+ * {@code rects[mKeys[1]].right}, {@code rects[mKeys[1]].bottom},
+ * {@code rects[mKeys[2]].left}, {@code rects[mKeys[2]].top}, ....
+ */
+ private final float[] mCoordinates;
+
+ /**
+ * Stores visibility information.
+ */
+ private final int[] mFlagsArray;
+
+ public SparseRectFArray(final Parcel source) {
+ mKeys = source.createIntArray();
+ mCoordinates = source.createFloatArray();
+ mFlagsArray = source.createIntArray();
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeIntArray(mKeys);
+ dest.writeFloatArray(mCoordinates);
+ dest.writeIntArray(mFlagsArray);
+ }
+
+ @Override
+ public int hashCode() {
+ // TODO: Improve the hash function.
+ if (mKeys == null || mKeys.length == 0) {
+ return 0;
+ }
+ int hash = mKeys.length;
+ // For performance reasons, only the first rectangle is used for the hash code now.
+ for (int i = 0; i < 4; i++) {
+ hash *= 31;
+ hash += mCoordinates[i];
+ }
+ hash *= 31;
+ hash += mFlagsArray[0];
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj){
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SparseRectFArray)) {
+ return false;
+ }
+ final SparseRectFArray that = (SparseRectFArray) obj;
+
+ return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates)
+ && Arrays.equals(mFlagsArray, that.mFlagsArray);
+ }
+
+ @Override
+ public String toString() {
+ if (mKeys == null || mCoordinates == null || mFlagsArray == null) {
+ return "SparseRectFArray{}";
+ }
+ final StringBuilder sb = new StringBuilder();
+ sb.append("SparseRectFArray{");
+ for (int i = 0; i < mKeys.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ final int baseIndex = i * 4;
+ sb.append(mKeys[i]);
+ sb.append(":[");
+ sb.append(mCoordinates[baseIndex + 0]);
+ sb.append(",");
+ sb.append(mCoordinates[baseIndex + 1]);
+ sb.append("],[");
+ sb.append(mCoordinates[baseIndex + 2]);
+ sb.append(",");
+ sb.append(mCoordinates[baseIndex + 3]);
+ sb.append("]:flagsArray=");
+ sb.append(mFlagsArray[i]);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Builder for {@link SparseRectFArray}. This class is not designed to be thread-safe.
+ * @hide
+ */
+ public static final class SparseRectFArrayBuilder {
+ /**
+ * Throws {@link IllegalArgumentException} to make sure that this class is correctly used.
+ * @param key key to be checked.
+ */
+ private void checkIndex(final int key) {
+ if (mCount == 0) {
+ return;
+ }
+ if (mKeys[mCount - 1] >= key) {
+ throw new IllegalArgumentException("key must be greater than all existing keys.");
+ }
+ }
+
+ /**
+ * Extends the internal array if necessary.
+ */
+ private void ensureBufferSize() {
+ if (mKeys == null) {
+ mKeys = new int[INITIAL_SIZE];
+ }
+ if (mCoordinates == null) {
+ mCoordinates = new float[INITIAL_SIZE * 4];
+ }
+ if (mFlagsArray == null) {
+ mFlagsArray = new int[INITIAL_SIZE];
+ }
+ final int requiredIndexArraySize = mCount + 1;
+ if (mKeys.length <= requiredIndexArraySize) {
+ final int[] newArray = new int[requiredIndexArraySize * 2];
+ System.arraycopy(mKeys, 0, newArray, 0, mCount);
+ mKeys = newArray;
+ }
+ final int requiredCoordinatesArraySize = (mCount + 1) * 4;
+ if (mCoordinates.length <= requiredCoordinatesArraySize) {
+ final float[] newArray = new float[requiredCoordinatesArraySize * 2];
+ System.arraycopy(mCoordinates, 0, newArray, 0, mCount * 4);
+ mCoordinates = newArray;
+ }
+ final int requiredFlagsArraySize = requiredIndexArraySize;
+ if (mFlagsArray.length <= requiredFlagsArraySize) {
+ final int[] newArray = new int[requiredFlagsArraySize * 2];
+ System.arraycopy(mFlagsArray, 0, newArray, 0, mCount);
+ mFlagsArray = newArray;
+ }
+ }
+
+ /**
+ * Puts the rectangle with an integer key.
+ * @param key the key to be associated with the rectangle. It must be greater than all
+ * existing keys that have been previously specified.
+ * @param left left of the rectangle.
+ * @param top top of the rectangle.
+ * @param right right of the rectangle.
+ * @param bottom bottom of the rectangle.
+ * @param flags an arbitrary integer value to be associated with this rectangle.
+ * @return the receiver object itself for chaining method calls.
+ * @throws IllegalArgumentException If the index is not greater than all of existing keys.
+ */
+ public SparseRectFArrayBuilder append(final int key,
+ final float left, final float top, final float right, final float bottom,
+ final int flags) {
+ checkIndex(key);
+ ensureBufferSize();
+ final int baseCoordinatesIndex = mCount * 4;
+ mCoordinates[baseCoordinatesIndex + 0] = left;
+ mCoordinates[baseCoordinatesIndex + 1] = top;
+ mCoordinates[baseCoordinatesIndex + 2] = right;
+ mCoordinates[baseCoordinatesIndex + 3] = bottom;
+ final int flagsIndex = mCount;
+ mFlagsArray[flagsIndex] = flags;
+ mKeys[mCount] = key;
+ ++mCount;
+ return this;
+ }
+ private int mCount = 0;
+ private int[] mKeys = null;
+ private float[] mCoordinates = null;
+ private int[] mFlagsArray = null;
+ private static int INITIAL_SIZE = 16;
+
+ public boolean isEmpty() {
+ return mCount <= 0;
+ }
+
+ /**
+ * @return {@link SparseRectFArray} using parameters in this {@link SparseRectFArray}.
+ */
+ public SparseRectFArray build() {
+ return new SparseRectFArray(this);
+ }
+
+ public void reset() {
+ if (mCount == 0) {
+ mKeys = null;
+ mCoordinates = null;
+ mFlagsArray = null;
+ }
+ mCount = 0;
+ }
+ }
+
+ private SparseRectFArray(final SparseRectFArrayBuilder builder) {
+ if (builder.mCount == 0) {
+ mKeys = null;
+ mCoordinates = null;
+ mFlagsArray = null;
+ } else {
+ mKeys = new int[builder.mCount];
+ mCoordinates = new float[builder.mCount * 4];
+ mFlagsArray = new int[builder.mCount];
+ System.arraycopy(builder.mKeys, 0, mKeys, 0, builder.mCount);
+ System.arraycopy(builder.mCoordinates, 0, mCoordinates, 0, builder.mCount * 4);
+ System.arraycopy(builder.mFlagsArray, 0, mFlagsArray, 0, builder.mCount);
+ }
+ }
+
+ public RectF get(final int index) {
+ if (mKeys == null) {
+ return null;
+ }
+ if (index < 0) {
+ return null;
+ }
+ final int arrayIndex = Arrays.binarySearch(mKeys, index);
+ if (arrayIndex < 0) {
+ return null;
+ }
+ final int baseCoordIndex = arrayIndex * 4;
+ return new RectF(mCoordinates[baseCoordIndex],
+ mCoordinates[baseCoordIndex + 1],
+ mCoordinates[baseCoordIndex + 2],
+ mCoordinates[baseCoordIndex + 3]);
+ }
+
+ public int getFlags(final int index, final int valueIfKeyNotFound) {
+ if (mKeys == null) {
+ return valueIfKeyNotFound;
+ }
+ if (index < 0) {
+ return valueIfKeyNotFound;
+ }
+ final int arrayIndex = Arrays.binarySearch(mKeys, index);
+ if (arrayIndex < 0) {
+ return valueIfKeyNotFound;
+ }
+ return mFlagsArray[arrayIndex];
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<SparseRectFArray> CREATOR =
+ new Parcelable.Creator<SparseRectFArray>() {
+ @Override
+ public SparseRectFArray createFromParcel(Parcel source) {
+ return new SparseRectFArray(source);
+ }
+ @Override
+ public SparseRectFArray[] newArray(int size) {
+ return new SparseRectFArray[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
+
diff --git a/android/view/textclassifier/EntityConfidence.java b/android/view/textclassifier/EntityConfidence.java
new file mode 100644
index 00000000..0589d204
--- /dev/null
+++ b/android/view/textclassifier/EntityConfidence.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper object for setting and getting entity scores for classified text.
+ *
+ * @param <T> the entity type.
+ * @hide
+ */
+final class EntityConfidence<T> {
+
+ private final Map<T, Float> mEntityConfidence = new HashMap<>();
+
+ private final Comparator<T> mEntityComparator = (e1, e2) -> {
+ float score1 = mEntityConfidence.get(e1);
+ float score2 = mEntityConfidence.get(e2);
+ if (score1 > score2) {
+ return -1;
+ }
+ if (score1 < score2) {
+ return 1;
+ }
+ return 0;
+ };
+
+ EntityConfidence() {}
+
+ EntityConfidence(@NonNull EntityConfidence<T> source) {
+ Preconditions.checkNotNull(source);
+ mEntityConfidence.putAll(source.mEntityConfidence);
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public void setEntityType(
+ @NonNull T type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ Preconditions.checkNotNull(type);
+ if (confidenceScore > 0) {
+ mEntityConfidence.put(type, Math.min(1, confidenceScore));
+ } else {
+ mEntityConfidence.remove(type);
+ }
+ }
+
+ /**
+ * Returns an immutable list of entities found in the classified text ordered from
+ * high confidence to low confidence.
+ */
+ @NonNull
+ public List<T> getEntities() {
+ List<T> entities = new ArrayList<>(mEntityConfidence.size());
+ entities.addAll(mEntityConfidence.keySet());
+ entities.sort(mEntityComparator);
+ return Collections.unmodifiableList(entities);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(T entity) {
+ if (mEntityConfidence.containsKey(entity)) {
+ return mEntityConfidence.get(entity);
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return mEntityConfidence.toString();
+ }
+}
diff --git a/android/view/textclassifier/LinksInfo.java b/android/view/textclassifier/LinksInfo.java
new file mode 100644
index 00000000..754c9e90
--- /dev/null
+++ b/android/view/textclassifier/LinksInfo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+import android.annotation.NonNull;
+
+/**
+ * Link information that can be applied to text. See: {@link #apply(CharSequence)}.
+ * Typical implementations of this interface will annotate spannable text with e.g
+ * {@link android.text.style.ClickableSpan}s or other annotations.
+ * @hide
+ */
+public interface LinksInfo {
+
+ /**
+ * @hide
+ */
+ LinksInfo NO_OP = text -> false;
+
+ /**
+ * Applies link annotations to the specified text.
+ * These annotations are not guaranteed to be applied. For example, the annotations may not be
+ * applied if the text has changed from what it was when the link spec was generated for it.
+ *
+ * @return Whether or not the link annotations were successfully applied.
+ */
+ boolean apply(@NonNull CharSequence text);
+}
diff --git a/android/view/textclassifier/SmartSelection.java b/android/view/textclassifier/SmartSelection.java
new file mode 100644
index 00000000..f0e83d1f
--- /dev/null
+++ b/android/view/textclassifier/SmartSelection.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+/**
+ * Java wrapper for SmartSelection native library interface.
+ * This library is used for detecting entities in text.
+ */
+final class SmartSelection {
+
+ static {
+ System.loadLibrary("textclassifier");
+ }
+
+ /** Hints the classifier that this may be a url. */
+ static final int HINT_FLAG_URL = 0x01;
+ /** Hints the classifier that this may be an email. */
+ static final int HINT_FLAG_EMAIL = 0x02;
+
+ private final long mCtx;
+
+ /**
+ * Creates a new instance of SmartSelect predictor, using the provided model image,
+ * given as a file descriptor.
+ */
+ SmartSelection(int fd) {
+ mCtx = nativeNew(fd);
+ }
+
+ /**
+ * Given a string context and current selection, computes the SmartSelection suggestion.
+ *
+ * The begin and end are character indices into the context UTF8 string. selectionBegin is the
+ * character index where the selection begins, and selectionEnd is the index of one character
+ * past the selection span.
+ *
+ * The return value is an array of two ints: suggested selection beginning and end, with the
+ * same semantics as the input selectionBeginning and selectionEnd.
+ */
+ public int[] suggest(String context, int selectionBegin, int selectionEnd) {
+ return nativeSuggest(mCtx, context, selectionBegin, selectionEnd);
+ }
+
+ /**
+ * Given a string context and current selection, classifies the type of the selected text.
+ *
+ * The begin and end params are character indices in the context string.
+ *
+ * Returns an array of ClassificationResult objects with the probability
+ * scores for different collections.
+ */
+ public ClassificationResult[] classifyText(
+ String context, int selectionBegin, int selectionEnd, int hintFlags) {
+ return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd, hintFlags);
+ }
+
+ /**
+ * Frees up the allocated memory.
+ */
+ public void close() {
+ nativeClose(mCtx);
+ }
+
+ /**
+ * Returns the language of the model.
+ */
+ public static String getLanguage(int fd) {
+ return nativeGetLanguage(fd);
+ }
+
+ /**
+ * Returns the version of the model.
+ */
+ public static int getVersion(int fd) {
+ return nativeGetVersion(fd);
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native int[] nativeSuggest(
+ long context, String text, int selectionBegin, int selectionEnd);
+
+ private static native ClassificationResult[] nativeClassifyText(
+ long context, String text, int selectionBegin, int selectionEnd, int hintFlags);
+
+ private static native void nativeClose(long context);
+
+ private static native String nativeGetLanguage(int fd);
+
+ private static native int nativeGetVersion(int fd);
+
+ /** Classification result for classifyText method. */
+ static final class ClassificationResult {
+ final String mCollection;
+ /** float range: 0 - 1 */
+ final float mScore;
+
+ ClassificationResult(String collection, float score) {
+ mCollection = collection;
+ mScore = score;
+ }
+ }
+}
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
new file mode 100644
index 00000000..1849368f
--- /dev/null
+++ b/android/view/textclassifier/TextClassification.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.View.OnClickListener;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information for generating a widget to handle classified text.
+ */
+public final class TextClassification {
+
+ /**
+ * @hide
+ */
+ static final TextClassification EMPTY = new TextClassification.Builder().build();
+
+ @NonNull private final String mText;
+ @Nullable private final Drawable mIcon;
+ @Nullable private final String mLabel;
+ @Nullable private final Intent mIntent;
+ @Nullable private final OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+ private int mLogType;
+ @NonNull private final String mVersionInfo;
+
+ private TextClassification(
+ @Nullable String text,
+ @Nullable Drawable icon,
+ @Nullable String label,
+ @Nullable Intent intent,
+ @Nullable OnClickListener onClickListener,
+ @NonNull EntityConfidence<String> entityConfidence,
+ int logType,
+ @NonNull String versionInfo) {
+ mText = text;
+ mIcon = icon;
+ mLabel = label;
+ mIntent = intent;
+ mOnClickListener = onClickListener;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ mLogType = logType;
+ mVersionInfo = versionInfo;
+ }
+
+ /**
+ * Gets the classified text.
+ */
+ @Nullable
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ /**
+ * Returns an icon that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns a label that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Returns an intent that may be fired to act on the classified text.
+ */
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Returns an OnClickListener that may be triggered to act on the classified text.
+ */
+ @Nullable
+ public OnClickListener getOnClickListener() {
+ return mOnClickListener;
+ }
+
+ /**
+ * Returns the MetricsLogger subtype for the action that is performed for this result.
+ * @hide
+ */
+ public int getLogType() {
+ return mLogType;
+ }
+
+ /**
+ * Returns information about the classifier model used to generate this TextClassification.
+ * @hide
+ */
+ @NonNull
+ public String getVersionInfo() {
+ return mVersionInfo;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextClassification {"
+ + "text=%s, entities=%s, label=%s, intent=%s}",
+ mText, mEntityConfidence, mLabel, mIntent);
+ }
+
+ /**
+ * Creates an OnClickListener that starts an activity with the specified intent.
+ *
+ * @throws IllegalArgumentException if context or intent is null
+ * @hide
+ */
+ @NonNull
+ public static OnClickListener createStartActivityOnClickListener(
+ @NonNull final Context context, @NonNull final Intent intent) {
+ Preconditions.checkArgument(context != null);
+ Preconditions.checkArgument(intent != null);
+ return v -> context.startActivity(intent);
+ }
+
+ /**
+ * Builder for building {@link TextClassification} objects.
+ */
+ public static final class Builder {
+
+ @NonNull private String mText;
+ @Nullable private Drawable mIcon;
+ @Nullable private String mLabel;
+ @Nullable private Intent mIntent;
+ @Nullable private OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+ private int mLogType;
+ @NonNull private String mVersionInfo = "";
+
+ /**
+ * Sets the classified text.
+ */
+ public Builder setText(@Nullable String text) {
+ mText = text;
+ return this;
+ }
+
+ /**
+ * Sets an entity type for the classification result and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Sets an icon that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setIcon(@Nullable Drawable icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets a label that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setLabel(@Nullable String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Sets an intent that may be fired to act on the classified text.
+ */
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the MetricsLogger subtype for the action that is performed for this result.
+ * @hide
+ */
+ public Builder setLogType(int type) {
+ mLogType = type;
+ return this;
+ }
+
+ /**
+ * Sets an OnClickListener that may be triggered to act on the classified text.
+ */
+ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
+ mOnClickListener = onClickListener;
+ return this;
+ }
+
+ /**
+ * Sets information about the classifier model used to generate this TextClassification.
+ * @hide
+ */
+ Builder setVersionInfo(@NonNull String versionInfo) {
+ mVersionInfo = Preconditions.checkNotNull(versionInfo);
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link TextClassification} object.
+ */
+ public TextClassification build() {
+ return new TextClassification(
+ mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence,
+ mLogType, mVersionInfo);
+ }
+ }
+}
diff --git a/android/view/textclassifier/TextClassificationManager.java b/android/view/textclassifier/TextClassificationManager.java
new file mode 100644
index 00000000..d7b07761
--- /dev/null
+++ b/android/view/textclassifier/TextClassificationManager.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Interface to the text classification service.
+ */
+@SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
+public final class TextClassificationManager {
+
+ private final Object mTextClassifierLock = new Object();
+
+ private final Context mContext;
+ private TextClassifier mTextClassifier;
+
+ /** @hide */
+ public TextClassificationManager(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Returns the text classifier.
+ */
+ public TextClassifier getTextClassifier() {
+ synchronized (mTextClassifierLock) {
+ if (mTextClassifier == null) {
+ mTextClassifier = new TextClassifierImpl(mContext);
+ }
+ return mTextClassifier;
+ }
+ }
+
+ /**
+ * Sets the text classifier.
+ * Set to null to use the system default text classifier.
+ * Set to {@link TextClassifier#NO_OP} to disable text classifier features.
+ */
+ public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+ synchronized (mTextClassifierLock) {
+ mTextClassifier = textClassifier;
+ }
+ }
+}
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
new file mode 100644
index 00000000..bb1e693f
--- /dev/null
+++ b/android/view/textclassifier/TextClassifier.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.annotation.WorkerThread;
+import android.os.LocaleList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for providing text classification related features.
+ *
+ * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
+ * avoid calling them on the UI thread.
+ */
+public interface TextClassifier {
+
+ /** @hide */
+ String DEFAULT_LOG_TAG = "TextClassifierImpl";
+
+ /** @hide */
+ String TYPE_UNKNOWN = ""; // TODO: Make this public API.
+ String TYPE_OTHER = "other";
+ String TYPE_EMAIL = "email";
+ String TYPE_PHONE = "phone";
+ String TYPE_ADDRESS = "address";
+ String TYPE_URL = "url";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ TYPE_UNKNOWN, TYPE_OTHER, TYPE_EMAIL, TYPE_PHONE, TYPE_ADDRESS, TYPE_URL
+ })
+ @interface EntityType {}
+
+ /**
+ * No-op TextClassifier.
+ * This may be used to turn off TextClassifier features.
+ */
+ TextClassifier NO_OP = new TextClassifier() {
+
+ @Override
+ public TextSelection suggestSelection(
+ CharSequence text,
+ int selectionStartIndex,
+ int selectionEndIndex,
+ LocaleList defaultLocales) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
+
+ @Override
+ public TextClassification classifyText(
+ CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales) {
+ return TextClassification.EMPTY;
+ }
+ };
+
+ /**
+ * Returns suggested text selection indices, recognized types and their associated confidence
+ * scores. The selections are ordered from highest to lowest scoring.
+ *
+ * @param text text providing context for the selected text (which is specified
+ * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+ * @param selectionStartIndex start index of the selected part of text
+ * @param selectionEndIndex end index of the selected part of text
+ * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty locale
+ * list in which case the classifier will decide whether to use no locale information, use
+ * a default locale, or use the system default.
+ *
+ * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+ * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
+ */
+ @WorkerThread
+ @NonNull
+ TextSelection suggestSelection(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int selectionStartIndex,
+ @IntRange(from = 0) int selectionEndIndex,
+ @Nullable LocaleList defaultLocales);
+
+ /**
+ * Classifies the specified text and returns a {@link TextClassification} object that can be
+ * used to generate a widget for handling the classified text.
+ *
+ * @param text text providing context for the text to classify (which is specified
+ * by the sub sequence starting at startIndex and ending at endIndex)
+ * @param startIndex start index of the text to classify
+ * @param endIndex end index of the text to classify
+ * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty locale
+ * list in which case the classifier will decide whether to use no locale information, use
+ * a default locale, or use the system default.
+ *
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or not greater than startIndex
+ */
+ @WorkerThread
+ @NonNull
+ TextClassification classifyText(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int startIndex,
+ @IntRange(from = 0) int endIndex,
+ @Nullable LocaleList defaultLocales);
+
+ /**
+ * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+ * information.
+ *
+ * @param text the text to generate annotations for
+ * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
+ * specified. Subclasses of this interface may specify additional linkMasks
+ * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty locale
+ * list in which case the classifier will decide whether to use no locale information, use
+ * a default locale, or use the system default.
+ *
+ * @throws IllegalArgumentException if text is null
+ * @hide
+ */
+ @WorkerThread
+ default LinksInfo getLinks(
+ @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
+ return LinksInfo.NO_OP;
+ }
+
+ /**
+ * Logs a TextClassifier event.
+ *
+ * @param source the text classifier used to generate this event
+ * @param event the text classifier related event
+ * @hide
+ */
+ @WorkerThread
+ default void logEvent(String source, String event) {}
+}
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
new file mode 100644
index 00000000..7e93b78c
--- /dev/null
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.icu.text.BreakIterator;
+import android.net.Uri;
+import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
+import android.provider.Browser;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.WordIterator;
+import android.text.style.ClickableSpan;
+import android.text.util.Linkify;
+import android.util.Log;
+import android.util.Patterns;
+import android.view.View;
+import android.widget.TextViewMetrics;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Default implementation of the {@link TextClassifier} interface.
+ *
+ * <p>This class uses machine learning to recognize entities in text.
+ * Unless otherwise stated, methods of this class are blocking operations and should most
+ * likely not be called on the UI thread.
+ *
+ * @hide
+ */
+final class TextClassifierImpl implements TextClassifier {
+
+ private static final String LOG_TAG = DEFAULT_LOG_TAG;
+ private static final String MODEL_DIR = "/etc/textclassifier/";
+ private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model";
+ private static final String UPDATED_MODEL_FILE_PATH =
+ "/data/misc/textclassifier/textclassifier.smartselection.model";
+
+ private final Context mContext;
+
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+ private final Object mSmartSelectionLock = new Object();
+ @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
+ private Map<Locale, String> mModelFilePaths;
+ @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
+ private Locale mLocale;
+ @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
+ private int mVersion;
+ @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
+ private SmartSelection mSmartSelection;
+
+ TextClassifierImpl(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ @Override
+ public TextSelection suggestSelection(
+ @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
+ @Nullable LocaleList defaultLocales) {
+ validateInput(text, selectionStartIndex, selectionEndIndex);
+ try {
+ if (text.length() > 0) {
+ final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+ final String string = text.toString();
+ final int[] startEnd = smartSelection.suggest(
+ string, selectionStartIndex, selectionEndIndex);
+ final int start = startEnd[0];
+ final int end = startEnd[1];
+ if (start <= end
+ && start >= 0 && end <= string.length()
+ && start <= selectionStartIndex && end >= selectionEndIndex) {
+ final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
+ final SmartSelection.ClassificationResult[] results =
+ smartSelection.classifyText(
+ string, start, end,
+ getHintFlags(string, start, end));
+ final int size = results.length;
+ for (int i = 0; i < size; i++) {
+ tsBuilder.setEntityType(results[i].mCollection, results[i].mScore);
+ }
+ return tsBuilder
+ .setLogSource(LOG_TAG)
+ .setVersionInfo(getVersionInfo())
+ .build();
+ } else {
+ // We can not trust the result. Log the issue and ignore the result.
+ Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result.");
+ }
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG,
+ "Error suggesting selection for text. No changes to selection suggested.",
+ t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ return TextClassifier.NO_OP.suggestSelection(
+ text, selectionStartIndex, selectionEndIndex, defaultLocales);
+ }
+
+ @Override
+ public TextClassification classifyText(
+ @NonNull CharSequence text, int startIndex, int endIndex,
+ @Nullable LocaleList defaultLocales) {
+ validateInput(text, startIndex, endIndex);
+ try {
+ if (text.length() > 0) {
+ final String string = text.toString();
+ SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales)
+ .classifyText(string, startIndex, endIndex,
+ getHintFlags(string, startIndex, endIndex));
+ if (results.length > 0) {
+ final TextClassification classificationResult =
+ createClassificationResult(
+ results, string.subSequence(startIndex, endIndex));
+ return classificationResult;
+ }
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error getting assist info.", t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ return TextClassifier.NO_OP.classifyText(
+ text, startIndex, endIndex, defaultLocales);
+ }
+
+ @Override
+ public LinksInfo getLinks(
+ @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
+ Preconditions.checkArgument(text != null);
+ try {
+ return LinksInfoFactory.create(
+ mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error getting links info.", t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
+ return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
+ }
+
+ @Override
+ public void logEvent(String source, String event) {
+ if (LOG_TAG.equals(source)) {
+ mMetricsLogger.count(event, 1);
+ }
+ }
+
+ private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
+ synchronized (mSmartSelectionLock) {
+ localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
+ final Locale locale = findBestSupportedLocaleLocked(localeList);
+ if (locale == null) {
+ throw new FileNotFoundException("No file for null locale");
+ }
+ if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
+ destroySmartSelectionIfExistsLocked();
+ final ParcelFileDescriptor fd = getFdLocked(locale);
+ mSmartSelection = new SmartSelection(fd.getFd());
+ closeAndLogError(fd);
+ mLocale = locale;
+ }
+ return mSmartSelection;
+ }
+ }
+
+ @NonNull
+ private String getVersionInfo() {
+ synchronized (mSmartSelectionLock) {
+ if (mLocale != null) {
+ return String.format("%s_v%d", mLocale.toLanguageTag(), mVersion);
+ }
+ return "";
+ }
+ }
+
+ @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
+ private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
+ ParcelFileDescriptor updateFd;
+ try {
+ updateFd = ParcelFileDescriptor.open(
+ new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ updateFd = null;
+ }
+ ParcelFileDescriptor factoryFd;
+ try {
+ final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
+ if (factoryModelFilePath != null) {
+ factoryFd = ParcelFileDescriptor.open(
+ new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
+ } else {
+ factoryFd = null;
+ }
+ } catch (FileNotFoundException e) {
+ factoryFd = null;
+ }
+
+ if (updateFd == null) {
+ if (factoryFd != null) {
+ return factoryFd;
+ } else {
+ throw new FileNotFoundException(
+ String.format("No model file found for %s", locale));
+ }
+ }
+
+ final int updateFdInt = updateFd.getFd();
+ final boolean localeMatches = Objects.equals(
+ locale.getLanguage().trim().toLowerCase(),
+ SmartSelection.getLanguage(updateFdInt).trim().toLowerCase());
+ if (factoryFd == null) {
+ if (localeMatches) {
+ return updateFd;
+ } else {
+ closeAndLogError(updateFd);
+ throw new FileNotFoundException(
+ String.format("No model file found for %s", locale));
+ }
+ }
+
+ if (!localeMatches) {
+ closeAndLogError(updateFd);
+ return factoryFd;
+ }
+
+ final int updateVersion = SmartSelection.getVersion(updateFdInt);
+ final int factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
+ if (updateVersion > factoryVersion) {
+ closeAndLogError(factoryFd);
+ mVersion = updateVersion;
+ return updateFd;
+ } else {
+ closeAndLogError(updateFd);
+ mVersion = factoryVersion;
+ return factoryFd;
+ }
+ }
+
+ @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
+ private void destroySmartSelectionIfExistsLocked() {
+ if (mSmartSelection != null) {
+ mSmartSelection.close();
+ mSmartSelection = null;
+ }
+ }
+
+ @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
+ @Nullable
+ private Locale findBestSupportedLocaleLocked(LocaleList localeList) {
+ // Specified localeList takes priority over the system default, so it is listed first.
+ final String languages = localeList.isEmpty()
+ ? LocaleList.getDefault().toLanguageTags()
+ : localeList.toLanguageTags() + "," + LocaleList.getDefault().toLanguageTags();
+ final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(languages);
+
+ final List<Locale> supportedLocales =
+ new ArrayList<>(getFactoryModelFilePathsLocked().keySet());
+ final Locale updatedModelLocale = getUpdatedModelLocale();
+ if (updatedModelLocale != null) {
+ supportedLocales.add(updatedModelLocale);
+ }
+ return Locale.lookup(languageRangeList, supportedLocales);
+ }
+
+ @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
+ private Map<Locale, String> getFactoryModelFilePathsLocked() {
+ if (mModelFilePaths == null) {
+ final Map<Locale, String> modelFilePaths = new HashMap<>();
+ final File modelsDir = new File(MODEL_DIR);
+ if (modelsDir.exists() && modelsDir.isDirectory()) {
+ final File[] models = modelsDir.listFiles();
+ final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX);
+ final int size = models.length;
+ for (int i = 0; i < size; i++) {
+ final File modelFile = models[i];
+ final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName());
+ if (matcher.matches() && modelFile.isFile()) {
+ final String language = matcher.group(1);
+ final Locale locale = Locale.forLanguageTag(language);
+ modelFilePaths.put(locale, modelFile.getAbsolutePath());
+ }
+ }
+ }
+ mModelFilePaths = modelFilePaths;
+ }
+ return mModelFilePaths;
+ }
+
+ @Nullable
+ private Locale getUpdatedModelLocale() {
+ try {
+ final ParcelFileDescriptor updateFd = ParcelFileDescriptor.open(
+ new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
+ final Locale locale = Locale.forLanguageTag(
+ SmartSelection.getLanguage(updateFd.getFd()));
+ closeAndLogError(updateFd);
+ return locale;
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ private TextClassification createClassificationResult(
+ SmartSelection.ClassificationResult[] classifications, CharSequence text) {
+ final TextClassification.Builder builder = new TextClassification.Builder()
+ .setText(text.toString());
+
+ final int size = classifications.length;
+ for (int i = 0; i < size; i++) {
+ builder.setEntityType(classifications[i].mCollection, classifications[i].mScore);
+ }
+
+ final String type = getHighestScoringType(classifications);
+ builder.setLogType(IntentFactory.getLogType(type));
+
+ final Intent intent = IntentFactory.create(mContext, type, text.toString());
+ final PackageManager pm;
+ final ResolveInfo resolveInfo;
+ if (intent != null) {
+ pm = mContext.getPackageManager();
+ resolveInfo = pm.resolveActivity(intent, 0);
+ } else {
+ pm = null;
+ resolveInfo = null;
+ }
+ if (resolveInfo != null && resolveInfo.activityInfo != null) {
+ builder.setIntent(intent)
+ .setOnClickListener(TextClassification.createStartActivityOnClickListener(
+ mContext, intent));
+
+ final String packageName = resolveInfo.activityInfo.packageName;
+ if ("android".equals(packageName)) {
+ // Requires the chooser to find an activity to handle the intent.
+ builder.setLabel(IntentFactory.getLabel(mContext, type));
+ } else {
+ // A default activity will handle the intent.
+ intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
+ Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+ if (icon == null) {
+ icon = resolveInfo.loadIcon(pm);
+ }
+ builder.setIcon(icon);
+ CharSequence label = resolveInfo.activityInfo.loadLabel(pm);
+ if (label == null) {
+ label = resolveInfo.loadLabel(pm);
+ }
+ builder.setLabel(label != null ? label.toString() : null);
+ }
+ }
+ return builder.setVersionInfo(getVersionInfo()).build();
+ }
+
+ private static int getHintFlags(CharSequence text, int start, int end) {
+ int flag = 0;
+ final CharSequence subText = text.subSequence(start, end);
+ if (Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(subText).matches()) {
+ flag |= SmartSelection.HINT_FLAG_EMAIL;
+ }
+ if (Patterns.AUTOLINK_WEB_URL.matcher(subText).matches()
+ && Linkify.sUrlMatchFilter.acceptMatch(text, start, end)) {
+ flag |= SmartSelection.HINT_FLAG_URL;
+ }
+ return flag;
+ }
+
+ private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) {
+ if (types.length < 1) {
+ return "";
+ }
+
+ String type = types[0].mCollection;
+ float highestScore = types[0].mScore;
+ final int size = types.length;
+ for (int i = 1; i < size; i++) {
+ if (types[i].mScore > highestScore) {
+ type = types[i].mCollection;
+ highestScore = types[i].mScore;
+ }
+ }
+ return type;
+ }
+
+ /**
+ * Closes the ParcelFileDescriptor and logs any errors that occur.
+ */
+ private static void closeAndLogError(ParcelFileDescriptor fd) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Error closing file.", e);
+ }
+ }
+
+ /**
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or is not greater than startIndex
+ */
+ private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) {
+ Preconditions.checkArgument(text != null);
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex <= text.length());
+ Preconditions.checkArgument(endIndex > startIndex);
+ }
+
+ /**
+ * Detects and creates links for specified text.
+ */
+ private static final class LinksInfoFactory {
+
+ private LinksInfoFactory() {}
+
+ public static LinksInfo create(
+ Context context, SmartSelection smartSelection, String text, int linkMask) {
+ final WordIterator wordIterator = new WordIterator();
+ wordIterator.setCharSequence(text, 0, text.length());
+ final List<SpanSpec> spans = new ArrayList<>();
+ int start = 0;
+ int end;
+ while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
+ final String token = text.substring(start, end);
+ if (TextUtils.isEmpty(token)) {
+ continue;
+ }
+
+ final int[] selection = smartSelection.suggest(text, start, end);
+ final int selectionStart = selection[0];
+ final int selectionEnd = selection[1];
+ if (selectionStart >= 0 && selectionEnd <= text.length()
+ && selectionStart <= selectionEnd) {
+ final SmartSelection.ClassificationResult[] results =
+ smartSelection.classifyText(
+ text, selectionStart, selectionEnd,
+ getHintFlags(text, selectionStart, selectionEnd));
+ if (results.length > 0) {
+ final String type = getHighestScoringType(results);
+ if (matches(type, linkMask)) {
+ final Intent intent = IntentFactory.create(
+ context, type, text.substring(selectionStart, selectionEnd));
+ if (hasActivityHandler(context, intent)) {
+ final ClickableSpan span = createSpan(context, intent);
+ spans.add(new SpanSpec(selectionStart, selectionEnd, span));
+ }
+ }
+ }
+ }
+ start = end;
+ }
+ return new LinksInfoImpl(text, avoidOverlaps(spans, text));
+ }
+
+ /**
+ * Returns true if the classification type matches the specified linkMask.
+ */
+ private static boolean matches(String type, int linkMask) {
+ type = type.trim().toLowerCase(Locale.ENGLISH);
+ if ((linkMask & Linkify.PHONE_NUMBERS) != 0
+ && TextClassifier.TYPE_PHONE.equals(type)) {
+ return true;
+ }
+ if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
+ && TextClassifier.TYPE_EMAIL.equals(type)) {
+ return true;
+ }
+ if ((linkMask & Linkify.MAP_ADDRESSES) != 0
+ && TextClassifier.TYPE_ADDRESS.equals(type)) {
+ return true;
+ }
+ if ((linkMask & Linkify.WEB_URLS) != 0
+ && TextClassifier.TYPE_URL.equals(type)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Trim the number of spans so that no two spans overlap.
+ *
+ * This algorithm first ensures that there is only one span per start index, then it
+ * makes sure that no two spans overlap.
+ */
+ private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
+ Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
+ // Group spans by start index. Take the longest span.
+ final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters.
+ final int size = spans.size();
+ for (int i = 0; i < size; i++) {
+ final SpanSpec span = spans.get(i);
+ final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
+ if (rep == null || rep.mEnd < span.mEnd) {
+ reps.put(span.mStart, span);
+ }
+ }
+ // Avoid span intersections. Take the longer span.
+ final LinkedList<SpanSpec> result = new LinkedList<>();
+ for (SpanSpec rep : reps.values()) {
+ if (result.isEmpty()) {
+ result.add(rep);
+ continue;
+ }
+
+ final SpanSpec last = result.getLast();
+ if (rep.mStart < last.mEnd) {
+ // Spans intersect. Use the one with characters.
+ if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
+ result.set(result.size() - 1, rep);
+ }
+ } else {
+ result.add(rep);
+ }
+ }
+ return result;
+ }
+
+ private static ClickableSpan createSpan(final Context context, final Intent intent) {
+ return new ClickableSpan() {
+ // TODO: Style this span.
+ @Override
+ public void onClick(View widget) {
+ context.startActivity(intent);
+ }
+ };
+ }
+
+ private static boolean hasActivityHandler(Context context, @Nullable Intent intent) {
+ if (intent == null) {
+ return false;
+ }
+ final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
+ return resolveInfo != null && resolveInfo.activityInfo != null;
+ }
+
+ /**
+ * Implementation of LinksInfo that adds ClickableSpans to the specified text.
+ */
+ private static final class LinksInfoImpl implements LinksInfo {
+
+ private final CharSequence mOriginalText;
+ private final List<SpanSpec> mSpans;
+
+ LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
+ mOriginalText = originalText;
+ mSpans = spans;
+ }
+
+ @Override
+ public boolean apply(@NonNull CharSequence text) {
+ Preconditions.checkArgument(text != null);
+ if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
+ Spannable spannable = (Spannable) text;
+ final int size = mSpans.size();
+ for (int i = 0; i < size; i++) {
+ final SpanSpec span = mSpans.get(i);
+ spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Span plus its start and end index.
+ */
+ private static final class SpanSpec {
+
+ private final int mStart;
+ private final int mEnd;
+ private final ClickableSpan mSpan;
+
+ SpanSpec(int start, int end, ClickableSpan span) {
+ mStart = start;
+ mEnd = end;
+ mSpan = span;
+ }
+ }
+ }
+
+ /**
+ * Creates intents based on the classification type.
+ */
+ private static final class IntentFactory {
+
+ private IntentFactory() {}
+
+ @Nullable
+ public static Intent create(Context context, String type, String text) {
+ type = type.trim().toLowerCase(Locale.ENGLISH);
+ text = text.trim();
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ return new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("mailto:%s", text)));
+ case TextClassifier.TYPE_PHONE:
+ return new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text)));
+ case TextClassifier.TYPE_ADDRESS:
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+ case TextClassifier.TYPE_URL:
+ final String httpPrefix = "http://";
+ final String httpsPrefix = "https://";
+ if (text.toLowerCase().startsWith(httpPrefix)) {
+ text = httpPrefix + text.substring(httpPrefix.length());
+ } else if (text.toLowerCase().startsWith(httpsPrefix)) {
+ text = httpsPrefix + text.substring(httpsPrefix.length());
+ } else {
+ text = httpPrefix + text;
+ }
+ return new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+ .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ default:
+ return null;
+ }
+ }
+
+ @Nullable
+ public static String getLabel(Context context, String type) {
+ type = type.trim().toLowerCase(Locale.ENGLISH);
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ return context.getString(com.android.internal.R.string.email);
+ case TextClassifier.TYPE_PHONE:
+ return context.getString(com.android.internal.R.string.dial);
+ case TextClassifier.TYPE_ADDRESS:
+ return context.getString(com.android.internal.R.string.map);
+ case TextClassifier.TYPE_URL:
+ return context.getString(com.android.internal.R.string.browse);
+ default:
+ return null;
+ }
+ }
+
+ @Nullable
+ public static int getLogType(String type) {
+ type = type.trim().toLowerCase(Locale.ENGLISH);
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_EMAIL;
+ case TextClassifier.TYPE_PHONE:
+ return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_PHONE;
+ case TextClassifier.TYPE_ADDRESS:
+ return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_ADDRESS;
+ case TextClassifier.TYPE_URL:
+ return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_URL;
+ default:
+ return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_OTHER;
+ }
+ }
+ }
+}
diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java
new file mode 100644
index 00000000..11ebe835
--- /dev/null
+++ b/android/view/textclassifier/TextSelection.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 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.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information about where text selection should be.
+ */
+public final class TextSelection {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+ @NonNull private final String mLogSource;
+ @NonNull private final String mVersionInfo;
+
+ private TextSelection(
+ int startIndex, int endIndex, @NonNull EntityConfidence<String> entityConfidence,
+ @NonNull String logSource, @NonNull String versionInfo) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ mLogSource = logSource;
+ mVersionInfo = versionInfo;
+ }
+
+ /**
+ * Returns the start index of the text selection.
+ */
+ public int getSelectionStartIndex() {
+ return mStartIndex;
+ }
+
+ /**
+ * Returns the end index of the text selection.
+ */
+ public int getSelectionEndIndex() {
+ return mEndIndex;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ /**
+ * Returns a tag for the source classifier used to generate this result.
+ * @hide
+ */
+ @NonNull
+ public String getSourceClassifier() {
+ return mLogSource;
+ }
+
+ /**
+ * Returns information about the classifier model used to generate this TextSelection.
+ * @hide
+ */
+ @NonNull
+ public String getVersionInfo() {
+ return mVersionInfo;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextSelection {%d, %d, %s}",
+ mStartIndex, mEndIndex, mEntityConfidence);
+ }
+
+ /**
+ * Builder used to build {@link TextSelection} objects.
+ */
+ public static final class Builder {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+ @NonNull private String mLogSource = "";
+ @NonNull private String mVersionInfo = "";
+
+ /**
+ * Creates a builder used to build {@link TextSelection} objects.
+ *
+ * @param startIndex the start index of the text selection.
+ * @param endIndex the end index of the text selection. Must be greater than startIndex
+ */
+ public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex > startIndex);
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Sets a tag for the source classifier used to generate this result.
+ * @hide
+ */
+ Builder setLogSource(@NonNull String logSource) {
+ mLogSource = Preconditions.checkNotNull(logSource);
+ return this;
+ }
+
+ /**
+ * Sets information about the classifier model used to generate this TextSelection.
+ * @hide
+ */
+ Builder setVersionInfo(@NonNull String versionInfo) {
+ mVersionInfo = Preconditions.checkNotNull(versionInfo);
+ return this;
+ }
+
+ /**
+ * Builds and returns {@link TextSelection} object.
+ */
+ public TextSelection build() {
+ return new TextSelection(
+ mStartIndex, mEndIndex, mEntityConfidence, mLogSource, mVersionInfo);
+ }
+ }
+}
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
new file mode 100644
index 00000000..77aea234
--- /dev/null
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2017 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.textclassifier.logging;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.metrics.LogMaker;
+import android.util.Log;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextSelection;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * A selection event tracker.
+ * @hide
+ */
+//TODO: Do not allow any crashes from this class.
+public final class SmartSelectionEventTracker {
+
+ private static final String LOG_TAG = "SmartSelectionEventTracker";
+ private static final boolean DEBUG_LOG_ENABLED = true;
+
+ private static final int START_EVENT_DELTA = MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS;
+ private static final int PREV_EVENT_DELTA = MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS;
+ private static final int ENTITY_TYPE = MetricsEvent.NOTIFICATION_TAG;
+ private static final int INDEX = MetricsEvent.NOTIFICATION_SHADE_INDEX;
+ private static final int TAG = MetricsEvent.FIELD_CLASS_NAME;
+ private static final int SMART_INDICES = MetricsEvent.FIELD_GESTURE_LENGTH;
+ private static final int EVENT_INDICES = MetricsEvent.FIELD_CONTEXT;
+ private static final int SESSION_ID = MetricsEvent.FIELD_INSTANT_APP_LAUNCH_TOKEN;
+
+ private static final String ZERO = "0";
+ private static final String TEXTVIEW = "textview";
+ private static final String EDITTEXT = "edittext";
+ private static final String WEBVIEW = "webview";
+ private static final String EDIT_WEBVIEW = "edit-webview";
+ private static final String UNKNOWN = "unknown";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW,
+ WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW})
+ public @interface WidgetType {
+ int UNSPECIFIED = 0;
+ int TEXTVIEW = 1;
+ int WEBVIEW = 2;
+ int EDITTEXT = 3;
+ int EDIT_WEBVIEW = 4;
+ }
+
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final int mWidgetType;
+ private final Context mContext;
+
+ @Nullable private String mSessionId;
+ private final int[] mSmartIndices = new int[2];
+ private final int[] mPrevIndices = new int[2];
+ private int mOrigStart;
+ private int mIndex;
+ private long mSessionStartTime;
+ private long mLastEventTime;
+ private boolean mSmartSelectionTriggered;
+
+ public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
+ mWidgetType = widgetType;
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Logs a selection event.
+ *
+ * @param event the selection event
+ */
+ public void logEvent(@NonNull SelectionEvent event) {
+ Preconditions.checkNotNull(event);
+
+ if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null) {
+ Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
+ return;
+ }
+
+ final long now = System.currentTimeMillis();
+ switch (event.mEventType) {
+ case SelectionEvent.EventType.SELECTION_STARTED:
+ mSessionId = startNewSession();
+ Preconditions.checkArgument(event.mEnd == event.mStart + 1);
+ mOrigStart = event.mStart;
+ mSessionStartTime = now;
+ break;
+ case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
+ case SelectionEvent.EventType.SMART_SELECTION_MULTI:
+ mSmartSelectionTriggered = true;
+ mSmartIndices[0] = event.mStart;
+ mSmartIndices[1] = event.mEnd;
+ break;
+ case SelectionEvent.EventType.SELECTION_MODIFIED: // fall through
+ case SelectionEvent.EventType.AUTO_SELECTION:
+ if (mPrevIndices[0] == event.mStart && mPrevIndices[1] == event.mEnd) {
+ // Selection did not change. Ignore event.
+ return;
+ }
+ }
+ writeEvent(event, now);
+
+ if (event.isTerminal()) {
+ endSession();
+ }
+ }
+
+ private void writeEvent(SelectionEvent event, long now) {
+ final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+ .setType(getLogType(event))
+ .setSubtype(event.mEventType)
+ .setPackageName(mContext.getPackageName())
+ .setTimestamp(now)
+ .addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
+ .addTaggedData(PREV_EVENT_DELTA, now - mLastEventTime)
+ .addTaggedData(ENTITY_TYPE, event.mEntityType)
+ .addTaggedData(INDEX, mIndex)
+ .addTaggedData(TAG, getTag(event))
+ .addTaggedData(SMART_INDICES, getSmartDelta())
+ .addTaggedData(EVENT_INDICES, getEventDelta(event))
+ .addTaggedData(SESSION_ID, mSessionId);
+ mMetricsLogger.write(log);
+ debugLog(log);
+ mLastEventTime = now;
+ mPrevIndices[0] = event.mStart;
+ mPrevIndices[1] = event.mEnd;
+ mIndex++;
+ }
+
+ private String startNewSession() {
+ endSession();
+ mSessionId = createSessionId();
+ return mSessionId;
+ }
+
+ private void endSession() {
+ // Reset fields.
+ mOrigStart = 0;
+ mSmartIndices[0] = mSmartIndices[1] = 0;
+ mPrevIndices[0] = mPrevIndices[1] = 0;
+ mIndex = 0;
+ mSessionStartTime = 0;
+ mLastEventTime = 0;
+ mSmartSelectionTriggered = false;
+ mSessionId = null;
+ }
+
+ private int getLogType(SelectionEvent event) {
+ switch (event.mEventType) {
+ case SelectionEvent.EventType.SELECTION_STARTED: // fall through
+ case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
+ case SelectionEvent.EventType.SMART_SELECTION_MULTI: // fall through
+ case SelectionEvent.EventType.AUTO_SELECTION:
+ return MetricsEvent.TYPE_OPEN;
+ case SelectionEvent.ActionType.ABANDON:
+ return MetricsEvent.TYPE_CLOSE;
+ }
+ if (event.isActionType()) {
+ if (event.isTerminal() && mSmartSelectionTriggered) {
+ if (matchesSmartSelectionBounds(event)) {
+ // Smart selection accepted.
+ return MetricsEvent.TYPE_SUCCESS;
+ } else if (containsOriginalSelection(event)) {
+ // Smart selection rejected.
+ return MetricsEvent.TYPE_FAILURE;
+ }
+ // User changed the original selection entirely.
+ }
+ return MetricsEvent.TYPE_ACTION;
+ } else {
+ return MetricsEvent.TYPE_UPDATE;
+ }
+ }
+
+ private boolean matchesSmartSelectionBounds(SelectionEvent event) {
+ return event.mStart == mSmartIndices[0] && event.mEnd == mSmartIndices[1];
+ }
+
+ private boolean containsOriginalSelection(SelectionEvent event) {
+ return event.mStart <= mOrigStart && event.mEnd > mOrigStart;
+ }
+
+ private int getSmartDelta() {
+ if (mSmartSelectionTriggered) {
+ return (clamp(mSmartIndices[0] - mOrigStart) << 16)
+ | (clamp(mSmartIndices[1] - mOrigStart) & 0xffff);
+ }
+ // If no smart selection, return start selection indices (i.e. [0, 1])
+ return /* (0 << 16) | */ (1 & 0xffff);
+ }
+
+ private int getEventDelta(SelectionEvent event) {
+ return (clamp(event.mStart - mOrigStart) << 16)
+ | (clamp(event.mEnd - mOrigStart) & 0xffff);
+ }
+
+ private String getTag(SelectionEvent event) {
+ final String widgetType;
+ switch (mWidgetType) {
+ case WidgetType.TEXTVIEW:
+ widgetType = TEXTVIEW;
+ break;
+ case WidgetType.WEBVIEW:
+ widgetType = WEBVIEW;
+ break;
+ case WidgetType.EDITTEXT:
+ widgetType = EDITTEXT;
+ break;
+ case WidgetType.EDIT_WEBVIEW:
+ widgetType = EDIT_WEBVIEW;
+ break;
+ default:
+ widgetType = UNKNOWN;
+ }
+ final String version = Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
+ return String.format("%s/%s", widgetType, version);
+ }
+
+ private static String createSessionId() {
+ return UUID.randomUUID().toString();
+ }
+
+ private static int clamp(int val) {
+ return Math.max(Math.min(val, Short.MAX_VALUE), Short.MIN_VALUE);
+ }
+
+ private static void debugLog(LogMaker log) {
+ if (!DEBUG_LOG_ENABLED) return;
+
+ final String tag = Objects.toString(log.getTaggedData(TAG), "tag");
+ final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
+
+ final String event;
+ switch (log.getSubtype()) {
+ case SelectionEvent.ActionType.OVERTYPE:
+ event = "OVERTYPE";
+ break;
+ case SelectionEvent.ActionType.COPY:
+ event = "COPY";
+ break;
+ case SelectionEvent.ActionType.PASTE:
+ event = "PASTE";
+ break;
+ case SelectionEvent.ActionType.CUT:
+ event = "CUT";
+ break;
+ case SelectionEvent.ActionType.SHARE:
+ event = "SHARE";
+ break;
+ case SelectionEvent.ActionType.SMART_SHARE:
+ event = "SMART_SHARE";
+ break;
+ case SelectionEvent.ActionType.DRAG:
+ event = "DRAG";
+ break;
+ case SelectionEvent.ActionType.ABANDON:
+ event = "ABANDON";
+ break;
+ case SelectionEvent.ActionType.OTHER:
+ event = "OTHER";
+ break;
+ case SelectionEvent.ActionType.SELECT_ALL:
+ event = "SELECT_ALL";
+ break;
+ case SelectionEvent.ActionType.RESET:
+ event = "RESET";
+ break;
+ case SelectionEvent.EventType.SELECTION_STARTED:
+ String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
+ sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
+ Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
+ event = "SELECTION_STARTED";
+ break;
+ case SelectionEvent.EventType.SELECTION_MODIFIED:
+ event = "SELECTION_MODIFIED";
+ break;
+ case SelectionEvent.EventType.SMART_SELECTION_SINGLE:
+ event = "SMART_SELECTION_SINGLE";
+ break;
+ case SelectionEvent.EventType.SMART_SELECTION_MULTI:
+ event = "SMART_SELECTION_MULTI";
+ break;
+ case SelectionEvent.EventType.AUTO_SELECTION:
+ event = "AUTO_SELECTION";
+ break;
+ default:
+ event = "UNKNOWN";
+ }
+
+ final int smartIndices = Integer.parseInt(
+ Objects.toString(log.getTaggedData(SMART_INDICES), ZERO));
+ final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16);
+ final int smartEnd = (short) (smartIndices & 0xffff);
+
+ final int eventIndices = Integer.parseInt(
+ Objects.toString(log.getTaggedData(EVENT_INDICES), ZERO));
+ final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16);
+ final int eventEnd = (short) (eventIndices & 0xffff);
+
+ final String entity = Objects.toString(
+ log.getTaggedData(ENTITY_TYPE), TextClassifier.TYPE_UNKNOWN);
+
+ Log.d(LOG_TAG, String.format("%2d: %s, context=%d,%d - old=%d,%d [%s] (%s)",
+ index, event, eventStart, eventEnd, smartStart, smartEnd, entity, tag));
+ }
+
+ /**
+ * A selection event.
+ * Specify index parameters as word token indices.
+ */
+ public static final class SelectionEvent {
+
+ /**
+ * Use this to specify an indeterminate positive index.
+ */
+ public static final int OUT_OF_BOUNDS = Short.MAX_VALUE;
+
+ /**
+ * Use this to specify an indeterminate negative index.
+ */
+ public static final int OUT_OF_BOUNDS_NEGATIVE = Short.MIN_VALUE;
+
+ private static final String NO_VERSION_TAG = "";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT,
+ ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON,
+ ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET})
+ public @interface ActionType {
+ /** User typed over the selection. */
+ int OVERTYPE = 100;
+ /** User copied the selection. */
+ int COPY = 101;
+ /** User pasted over the selection. */
+ int PASTE = 102;
+ /** User cut the selection. */
+ int CUT = 103;
+ /** User shared the selection. */
+ int SHARE = 104;
+ /** User clicked the textAssist menu item. */
+ int SMART_SHARE = 105;
+ /** User dragged+dropped the selection. */
+ int DRAG = 106;
+ /** User abandoned the selection. */
+ int ABANDON = 107;
+ /** User performed an action on the selection. */
+ int OTHER = 108;
+
+ /* Non-terminal actions. */
+ /** User activated Select All */
+ int SELECT_ALL = 200;
+ /** User reset the smart selection. */
+ int RESET = 201;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT,
+ ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON,
+ ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET,
+ EventType.SELECTION_STARTED, EventType.SELECTION_MODIFIED,
+ EventType.SMART_SELECTION_SINGLE, EventType.SMART_SELECTION_MULTI,
+ EventType.AUTO_SELECTION})
+ private @interface EventType {
+ /** User started a new selection. */
+ int SELECTION_STARTED = 1;
+ /** User modified an existing selection. */
+ int SELECTION_MODIFIED = 2;
+ /** Smart selection triggered for a single token (word). */
+ int SMART_SELECTION_SINGLE = 3;
+ /** Smart selection triggered spanning multiple tokens (words). */
+ int SMART_SELECTION_MULTI = 4;
+ /** Something else other than User or the default TextClassifier triggered a selection. */
+ int AUTO_SELECTION = 5;
+ }
+
+ private final int mStart;
+ private final int mEnd;
+ private @EventType int mEventType;
+ private final @TextClassifier.EntityType String mEntityType;
+ private final String mVersionTag;
+
+ private SelectionEvent(
+ int start, int end, int eventType,
+ @TextClassifier.EntityType String entityType, String versionTag) {
+ Preconditions.checkArgument(end >= start, "end cannot be less than start");
+ mStart = start;
+ mEnd = end;
+ mEventType = eventType;
+ mEntityType = Preconditions.checkNotNull(entityType);
+ mVersionTag = Preconditions.checkNotNull(versionTag);
+ }
+
+ /**
+ * Creates a "selection started" event.
+ *
+ * @param start the word index of the selected word
+ */
+ public static SelectionEvent selectionStarted(int start) {
+ return new SelectionEvent(
+ start, start + 1, EventType.SELECTION_STARTED,
+ TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG);
+ }
+
+ /**
+ * Creates a "selection modified" event.
+ * Use when the user modifies the selection.
+ *
+ * @param start the start word (inclusive) index of the selection
+ * @param end the end word (exclusive) index of the selection
+ */
+ public static SelectionEvent selectionModified(int start, int end) {
+ return new SelectionEvent(
+ start, end, EventType.SELECTION_MODIFIED,
+ TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG);
+ }
+
+ /**
+ * Creates a "selection modified" event.
+ * Use when the user modifies the selection and the selection's entity type is known.
+ *
+ * @param start the start word (inclusive) index of the selection
+ * @param end the end word (exclusive) index of the selection
+ * @param classification the TextClassification object returned by the TextClassifier that
+ * classified the selected text
+ */
+ public static SelectionEvent selectionModified(
+ int start, int end, @NonNull TextClassification classification) {
+ final String entityType = classification.getEntityCount() > 0
+ ? classification.getEntity(0)
+ : TextClassifier.TYPE_UNKNOWN;
+ final String versionTag = classification.getVersionInfo();
+ return new SelectionEvent(
+ start, end, EventType.SELECTION_MODIFIED, entityType, versionTag);
+ }
+
+ /**
+ * Creates a "selection modified" event.
+ * Use when a TextClassifier modifies the selection.
+ *
+ * @param start the start word (inclusive) index of the selection
+ * @param end the end word (exclusive) index of the selection
+ * @param selection the TextSelection object returned by the TextClassifier for the
+ * specified selection
+ */
+ public static SelectionEvent selectionModified(
+ int start, int end, @NonNull TextSelection selection) {
+ final boolean smartSelection = selection.getSourceClassifier()
+ .equals(TextClassifier.DEFAULT_LOG_TAG);
+ final int eventType;
+ if (smartSelection) {
+ eventType = end - start > 1
+ ? EventType.SMART_SELECTION_MULTI
+ : EventType.SMART_SELECTION_SINGLE;
+
+ } else {
+ eventType = EventType.AUTO_SELECTION;
+ }
+ final String entityType = selection.getEntityCount() > 0
+ ? selection.getEntity(0)
+ : TextClassifier.TYPE_UNKNOWN;
+ final String versionTag = selection.getVersionInfo();
+ return new SelectionEvent(start, end, eventType, entityType, versionTag);
+ }
+
+ /**
+ * Creates an event specifying an action taken on a selection.
+ * Use when the user clicks on an action to act on the selected text.
+ *
+ * @param start the start word (inclusive) index of the selection
+ * @param end the end word (exclusive) index of the selection
+ * @param actionType the action that was performed on the selection
+ */
+ public static SelectionEvent selectionAction(
+ int start, int end, @ActionType int actionType) {
+ return new SelectionEvent(
+ start, end, actionType, TextClassifier.TYPE_UNKNOWN, NO_VERSION_TAG);
+ }
+
+ /**
+ * Creates an event specifying an action taken on a selection.
+ * Use when the user clicks on an action to act on the selected text and the selection's
+ * entity type is known.
+ *
+ * @param start the start word (inclusive) index of the selection
+ * @param end the end word (exclusive) index of the selection
+ * @param actionType the action that was performed on the selection
+ * @param classification the TextClassification object returned by the TextClassifier that
+ * classified the selected text
+ */
+ public static SelectionEvent selectionAction(
+ int start, int end, @ActionType int actionType,
+ @NonNull TextClassification classification) {
+ final String entityType = classification.getEntityCount() > 0
+ ? classification.getEntity(0)
+ : TextClassifier.TYPE_UNKNOWN;
+ final String versionTag = classification.getVersionInfo();
+ return new SelectionEvent(start, end, actionType, entityType, versionTag);
+ }
+
+ private boolean isActionType() {
+ switch (mEventType) {
+ case ActionType.OVERTYPE: // fall through
+ case ActionType.COPY: // fall through
+ case ActionType.PASTE: // fall through
+ case ActionType.CUT: // fall through
+ case ActionType.SHARE: // fall through
+ case ActionType.SMART_SHARE: // fall through
+ case ActionType.DRAG: // fall through
+ case ActionType.ABANDON: // fall through
+ case ActionType.SELECT_ALL: // fall through
+ case ActionType.RESET: // fall through
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean isTerminal() {
+ switch (mEventType) {
+ case ActionType.OVERTYPE: // fall through
+ case ActionType.COPY: // fall through
+ case ActionType.PASTE: // fall through
+ case ActionType.CUT: // fall through
+ case ActionType.SHARE: // fall through
+ case ActionType.SMART_SHARE: // fall through
+ case ActionType.DRAG: // fall through
+ case ActionType.ABANDON: // fall through
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/android/view/textservice/SentenceSuggestionsInfo.java b/android/view/textservice/SentenceSuggestionsInfo.java
new file mode 100644
index 00000000..afd62eb5
--- /dev/null
+++ b/android/view/textservice/SentenceSuggestionsInfo.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 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.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * This class contains a metadata of suggestions returned from a text service
+ * (e.g. {@link android.service.textservice.SpellCheckerService}).
+ * The text service uses this class to return the suggestions
+ * for a sentence. See {@link SuggestionsInfo} which is used for suggestions for a word.
+ * This class extends the functionality of {@link SuggestionsInfo} as far as this class enables
+ * you to put multiple {@link SuggestionsInfo}s on a sentence with the offsets and the lengths
+ * of all {@link SuggestionsInfo}s.
+ */
+public final class SentenceSuggestionsInfo implements Parcelable {
+
+ private final SuggestionsInfo[] mSuggestionsInfos;
+ private final int[] mOffsets;
+ private final int[] mLengths;
+
+ /**
+ * Constructor.
+ * @param suggestionsInfos from the text service
+ * @param offsets the array of offsets of suggestions
+ * @param lengths the array of lengths of suggestions
+ */
+ public SentenceSuggestionsInfo(
+ SuggestionsInfo[] suggestionsInfos, int[] offsets, int[] lengths) {
+ if (suggestionsInfos == null || offsets == null || lengths == null) {
+ throw new NullPointerException();
+ }
+ if (suggestionsInfos.length != offsets.length || offsets.length != lengths.length) {
+ throw new IllegalArgumentException();
+ }
+ final int infoSize = suggestionsInfos.length;
+ mSuggestionsInfos = Arrays.copyOf(suggestionsInfos, infoSize);
+ mOffsets = Arrays.copyOf(offsets, infoSize);
+ mLengths = Arrays.copyOf(lengths, infoSize);
+ }
+
+ public SentenceSuggestionsInfo(Parcel source) {
+ final int infoSize = source.readInt();
+ mSuggestionsInfos = new SuggestionsInfo[infoSize];
+ source.readTypedArray(mSuggestionsInfos, SuggestionsInfo.CREATOR);
+ mOffsets = new int[mSuggestionsInfos.length];
+ source.readIntArray(mOffsets);
+ mLengths = new int[mSuggestionsInfos.length];
+ source.readIntArray(mLengths);
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int infoSize = mSuggestionsInfos.length;
+ dest.writeInt(infoSize);
+ dest.writeTypedArray(mSuggestionsInfos, 0);
+ dest.writeIntArray(mOffsets);
+ dest.writeIntArray(mLengths);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @return the count of {@link SuggestionsInfo}s this instance holds.
+ */
+ public int getSuggestionsCount() {
+ return mSuggestionsInfos.length;
+ }
+
+ /**
+ * @param i the id of {@link SuggestionsInfo}s this instance holds.
+ * @return a {@link SuggestionsInfo} at the specified id
+ */
+ public SuggestionsInfo getSuggestionsInfoAt(int i) {
+ if (i >= 0 && i < mSuggestionsInfos.length) {
+ return mSuggestionsInfos[i];
+ }
+ return null;
+ }
+
+ /**
+ * @param i the id of {@link SuggestionsInfo}s this instance holds
+ * @return the offset of the specified {@link SuggestionsInfo}
+ */
+ public int getOffsetAt(int i) {
+ if (i >= 0 && i < mOffsets.length) {
+ return mOffsets[i];
+ }
+ return -1;
+ }
+
+ /**
+ * @param i the id of {@link SuggestionsInfo}s this instance holds
+ * @return the length of the specified {@link SuggestionsInfo}
+ */
+ public int getLengthAt(int i) {
+ if (i >= 0 && i < mLengths.length) {
+ return mLengths[i];
+ }
+ return -1;
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<SentenceSuggestionsInfo> CREATOR
+ = new Parcelable.Creator<SentenceSuggestionsInfo>() {
+ @Override
+ public SentenceSuggestionsInfo createFromParcel(Parcel source) {
+ return new SentenceSuggestionsInfo(source);
+ }
+
+ @Override
+ public SentenceSuggestionsInfo[] newArray(int size) {
+ return new SentenceSuggestionsInfo[size];
+ }
+ };
+}
diff --git a/android/view/textservice/SpellCheckerInfo.java b/android/view/textservice/SpellCheckerInfo.java
new file mode 100644
index 00000000..7aa2c23a
--- /dev/null
+++ b/android/view/textservice/SpellCheckerInfo.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2011 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.textservice;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * This class is used to specify meta information of a spell checker.
+ */
+public final class SpellCheckerInfo implements Parcelable {
+ private static final String TAG = SpellCheckerInfo.class.getSimpleName();
+ private final ResolveInfo mService;
+ private final String mId;
+ private final int mLabel;
+
+ /**
+ * The spell checker setting activity's name, used by the system settings to
+ * launch the setting activity.
+ */
+ private final String mSettingsActivityName;
+
+ /**
+ * The array of subtypes.
+ */
+ private final ArrayList<SpellCheckerSubtype> mSubtypes = new ArrayList<>();
+
+ /**
+ * Constructor.
+ * @hide
+ */
+ public SpellCheckerInfo(Context context, ResolveInfo service)
+ throws XmlPullParserException, IOException {
+ mService = service;
+ ServiceInfo si = service.serviceInfo;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+
+ final PackageManager pm = context.getPackageManager();
+ int label = 0;
+ String settingsActivityComponent = null;
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, SpellCheckerSession.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No "
+ + SpellCheckerSession.SERVICE_META_DATA + " meta-data");
+ }
+
+ final Resources res = pm.getResourcesForApplication(si.applicationInfo);
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ final String nodeName = parser.getName();
+ if (!"spell-checker".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with spell-checker tag");
+ }
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.SpellChecker);
+ label = sa.getResourceId(com.android.internal.R.styleable.SpellChecker_label, 0);
+ settingsActivityComponent = sa.getString(
+ com.android.internal.R.styleable.SpellChecker_settingsActivity);
+ sa.recycle();
+
+ final int depth = parser.getDepth();
+ // Parse all subtypes
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ final String subtypeNodeName = parser.getName();
+ if (!"subtype".equals(subtypeNodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data in spell-checker does not start with subtype tag");
+ }
+ final TypedArray a = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.SpellChecker_Subtype);
+ SpellCheckerSubtype subtype = new SpellCheckerSubtype(
+ a.getResourceId(com.android.internal.R.styleable
+ .SpellChecker_Subtype_label, 0),
+ a.getString(com.android.internal.R.styleable
+ .SpellChecker_Subtype_subtypeLocale),
+ a.getString(com.android.internal.R.styleable
+ .SpellChecker_Subtype_languageTag),
+ a.getString(com.android.internal.R.styleable
+ .SpellChecker_Subtype_subtypeExtraValue),
+ a.getInt(com.android.internal.R.styleable
+ .SpellChecker_Subtype_subtypeId, 0));
+ mSubtypes.add(subtype);
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Caught exception: " + e);
+ throw new XmlPullParserException(
+ "Unable to create context for: " + si.packageName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ mLabel = label;
+ mSettingsActivityName = settingsActivityComponent;
+ }
+
+ /**
+ * Constructor.
+ * @hide
+ */
+ public SpellCheckerInfo(Parcel source) {
+ mLabel = source.readInt();
+ mId = source.readString();
+ mSettingsActivityName = source.readString();
+ mService = ResolveInfo.CREATOR.createFromParcel(source);
+ source.readTypedList(mSubtypes, SpellCheckerSubtype.CREATOR);
+ }
+
+ /**
+ * Return a unique ID for this spell checker. The ID is generated from
+ * the package and class name implementing the method.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Return the component of the service that implements.
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(
+ mService.serviceInfo.packageName, mService.serviceInfo.name);
+ }
+
+ /**
+ * Return the .apk package that implements this.
+ */
+ public String getPackageName() {
+ return mService.serviceInfo.packageName;
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mLabel);
+ dest.writeString(mId);
+ dest.writeString(mSettingsActivityName);
+ mService.writeToParcel(dest, flags);
+ dest.writeTypedList(mSubtypes);
+ }
+
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<SpellCheckerInfo> CREATOR
+ = new Parcelable.Creator<SpellCheckerInfo>() {
+ @Override
+ public SpellCheckerInfo createFromParcel(Parcel source) {
+ return new SpellCheckerInfo(source);
+ }
+
+ @Override
+ public SpellCheckerInfo[] newArray(int size) {
+ return new SpellCheckerInfo[size];
+ }
+ };
+
+ /**
+ * Load the user-displayed label for this spell checker.
+ *
+ * @param pm Supply a PackageManager used to load the spell checker's resources.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ if (mLabel == 0 || pm == null) return "";
+ return pm.getText(getPackageName(), mLabel, mService.serviceInfo.applicationInfo);
+ }
+
+ /**
+ * Load the user-displayed icon for this spell checker.
+ *
+ * @param pm Supply a PackageManager used to load the spell checker's resources.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ return mService.loadIcon(pm);
+ }
+
+
+ /**
+ * Return the raw information about the Service implementing this
+ * spell checker. Do not modify the returned object.
+ */
+ public ServiceInfo getServiceInfo() {
+ return mService.serviceInfo;
+ }
+
+ /**
+ * Return the class name of an activity that provides a settings UI.
+ * You can launch this activity be starting it with
+ * an {@link android.content.Intent} whose action is MAIN and with an
+ * explicit {@link android.content.ComponentName}
+ * composed of {@link #getPackageName} and the class name returned here.
+ *
+ * <p>A null will be returned if there is no settings activity.
+ */
+ public String getSettingsActivity() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Return the count of the subtypes.
+ */
+ public int getSubtypeCount() {
+ return mSubtypes.size();
+ }
+
+ /**
+ * Return the subtype at the specified index.
+ *
+ * @param index the index of the subtype to return.
+ */
+ public SpellCheckerSubtype getSubtypeAt(int index) {
+ return mSubtypes.get(index);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public void dump(final PrintWriter pw, final String prefix) {
+ pw.println(prefix + "mId=" + mId);
+ pw.println(prefix + "mSettingsActivityName=" + mSettingsActivityName);
+ pw.println(prefix + "Service:");
+ mService.dump(new PrintWriterPrinter(pw), prefix + " ");
+ final int N = getSubtypeCount();
+ for (int i = 0; i < N; i++) {
+ final SpellCheckerSubtype st = getSubtypeAt(i);
+ pw.println(prefix + " " + "Subtype #" + i + ":");
+ pw.println(prefix + " " + "locale=" + st.getLocale()
+ + " languageTag=" + st.getLanguageTag());
+ pw.println(prefix + " " + "extraValue=" + st.getExtraValue());
+ }
+ }
+}
diff --git a/android/view/textservice/SpellCheckerSession.java b/android/view/textservice/SpellCheckerSession.java
new file mode 100644
index 00000000..779eefb1
--- /dev/null
+++ b/android/view/textservice/SpellCheckerSession.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2011 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.textservice;
+
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.textservice.ISpellCheckerSession;
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
+import dalvik.system.CloseGuard;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService.
+ *
+ *
+ * <a name="Applications"></a>
+ * <h3>Applications</h3>
+ *
+ * <p>In most cases, applications that are using the standard
+ * {@link android.widget.TextView} or its subclasses will have little they need
+ * to do to work well with spell checker services. The main things you need to
+ * be aware of are:</p>
+ *
+ * <ul>
+ * <li> Properly set the {@link android.R.attr#inputType} in your editable
+ * text views, so that the spell checker will have enough context to help the
+ * user in editing text in them.
+ * </ul>
+ *
+ * <p>For the rare people amongst us writing client applications that use the spell checker service
+ * directly, you will need to use {@link #getSuggestions(TextInfo, int)} or
+ * {@link #getSuggestions(TextInfo[], int, boolean)} for obtaining results from the spell checker
+ * service by yourself.</p>
+ *
+ * <h3>Security</h3>
+ *
+ * <p>There are a lot of security issues associated with spell checkers,
+ * since they could monitor all the text being sent to them
+ * through, for instance, {@link android.widget.TextView}.
+ * The Android spell checker framework also allows
+ * arbitrary third party spell checkers, so care must be taken to restrict their
+ * selection and interactions.</p>
+ *
+ * <p>Here are some key points about the security architecture behind the
+ * spell checker framework:</p>
+ *
+ * <ul>
+ * <li>Only the system is allowed to directly access a spell checker framework's
+ * {@link android.service.textservice.SpellCheckerService} interface, via the
+ * {@link android.Manifest.permission#BIND_TEXT_SERVICE} permission. This is
+ * enforced in the system by not binding to a spell checker service that does
+ * not require this permission.
+ *
+ * <li>The user must explicitly enable a new spell checker in settings before
+ * they can be enabled, to confirm with the system that they know about it
+ * and want to make it available for use.
+ * </ul>
+ *
+ */
+public class SpellCheckerSession {
+ private static final String TAG = SpellCheckerSession.class.getSimpleName();
+ private static final boolean DBG = false;
+ /**
+ * Name under which a SpellChecker service component publishes information about itself.
+ * This meta-data must reference an XML resource.
+ **/
+ public static final String SERVICE_META_DATA = "android.view.textservice.scs";
+
+ private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
+ private static final int MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE = 2;
+
+ private final InternalListener mInternalListener;
+ private final ITextServicesManager mTextServicesManager;
+ private final SpellCheckerInfo mSpellCheckerInfo;
+ private final SpellCheckerSessionListener mSpellCheckerSessionListener;
+ private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
+
+ private final CloseGuard mGuard = CloseGuard.get();
+
+ /** Handler that will execute the main tasks */
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ON_GET_SUGGESTION_MULTIPLE:
+ handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
+ break;
+ case MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE:
+ handleOnGetSentenceSuggestionsMultiple((SentenceSuggestionsInfo[]) msg.obj);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Constructor
+ * @hide
+ */
+ public SpellCheckerSession(
+ SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) {
+ if (info == null || listener == null || tsm == null) {
+ throw new NullPointerException();
+ }
+ mSpellCheckerInfo = info;
+ mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
+ mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
+ mTextServicesManager = tsm;
+ mSpellCheckerSessionListener = listener;
+
+ mGuard.open("finishSession");
+ }
+
+ /**
+ * @return true if the connection to a text service of this session is disconnected and not
+ * alive.
+ */
+ public boolean isSessionDisconnected() {
+ return mSpellCheckerSessionListenerImpl.isDisconnected();
+ }
+
+ /**
+ * Get the spell checker service info this spell checker session has.
+ * @return SpellCheckerInfo for the specified locale.
+ */
+ public SpellCheckerInfo getSpellChecker() {
+ return mSpellCheckerInfo;
+ }
+
+ /**
+ * Cancel pending and running spell check tasks
+ */
+ public void cancel() {
+ mSpellCheckerSessionListenerImpl.cancel();
+ }
+
+ /**
+ * Finish this session and allow TextServicesManagerService to disconnect the bound spell
+ * checker.
+ */
+ public void close() {
+ mGuard.close();
+ try {
+ mSpellCheckerSessionListenerImpl.close();
+ mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
+ } catch (RemoteException e) {
+ // do nothing
+ }
+ }
+
+ /**
+ * Get suggestions from the specified sentences
+ * @param textInfos an array of text metadata for a spell checker
+ * @param suggestionsLimit the maximum number of suggestions that will be returned
+ */
+ public void getSentenceSuggestions(TextInfo[] textInfos, int suggestionsLimit) {
+ mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple(
+ textInfos, suggestionsLimit);
+ }
+
+ /**
+ * Get candidate strings for a substring of the specified text.
+ * @param textInfo text metadata for a spell checker
+ * @param suggestionsLimit the maximum number of suggestions that will be returned
+ * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
+ */
+ @Deprecated
+ public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
+ getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
+ }
+
+ /**
+ * A batch process of getSuggestions
+ * @param textInfos an array of text metadata for a spell checker
+ * @param suggestionsLimit the maximum number of suggestions that will be returned
+ * @param sequentialWords true if textInfos can be treated as sequential words.
+ * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
+ */
+ @Deprecated
+ public void getSuggestions(
+ TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
+ if (DBG) {
+ Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
+ }
+ mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
+ textInfos, suggestionsLimit, sequentialWords);
+ }
+
+ private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) {
+ mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
+ }
+
+ private void handleOnGetSentenceSuggestionsMultiple(SentenceSuggestionsInfo[] suggestionInfos) {
+ mSpellCheckerSessionListener.onGetSentenceSuggestions(suggestionInfos);
+ }
+
+ private static final class SpellCheckerSessionListenerImpl
+ extends ISpellCheckerSessionListener.Stub {
+ private static final int TASK_CANCEL = 1;
+ private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
+ private static final int TASK_CLOSE = 3;
+ private static final int TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE = 4;
+ private static String taskToString(int task) {
+ switch (task) {
+ case TASK_CANCEL:
+ return "TASK_CANCEL";
+ case TASK_GET_SUGGESTIONS_MULTIPLE:
+ return "TASK_GET_SUGGESTIONS_MULTIPLE";
+ case TASK_CLOSE:
+ return "TASK_CLOSE";
+ case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
+ return "TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE";
+ default:
+ return "Unexpected task=" + task;
+ }
+ }
+
+ private final Queue<SpellCheckerParams> mPendingTasks = new LinkedList<>();
+ private Handler mHandler;
+
+ private static final int STATE_WAIT_CONNECTION = 0;
+ private static final int STATE_CONNECTED = 1;
+ private static final int STATE_CLOSED_AFTER_CONNECTION = 2;
+ private static final int STATE_CLOSED_BEFORE_CONNECTION = 3;
+ private static String stateToString(int state) {
+ switch (state) {
+ case STATE_WAIT_CONNECTION: return "STATE_WAIT_CONNECTION";
+ case STATE_CONNECTED: return "STATE_CONNECTED";
+ case STATE_CLOSED_AFTER_CONNECTION: return "STATE_CLOSED_AFTER_CONNECTION";
+ case STATE_CLOSED_BEFORE_CONNECTION: return "STATE_CLOSED_BEFORE_CONNECTION";
+ default: return "Unexpected state=" + state;
+ }
+ }
+ private int mState = STATE_WAIT_CONNECTION;
+
+ private ISpellCheckerSession mISpellCheckerSession;
+ private HandlerThread mThread;
+ private Handler mAsyncHandler;
+
+ public SpellCheckerSessionListenerImpl(Handler handler) {
+ mHandler = handler;
+ }
+
+ private static class SpellCheckerParams {
+ public final int mWhat;
+ public final TextInfo[] mTextInfos;
+ public final int mSuggestionsLimit;
+ public final boolean mSequentialWords;
+ public ISpellCheckerSession mSession;
+ public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit,
+ boolean sequentialWords) {
+ mWhat = what;
+ mTextInfos = textInfos;
+ mSuggestionsLimit = suggestionsLimit;
+ mSequentialWords = sequentialWords;
+ }
+ }
+
+ private void processTask(ISpellCheckerSession session, SpellCheckerParams scp,
+ boolean async) {
+ if (DBG) {
+ synchronized (this) {
+ Log.d(TAG, "entering processTask:"
+ + " session.hashCode()=#" + Integer.toHexString(session.hashCode())
+ + " scp.mWhat=" + taskToString(scp.mWhat) + " async=" + async
+ + " mAsyncHandler=" + mAsyncHandler
+ + " mState=" + stateToString(mState));
+ }
+ }
+ if (async || mAsyncHandler == null) {
+ switch (scp.mWhat) {
+ case TASK_CANCEL:
+ try {
+ session.onCancel();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to cancel " + e);
+ }
+ break;
+ case TASK_GET_SUGGESTIONS_MULTIPLE:
+ try {
+ session.onGetSuggestionsMultiple(scp.mTextInfos,
+ scp.mSuggestionsLimit, scp.mSequentialWords);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get suggestions " + e);
+ }
+ break;
+ case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
+ try {
+ session.onGetSentenceSuggestionsMultiple(
+ scp.mTextInfos, scp.mSuggestionsLimit);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get suggestions " + e);
+ }
+ break;
+ case TASK_CLOSE:
+ try {
+ session.onClose();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to close " + e);
+ }
+ break;
+ }
+ } else {
+ // The interface is to a local object, so need to execute it
+ // asynchronously.
+ scp.mSession = session;
+ mAsyncHandler.sendMessage(Message.obtain(mAsyncHandler, 1, scp));
+ }
+
+ if (scp.mWhat == TASK_CLOSE) {
+ // If we are closing, we want to clean up our state now even
+ // if it is pending as an async operation.
+ synchronized (this) {
+ processCloseLocked();
+ }
+ }
+ }
+
+ private void processCloseLocked() {
+ if (DBG) Log.d(TAG, "entering processCloseLocked:"
+ + " session" + (mISpellCheckerSession != null ? ".hashCode()=#"
+ + Integer.toHexString(mISpellCheckerSession.hashCode()) : "=null")
+ + " mState=" + stateToString(mState));
+ mISpellCheckerSession = null;
+ if (mThread != null) {
+ mThread.quit();
+ }
+ mHandler = null;
+ mPendingTasks.clear();
+ mThread = null;
+ mAsyncHandler = null;
+ switch (mState) {
+ case STATE_WAIT_CONNECTION:
+ mState = STATE_CLOSED_BEFORE_CONNECTION;
+ break;
+ case STATE_CONNECTED:
+ mState = STATE_CLOSED_AFTER_CONNECTION;
+ break;
+ default:
+ Log.e(TAG, "processCloseLocked is called unexpectedly. mState=" +
+ stateToString(mState));
+ break;
+ }
+ }
+
+ public void onServiceConnected(ISpellCheckerSession session) {
+ synchronized (this) {
+ switch (mState) {
+ case STATE_WAIT_CONNECTION:
+ // OK, go ahead.
+ break;
+ case STATE_CLOSED_BEFORE_CONNECTION:
+ // This is possible, and not an error. The client no longer is interested
+ // in this connection. OK to ignore.
+ if (DBG) Log.i(TAG, "ignoring onServiceConnected since the session is"
+ + " already closed.");
+ return;
+ default:
+ Log.e(TAG, "ignoring onServiceConnected due to unexpected mState="
+ + stateToString(mState));
+ return;
+ }
+ if (session == null) {
+ Log.e(TAG, "ignoring onServiceConnected due to session=null");
+ return;
+ }
+ mISpellCheckerSession = session;
+ if (session.asBinder() instanceof Binder && mThread == null) {
+ if (DBG) Log.d(TAG, "starting HandlerThread in onServiceConnected.");
+ // If this is a local object, we need to do our own threading
+ // to make sure we handle it asynchronously.
+ mThread = new HandlerThread("SpellCheckerSession",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ mAsyncHandler = new Handler(mThread.getLooper()) {
+ @Override public void handleMessage(Message msg) {
+ SpellCheckerParams scp = (SpellCheckerParams)msg.obj;
+ processTask(scp.mSession, scp, true);
+ }
+ };
+ }
+ mState = STATE_CONNECTED;
+ if (DBG) {
+ Log.d(TAG, "processed onServiceConnected: mISpellCheckerSession.hashCode()=#"
+ + Integer.toHexString(mISpellCheckerSession.hashCode())
+ + " mPendingTasks.size()=" + mPendingTasks.size());
+ }
+ while (!mPendingTasks.isEmpty()) {
+ processTask(session, mPendingTasks.poll(), false);
+ }
+ }
+ }
+
+ public void cancel() {
+ processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false));
+ }
+
+ public void getSuggestionsMultiple(
+ TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
+ processOrEnqueueTask(
+ new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos,
+ suggestionsLimit, sequentialWords));
+ }
+
+ public void getSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
+ processOrEnqueueTask(
+ new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE,
+ textInfos, suggestionsLimit, false));
+ }
+
+ public void close() {
+ processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false));
+ }
+
+ public boolean isDisconnected() {
+ synchronized (this) {
+ return mState != STATE_CONNECTED;
+ }
+ }
+
+ private void processOrEnqueueTask(SpellCheckerParams scp) {
+ ISpellCheckerSession session;
+ synchronized (this) {
+ if (mState != STATE_WAIT_CONNECTION && mState != STATE_CONNECTED) {
+ Log.e(TAG, "ignoring processOrEnqueueTask due to unexpected mState="
+ + taskToString(scp.mWhat)
+ + " scp.mWhat=" + taskToString(scp.mWhat));
+ return;
+ }
+
+ if (mState == STATE_WAIT_CONNECTION) {
+ // If we are still waiting for the connection. Need to pay special attention.
+ if (scp.mWhat == TASK_CLOSE) {
+ processCloseLocked();
+ return;
+ }
+ // Enqueue the task to task queue.
+ SpellCheckerParams closeTask = null;
+ if (scp.mWhat == TASK_CANCEL) {
+ if (DBG) Log.d(TAG, "canceling pending tasks in processOrEnqueueTask.");
+ while (!mPendingTasks.isEmpty()) {
+ final SpellCheckerParams tmp = mPendingTasks.poll();
+ if (tmp.mWhat == TASK_CLOSE) {
+ // Only one close task should be processed, while we need to remove
+ // all close tasks from the queue
+ closeTask = tmp;
+ }
+ }
+ }
+ mPendingTasks.offer(scp);
+ if (closeTask != null) {
+ mPendingTasks.offer(closeTask);
+ }
+ if (DBG) Log.d(TAG, "queueing tasks in processOrEnqueueTask since the"
+ + " connection is not established."
+ + " mPendingTasks.size()=" + mPendingTasks.size());
+ return;
+ }
+
+ session = mISpellCheckerSession;
+ }
+ // session must never be null here.
+ processTask(session, scp, false);
+ }
+
+ @Override
+ public void onGetSuggestions(SuggestionsInfo[] results) {
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.sendMessage(Message.obtain(mHandler,
+ MSG_ON_GET_SUGGESTION_MULTIPLE, results));
+ }
+ }
+ }
+
+ @Override
+ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.sendMessage(Message.obtain(mHandler,
+ MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback for getting results from text services
+ */
+ public interface SpellCheckerSessionListener {
+ /**
+ * Callback for {@link SpellCheckerSession#getSuggestions(TextInfo, int)}
+ * and {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)}
+ * @param results an array of {@link SuggestionsInfo}s.
+ * These results are suggestions for {@link TextInfo}s queried by
+ * {@link SpellCheckerSession#getSuggestions(TextInfo, int)} or
+ * {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)}
+ */
+ public void onGetSuggestions(SuggestionsInfo[] results);
+ /**
+ * Callback for {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)}
+ * @param results an array of {@link SentenceSuggestionsInfo}s.
+ * These results are suggestions for {@link TextInfo}s
+ * queried by {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)}.
+ */
+ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results);
+ }
+
+ private static final class InternalListener extends ITextServicesSessionListener.Stub {
+ private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;
+
+ public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
+ mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl;
+ }
+
+ @Override
+ public void onServiceConnected(ISpellCheckerSession session) {
+ mParentSpellCheckerSessionListenerImpl.onServiceConnected(session);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ // Note that mGuard will be null if the constructor threw.
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ close();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public ITextServicesSessionListener getTextServicesSessionListener() {
+ return mInternalListener;
+ }
+
+ /**
+ * @hide
+ */
+ public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
+ return mSpellCheckerSessionListenerImpl;
+ }
+}
diff --git a/android/view/textservice/SpellCheckerSubtype.java b/android/view/textservice/SpellCheckerSubtype.java
new file mode 100644
index 00000000..026610ec
--- /dev/null
+++ b/android/view/textservice/SpellCheckerSubtype.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2011 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.textservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.inputmethod.InputMethodUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is used to specify meta information of a subtype contained in a spell checker.
+ * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
+ *
+ * @see SpellCheckerInfo
+ *
+ * @attr ref android.R.styleable#SpellChecker_Subtype_label
+ * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
+ */
+public final class SpellCheckerSubtype implements Parcelable {
+ private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
+ private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
+ private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
+ /**
+ * @hide
+ */
+ public static final int SUBTYPE_ID_NONE = 0;
+ private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
+
+ private final int mSubtypeId;
+ private final int mSubtypeHashCode;
+ private final int mSubtypeNameResId;
+ private final String mSubtypeLocale;
+ private final String mSubtypeLanguageTag;
+ private final String mSubtypeExtraValue;
+ private HashMap<String, String> mExtraValueHashMapCache;
+
+ /**
+ * Constructor.
+ *
+ * <p>There is no public API that requires developers to instantiate custom
+ * {@link SpellCheckerSubtype} object. Hence so far there is no need to make this constructor
+ * available in public API.</p>
+ *
+ * @param nameId The name of the subtype
+ * @param locale The locale supported by the subtype
+ * @param languageTag The BCP-47 Language Tag associated with this subtype.
+ * @param extraValue The extra value of the subtype
+ * @param subtypeId The subtype ID that is supposed to be stable during package update.
+ *
+ * @hide
+ */
+ public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
+ int subtypeId) {
+ mSubtypeNameResId = nameId;
+ mSubtypeLocale = locale != null ? locale : "";
+ mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
+ mSubtypeExtraValue = extraValue != null ? extraValue : "";
+ mSubtypeId = subtypeId;
+ mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+ mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+ }
+
+ /**
+ * Constructor.
+ * @param nameId The name of the subtype
+ * @param locale The locale supported by the subtype
+ * @param extraValue The extra value of the subtype
+ *
+ * @deprecated There is no public API that requires developers to directly instantiate custom
+ * {@link SpellCheckerSubtype} objects right now. Hence only the system is expected to be able
+ * to instantiate {@link SpellCheckerSubtype} object.
+ */
+ @Deprecated
+ public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
+ this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
+ }
+
+ SpellCheckerSubtype(Parcel source) {
+ String s;
+ mSubtypeNameResId = source.readInt();
+ s = source.readString();
+ mSubtypeLocale = s != null ? s : "";
+ s = source.readString();
+ mSubtypeLanguageTag = s != null ? s : "";
+ s = source.readString();
+ mSubtypeExtraValue = s != null ? s : "";
+ mSubtypeId = source.readInt();
+ mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+ mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+ }
+
+ /**
+ * @return the name of the subtype
+ */
+ public int getNameResId() {
+ return mSubtypeNameResId;
+ }
+
+ /**
+ * @return the locale of the subtype
+ *
+ * @deprecated Use {@link #getLanguageTag()} instead.
+ */
+ @Deprecated
+ @NonNull
+ public String getLocale() {
+ return mSubtypeLocale;
+ }
+
+ /**
+ * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag
+ * is specified.
+ *
+ * @see Locale#forLanguageTag(String)
+ */
+ @NonNull
+ public String getLanguageTag() {
+ return mSubtypeLanguageTag;
+ }
+
+ /**
+ * @return the extra value of the subtype
+ */
+ public String getExtraValue() {
+ return mSubtypeExtraValue;
+ }
+
+ private HashMap<String, String> getExtraValueHashMap() {
+ if (mExtraValueHashMapCache == null) {
+ mExtraValueHashMapCache = new HashMap<String, String>();
+ final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
+ final int N = pairs.length;
+ for (int i = 0; i < N; ++i) {
+ final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
+ if (pair.length == 1) {
+ mExtraValueHashMapCache.put(pair[0], null);
+ } else if (pair.length > 1) {
+ if (pair.length > 2) {
+ Slog.w(TAG, "ExtraValue has two or more '='s");
+ }
+ mExtraValueHashMapCache.put(pair[0], pair[1]);
+ }
+ }
+ }
+ return mExtraValueHashMapCache;
+ }
+
+ /**
+ * The string of ExtraValue in subtype should be defined as follows:
+ * example: key0,key1=value1,key2,key3,key4=value4
+ * @param key the key of extra value
+ * @return the subtype contains specified the extra value
+ */
+ public boolean containsExtraValueKey(String key) {
+ return getExtraValueHashMap().containsKey(key);
+ }
+
+ /**
+ * The string of ExtraValue in subtype should be defined as follows:
+ * example: key0,key1=value1,key2,key3,key4=value4
+ * @param key the key of extra value
+ * @return the value of the specified key
+ */
+ public String getExtraValueOf(String key) {
+ return getExtraValueHashMap().get(key);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubtypeHashCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof SpellCheckerSubtype) {
+ SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
+ if (subtype.mSubtypeId != SUBTYPE_ID_NONE || mSubtypeId != SUBTYPE_ID_NONE) {
+ return (subtype.hashCode() == hashCode());
+ }
+ return (subtype.hashCode() == hashCode())
+ && (subtype.getNameResId() == getNameResId())
+ && (subtype.getLocale().equals(getLocale()))
+ && (subtype.getLanguageTag().equals(getLanguageTag()))
+ && (subtype.getExtraValue().equals(getExtraValue()));
+ }
+ return false;
+ }
+
+ /**
+ * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+ * specified, then try to construct from {@link #getLocale()}
+ *
+ * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
+ * @hide
+ */
+ @Nullable
+ public Locale getLocaleObject() {
+ if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+ return Locale.forLanguageTag(mSubtypeLanguageTag);
+ }
+ return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
+ }
+
+ /**
+ * @param context Context will be used for getting Locale and PackageManager.
+ * @param packageName The package name of the spell checker
+ * @param appInfo The application info of the spell checker
+ * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
+ * can have only one %s in it. If there is, the %s part will be replaced with the locale's
+ * display name by the formatter. If there is not, this method simply returns the string
+ * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
+ * framework to generate an appropriate display name.
+ */
+ public CharSequence getDisplayName(
+ Context context, String packageName, ApplicationInfo appInfo) {
+ final Locale locale = getLocaleObject();
+ final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
+ if (mSubtypeNameResId == 0) {
+ return localeStr;
+ }
+ final CharSequence subtypeName = context.getPackageManager().getText(
+ packageName, mSubtypeNameResId, appInfo);
+ if (!TextUtils.isEmpty(subtypeName)) {
+ return String.format(subtypeName.toString(), localeStr);
+ } else {
+ return localeStr;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(mSubtypeNameResId);
+ dest.writeString(mSubtypeLocale);
+ dest.writeString(mSubtypeLanguageTag);
+ dest.writeString(mSubtypeExtraValue);
+ dest.writeInt(mSubtypeId);
+ }
+
+ public static final Parcelable.Creator<SpellCheckerSubtype> CREATOR
+ = new Parcelable.Creator<SpellCheckerSubtype>() {
+ @Override
+ public SpellCheckerSubtype createFromParcel(Parcel source) {
+ return new SpellCheckerSubtype(source);
+ }
+
+ @Override
+ public SpellCheckerSubtype[] newArray(int size) {
+ return new SpellCheckerSubtype[size];
+ }
+ };
+
+ private static int hashCodeInternal(String locale, String extraValue) {
+ return Arrays.hashCode(new Object[] {locale, extraValue});
+ }
+
+ /**
+ * Sort the list of subtypes
+ * @param context Context will be used for getting localized strings
+ * @param flags Flags for the sort order
+ * @param sci SpellCheckerInfo of which subtypes are subject to be sorted
+ * @param subtypeList List which will be sorted
+ * @return Sorted list of subtypes
+ * @hide
+ */
+ public static List<SpellCheckerSubtype> sort(Context context, int flags, SpellCheckerInfo sci,
+ List<SpellCheckerSubtype> subtypeList) {
+ if (sci == null) return subtypeList;
+ final HashSet<SpellCheckerSubtype> subtypesSet = new HashSet<SpellCheckerSubtype>(
+ subtypeList);
+ final ArrayList<SpellCheckerSubtype> sortedList = new ArrayList<SpellCheckerSubtype>();
+ int N = sci.getSubtypeCount();
+ for (int i = 0; i < N; ++i) {
+ SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+ if (subtypesSet.contains(subtype)) {
+ sortedList.add(subtype);
+ subtypesSet.remove(subtype);
+ }
+ }
+ // If subtypes in subtypesSet remain, that means these subtypes are not
+ // contained in sci, so the remaining subtypes will be appended.
+ for (SpellCheckerSubtype subtype: subtypesSet) {
+ sortedList.add(subtype);
+ }
+ return sortedList;
+ }
+}
diff --git a/android/view/textservice/SuggestionsInfo.java b/android/view/textservice/SuggestionsInfo.java
new file mode 100644
index 00000000..dc2051cc
--- /dev/null
+++ b/android/view/textservice/SuggestionsInfo.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2011 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.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * This class contains a metadata of suggestions from the text service
+ */
+public final class SuggestionsInfo implements Parcelable {
+ private static final String[] EMPTY = ArrayUtils.emptyArray(String.class);
+
+ /**
+ * Flag of the attributes of the suggestions that can be obtained by
+ * {@link #getSuggestionsAttributes}: this tells that the requested word was found
+ * in the dictionary in the text service.
+ */
+ public static final int RESULT_ATTR_IN_THE_DICTIONARY = 0x0001;
+ /**
+ * Flag of the attributes of the suggestions that can be obtained by
+ * {@link #getSuggestionsAttributes}: this tells that the text service thinks the requested
+ * word looks like a typo.
+ */
+ public static final int RESULT_ATTR_LOOKS_LIKE_TYPO = 0x0002;
+ /**
+ * Flag of the attributes of the suggestions that can be obtained by
+ * {@link #getSuggestionsAttributes}: this tells that the text service thinks
+ * the result suggestions include highly recommended ones.
+ */
+ public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 0x0004;
+ private final int mSuggestionsAttributes;
+ private final String[] mSuggestions;
+ private final boolean mSuggestionsAvailable;
+ private int mCookie;
+ private int mSequence;
+
+ /**
+ * Constructor.
+ * @param suggestionsAttributes from the text service
+ * @param suggestions from the text service
+ */
+ public SuggestionsInfo(int suggestionsAttributes, String[] suggestions) {
+ this(suggestionsAttributes, suggestions, 0, 0);
+ }
+
+ /**
+ * Constructor.
+ * @param suggestionsAttributes from the text service
+ * @param suggestions from the text service
+ * @param cookie the cookie of the input TextInfo
+ * @param sequence the cookie of the input TextInfo
+ */
+ public SuggestionsInfo(
+ int suggestionsAttributes, String[] suggestions, int cookie, int sequence) {
+ if (suggestions == null) {
+ mSuggestions = EMPTY;
+ mSuggestionsAvailable = false;
+ } else {
+ mSuggestions = suggestions;
+ mSuggestionsAvailable = true;
+ }
+ mSuggestionsAttributes = suggestionsAttributes;
+ mCookie = cookie;
+ mSequence = sequence;
+ }
+
+ public SuggestionsInfo(Parcel source) {
+ mSuggestionsAttributes = source.readInt();
+ mSuggestions = source.readStringArray();
+ mCookie = source.readInt();
+ mSequence = source.readInt();
+ mSuggestionsAvailable = source.readInt() == 1;
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSuggestionsAttributes);
+ dest.writeStringArray(mSuggestions);
+ dest.writeInt(mCookie);
+ dest.writeInt(mSequence);
+ dest.writeInt(mSuggestionsAvailable ? 1 : 0);
+ }
+
+ /**
+ * Set the cookie and the sequence of SuggestionsInfo which are set to TextInfo from a client
+ * application
+ * @param cookie the cookie of an input TextInfo
+ * @param sequence the cookie of an input TextInfo
+ */
+ public void setCookieAndSequence(int cookie, int sequence) {
+ mCookie = cookie;
+ mSequence = sequence;
+ }
+
+ /**
+ * @return the cookie which may be set by a client application
+ */
+ public int getCookie() {
+ return mCookie;
+ }
+
+ /**
+ * @return the sequence which may be set by a client application
+ */
+ public int getSequence() {
+ return mSequence;
+ }
+
+ /**
+ * @return the attributes of suggestions. This includes whether the spell checker has the word
+ * in its dictionary or not and whether the spell checker has confident suggestions for the
+ * word or not.
+ */
+ public int getSuggestionsAttributes() {
+ return mSuggestionsAttributes;
+ }
+
+ /**
+ * @return the count of the suggestions. If there's no suggestions at all, this method returns
+ * -1. Even if this method returns 0, it doesn't necessarily mean that there are no suggestions
+ * for the requested word. For instance, the caller could have been asked to limit the maximum
+ * number of suggestions returned.
+ */
+ public int getSuggestionsCount() {
+ if (!mSuggestionsAvailable) {
+ return -1;
+ }
+ return mSuggestions.length;
+ }
+
+ /**
+ * @param i the id of suggestions
+ * @return the suggestion at the specified id
+ */
+ public String getSuggestionAt(int i) {
+ return mSuggestions[i];
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<SuggestionsInfo> CREATOR
+ = new Parcelable.Creator<SuggestionsInfo>() {
+ @Override
+ public SuggestionsInfo createFromParcel(Parcel source) {
+ return new SuggestionsInfo(source);
+ }
+
+ @Override
+ public SuggestionsInfo[] newArray(int size) {
+ return new SuggestionsInfo[size];
+ }
+ };
+
+ /**
+ * Used to make this class parcelable.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/textservice/TextInfo.java b/android/view/textservice/TextInfo.java
new file mode 100644
index 00000000..5499918a
--- /dev/null
+++ b/android/view/textservice/TextInfo.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 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.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.ParcelableSpan;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.SpellCheckSpan;
+
+/**
+ * This class contains a metadata of the input of TextService
+ */
+public final class TextInfo implements Parcelable {
+ private final CharSequence mCharSequence;
+ private final int mCookie;
+ private final int mSequenceNumber;
+
+ private static final int DEFAULT_COOKIE = 0;
+ private static final int DEFAULT_SEQUENCE_NUMBER = 0;
+
+ /**
+ * Constructor.
+ * @param text the text which will be input to TextService
+ */
+ public TextInfo(String text) {
+ this(text, 0, getStringLengthOrZero(text), DEFAULT_COOKIE, DEFAULT_SEQUENCE_NUMBER);
+ }
+
+ /**
+ * Constructor.
+ * @param text the text which will be input to TextService
+ * @param cookie the cookie for this TextInfo
+ * @param sequenceNumber the sequence number for this TextInfo
+ */
+ public TextInfo(String text, int cookie, int sequenceNumber) {
+ this(text, 0, getStringLengthOrZero(text), cookie, sequenceNumber);
+ }
+
+ private static int getStringLengthOrZero(final String text) {
+ return TextUtils.isEmpty(text) ? 0 : text.length();
+ }
+
+ /**
+ * Constructor.
+ * @param charSequence the text which will be input to TextService. Attached spans that
+ * implement {@link ParcelableSpan} will also be marshaled alongside with the text.
+ * @param start the beginning of the range of text (inclusive).
+ * @param end the end of the range of text (exclusive).
+ * @param cookie the cookie for this TextInfo
+ * @param sequenceNumber the sequence number for this TextInfo
+ */
+ public TextInfo(CharSequence charSequence, int start, int end, int cookie, int sequenceNumber) {
+ if (TextUtils.isEmpty(charSequence)) {
+ throw new IllegalArgumentException("charSequence is empty");
+ }
+ // Create a snapshot of the text including spans in case they are updated outside later.
+ final SpannableStringBuilder spannableString =
+ new SpannableStringBuilder(charSequence, start, end);
+ // SpellCheckSpan is for internal use. We do not want to marshal this for TextService.
+ final SpellCheckSpan[] spans = spannableString.getSpans(0, spannableString.length(),
+ SpellCheckSpan.class);
+ for (int i = 0; i < spans.length; ++i) {
+ spannableString.removeSpan(spans[i]);
+ }
+
+ mCharSequence = spannableString;
+ mCookie = cookie;
+ mSequenceNumber = sequenceNumber;
+ }
+
+ public TextInfo(Parcel source) {
+ mCharSequence = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mCookie = source.readInt();
+ mSequenceNumber = source.readInt();
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ TextUtils.writeToParcel(mCharSequence, dest, flags);
+ dest.writeInt(mCookie);
+ dest.writeInt(mSequenceNumber);
+ }
+
+ /**
+ * @return the text which is an input of a text service
+ */
+ public String getText() {
+ if (mCharSequence == null) {
+ return null;
+ }
+ return mCharSequence.toString();
+ }
+
+ /**
+ * @return the charSequence which is an input of a text service. This may have some parcelable
+ * spans.
+ */
+ public CharSequence getCharSequence() {
+ return mCharSequence;
+ }
+
+ /**
+ * @return the cookie of TextInfo
+ */
+ public int getCookie() {
+ return mCookie;
+ }
+
+ /**
+ * @return the sequence of TextInfo
+ */
+ public int getSequence() {
+ return mSequenceNumber;
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<TextInfo> CREATOR
+ = new Parcelable.Creator<TextInfo>() {
+ @Override
+ public TextInfo createFromParcel(Parcel source) {
+ return new TextInfo(source);
+ }
+
+ @Override
+ public TextInfo[] newArray(int size) {
+ return new TextInfo[size];
+ }
+ };
+
+ /**
+ * Used to make this class parcelable.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
new file mode 100644
index 00000000..f368c74a
--- /dev/null
+++ b/android/view/textservice/TextServicesManager.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2011 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.textservice;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
+import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
+
+import com.android.internal.textservice.ITextServicesManager;
+
+import java.util.Locale;
+
+/**
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ *
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the text services
+ * framework (TSF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>text services manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts. It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> A <strong>text service</strong> implements a particular
+ * interaction model allowing the client application to retrieve information of text.
+ * The system binds to the current text service that is in use, causing it to be created and run.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the text service
+ * manager for connections to text services.
+ * </ul>
+ *
+ * <h3>Text services sessions</h3>
+ * <ul>
+ * <li>The <strong>spell checker session</strong> is one of the text services.
+ * {@link android.view.textservice.SpellCheckerSession}</li>
+ * </ul>
+ *
+ */
+@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
+public final class TextServicesManager {
+ private static final String TAG = TextServicesManager.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ private static TextServicesManager sInstance;
+
+ private final ITextServicesManager mService;
+
+ private TextServicesManager() throws ServiceNotFoundException {
+ mService = ITextServicesManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
+ }
+
+ /**
+ * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
+ * @hide
+ */
+ public static TextServicesManager getInstance() {
+ synchronized (TextServicesManager.class) {
+ if (sInstance == null) {
+ try {
+ sInstance = new TextServicesManager();
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Returns the language component of a given locale string.
+ */
+ private static String parseLanguageFromLocaleString(String locale) {
+ final int idx = locale.indexOf('_');
+ if (idx < 0) {
+ return locale;
+ } else {
+ return locale.substring(0, idx);
+ }
+ }
+
+ /**
+ * Get a spell checker session for the specified spell checker
+ * @param locale the locale for the spell checker. If {@code locale} is null and
+ * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+ * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
+ * the locale specified in Settings will be returned only when it is same as {@code locale}.
+ * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
+ * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+ * selected.
+ * @param listener a spell checker session lister for getting results from a spell checker.
+ * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
+ * languages in settings will be returned.
+ * @return the spell checker session of the spell checker
+ */
+ public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
+ SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
+ if (listener == null) {
+ throw new NullPointerException();
+ }
+ if (!referToSpellCheckerLanguageSettings && locale == null) {
+ throw new IllegalArgumentException("Locale should not be null if you don't refer"
+ + " settings.");
+ }
+
+ if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+ return null;
+ }
+
+ final SpellCheckerInfo sci;
+ try {
+ sci = mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ return null;
+ }
+ if (sci == null) {
+ return null;
+ }
+ SpellCheckerSubtype subtypeInUse = null;
+ if (referToSpellCheckerLanguageSettings) {
+ subtypeInUse = getCurrentSpellCheckerSubtype(true);
+ if (subtypeInUse == null) {
+ return null;
+ }
+ if (locale != null) {
+ final String subtypeLocale = subtypeInUse.getLocale();
+ final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+ if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
+ return null;
+ }
+ }
+ } else {
+ final String localeStr = locale.toString();
+ for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+ final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+ final String tempSubtypeLocale = subtype.getLocale();
+ final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
+ if (tempSubtypeLocale.equals(localeStr)) {
+ subtypeInUse = subtype;
+ break;
+ } else if (tempSubtypeLanguage.length() >= 2 &&
+ locale.getLanguage().equals(tempSubtypeLanguage)) {
+ subtypeInUse = subtype;
+ }
+ }
+ }
+ if (subtypeInUse == null) {
+ return null;
+ }
+ final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
+ try {
+ mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
+ session.getTextServicesSessionListener(),
+ session.getSpellCheckerSessionListener(), bundle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return session;
+ }
+
+ /**
+ * @hide
+ */
+ public SpellCheckerInfo[] getEnabledSpellCheckers() {
+ try {
+ final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
+ if (DBG) {
+ Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
+ }
+ return retval;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public SpellCheckerInfo getCurrentSpellChecker() {
+ try {
+ // Passing null as a locale for ICS
+ return mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
+ boolean allowImplicitlySelectedSubtype) {
+ try {
+ // Passing null as a locale until we support multiple enabled spell checker subtypes.
+ return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isSpellCheckerEnabled() {
+ try {
+ return mService.isSpellCheckerEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}