summaryrefslogtreecommitdiff
path: root/android/view/ViewRootImpl.java
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/ViewRootImpl.java
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/ViewRootImpl.java')
-rw-r--r--android/view/ViewRootImpl.java7976
1 files changed, 7976 insertions, 0 deletions
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();
+ }
+ }
+}