| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.view; |
| |
| import android.Manifest; |
| import android.animation.LayoutTransition; |
| import android.app.ActivityManagerNative; |
| import android.content.ClipDescription; |
| import android.content.ComponentCallbacks; |
| import android.content.ComponentCallbacks2; |
| import android.content.Context; |
| 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.Matrix; |
| import android.graphics.Paint; |
| 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.Drawable; |
| import android.hardware.display.DisplayManager; |
| import android.hardware.display.DisplayManager.DisplayListener; |
| import android.media.AudioManager; |
| import android.os.Binder; |
| 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.PowerManager; |
| 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.Slog; |
| import android.util.TypedValue; |
| import android.view.Surface.OutOfResourcesException; |
| import android.view.View.AttachInfo; |
| import android.view.View.MeasureSpec; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityManager; |
| import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| import android.view.accessibility.AccessibilityNodeProvider; |
| import android.view.accessibility.IAccessibilityInteractionConnection; |
| import android.view.accessibility.IAccessibilityInteractionConnectionCallback; |
| import android.view.animation.AccelerateDecelerateInterpolator; |
| import android.view.animation.Interpolator; |
| import android.view.inputmethod.InputConnection; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.Scroller; |
| |
| import com.android.internal.R; |
| import com.android.internal.os.SomeArgs; |
| import com.android.internal.policy.PolicyManager; |
| import com.android.internal.view.BaseSurfaceHolder; |
| import com.android.internal.view.RootViewSurfaceTaker; |
| |
| 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; |
| |
| /** |
| * 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, HardwareRenderer.HardwareDrawCallbacks { |
| 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; |
| |
| /** |
| * 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"; |
| private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media"; |
| |
| /** |
| * 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<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); |
| |
| static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>(); |
| static boolean sFirstDrawComplete = false; |
| |
| static final ArrayList<ComponentCallbacks> sConfigCallbacks |
| = new ArrayList<ComponentCallbacks>(); |
| |
| final Context mContext; |
| final IWindowSession mWindowSession; |
| final 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; |
| |
| int mViewVisibility; |
| boolean mAppVisible = true; |
| int mOrigWindowType = -1; |
| |
| // 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; |
| |
| boolean mLastInCompatMode = false; |
| |
| SurfaceHolder.Callback2 mSurfaceHolderCallback; |
| BaseSurfaceHolder mSurfaceHolder; |
| boolean mIsCreating; |
| boolean mDrawingAllowed; |
| |
| final Region mTransparentRegion; |
| final Region mPreviousTransparentRegion; |
| |
| int mWidth; |
| int mHeight; |
| Rect mDirty; |
| final Rect mCurrentDirty = new Rect(); |
| boolean mIsAnimating; |
| |
| 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. |
| |
| 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 mFitSystemWindowsRequested; |
| boolean mLayoutRequested; |
| boolean mFirst; |
| boolean mReportNextDraw; |
| boolean mFullRedrawNeeded; |
| boolean mNewSurfaceNeeded; |
| boolean mHasHadWindowFocus; |
| boolean mLastWasImTarget; |
| boolean mWindowsAnimating; |
| boolean mDrawDuringWindowsAnimating; |
| 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; |
| String mPendingInputEventQueueLengthCounterName = "pq"; |
| |
| InputStage mFirstInputStage; |
| InputStage mFirstPostImeInputStage; |
| |
| 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()). |
| private final Surface mSurface = new Surface(); |
| |
| boolean mAdded; |
| boolean mAddedTouchMode; |
| |
| final DisplayAdjustments mDisplayAdjustments; |
| |
| // 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 mPendingContentInsets = new Rect(); |
| final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets |
| = new ViewTreeObserver.InternalInsetsInfo(); |
| |
| final Rect mFitSystemWindowsInsets = new Rect(); |
| |
| final Configuration mLastConfiguration = new Configuration(); |
| final Configuration mPendingConfiguration = new Configuration(); |
| |
| boolean mScrollMayChange; |
| int mSoftInputMode; |
| WeakReference<View> mLastScrolledFocus; |
| int mScrollY; |
| int mCurScrollY; |
| Scroller mScroller; |
| HardwareLayer mResizeBuffer; |
| long mResizeBufferStartTime; |
| int mResizeBufferDuration; |
| // Used to block the creation of the ResizeBuffer due to invalidations in |
| // the previous DisplayList tree that must prevent re-execution. |
| // Currently this means a functor was detached. |
| boolean mBlockResizeBuffer; |
| 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(); |
| |
| private boolean mProfileRendering; |
| private Choreographer.FrameCallback mRenderProfiler; |
| private boolean mRenderProfilingEnabled; |
| |
| private boolean mMediaDisabled; |
| |
| // Variables to track frames per second, enabled via DEBUG_FPS flag |
| private long mFpsStartTime = -1; |
| private long mFpsPrevTime = -1; |
| private int mFpsNumFrames; |
| |
| /** |
| * see {@link #playSoundEffect(int)} |
| */ |
| AudioManager mAudioManager; |
| |
| final AccessibilityManager mAccessibilityManager; |
| |
| AccessibilityInteractionController mAccessibilityInteractionController; |
| |
| AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager; |
| |
| 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; |
| |
| /** |
| * 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; |
| } |
| |
| public ViewRootImpl(Context context, Display display) { |
| mContext = context; |
| mWindowSession = WindowManagerGlobal.getWindowSession(); |
| mDisplay = display; |
| mBasePackageName = context.getBasePackageName(); |
| |
| mDisplayAdjustments = display.getDisplayAdjustments(); |
| |
| 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; |
| mAccessibilityManager = AccessibilityManager.getInstance(context); |
| mAccessibilityInteractionConnectionManager = |
| new AccessibilityInteractionConnectionManager(); |
| mAccessibilityManager.addAccessibilityStateChangeListener( |
| mAccessibilityInteractionConnectionManager); |
| mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); |
| mViewConfiguration = ViewConfiguration.get(context); |
| mDensity = context.getResources().getDisplayMetrics().densityDpi; |
| mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; |
| mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context); |
| mChoreographer = Choreographer.getInstance(); |
| mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); |
| loadSystemProperties(); |
| } |
| |
| public static void addFirstDrawHandler(Runnable callback) { |
| synchronized (sFirstDrawHandlers) { |
| if (!sFirstDrawComplete) { |
| sFirstDrawHandlers.add(callback); |
| } |
| } |
| } |
| |
| public static void addConfigCallback(ComponentCallbacks callback) { |
| synchronized (sConfigCallbacks) { |
| sConfigCallbacks.add(callback); |
| } |
| } |
| |
| // 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; |
| } |
| |
| /** |
| * 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; |
| // 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); |
| } |
| } |
| |
| CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); |
| mTranslator = compatibilityInfo.getTranslator(); |
| mDisplayAdjustments.setActivityToken(attrs.token); |
| |
| // 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(TAG, "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(); |
| } |
| try { |
| mOrigWindowType = mWindowAttributes.type; |
| mAttachInfo.mRecomputeGlobalAttributes = true; |
| collectViewAttributes(); |
| res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, |
| getHostVisibility(), mDisplay.getDisplayId(), |
| mAttachInfo.mContentInsets, 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); |
| mPendingVisibleInsets.set(0, 0, 0, 0); |
| if (DEBUG_LAYOUT) Log.v(TAG, "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 this type already exists"); |
| case WindowManagerGlobal.ADD_PERMISSION_DENIED: |
| throw new WindowManager.BadTokenException( |
| "Unable to add window " + mWindow + |
| " -- permission denied for this window type"); |
| case WindowManagerGlobal.ADD_INVALID_DISPLAY: |
| throw new WindowManager.InvalidDisplayException( |
| "Unable to add window " + mWindow + |
| " -- the specified display can not be found"); |
| } |
| 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(); |
| InputStage syntheticInputStage = new SyntheticInputStage(); |
| InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage); |
| 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; |
| } |
| } |
| } |
| |
| /** Whether the window is in local focus mode or not */ |
| private boolean isInLocalFocusMode() { |
| return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; |
| } |
| |
| void destroyHardwareResources() { |
| if (mAttachInfo.mHardwareRenderer != null) { |
| mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView); |
| mAttachInfo.mHardwareRenderer.destroy(false); |
| } |
| } |
| |
| void destroyHardwareLayers() { |
| if (mThread != Thread.currentThread()) { |
| if (mAttachInfo.mHardwareRenderer != null && |
| mAttachInfo.mHardwareRenderer.isEnabled()) { |
| HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE); |
| } |
| } else { |
| destroyHardwareLayer(mView); |
| } |
| } |
| |
| private static void destroyHardwareLayer(View view) { |
| view.destroyLayer(true); |
| |
| if (view instanceof ViewGroup) { |
| ViewGroup group = (ViewGroup) view; |
| |
| int count = group.getChildCount(); |
| for (int i = 0; i < count; i++) { |
| destroyHardwareLayer(group.getChildAt(i)); |
| } |
| } |
| } |
| |
| void flushHardwareLayerUpdates() { |
| if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { |
| mAttachInfo.mHardwareRenderer.flushLayerUpdates(); |
| } |
| } |
| |
| void dispatchFlushHardwareLayerUpdates() { |
| mHandler.removeMessages(MSG_FLUSH_LAYER_UPDATES); |
| mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_FLUSH_LAYER_UPDATES)); |
| } |
| |
| public void attachFunctor(long functor) { |
| //noinspection SimplifiableIfStatement |
| if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { |
| mAttachInfo.mHardwareRenderer.attachFunctor(mAttachInfo, functor); |
| } |
| } |
| |
| public void detachFunctor(long functor) { |
| mBlockResizeBuffer = true; |
| if (mAttachInfo.mHardwareRenderer != null) { |
| mAttachInfo.mHardwareRenderer.detachFunctor(functor); |
| } |
| } |
| |
| 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 (!HardwareRenderer.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 (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled |
| && forceHwAccelerated)) { |
| // Don't enable hardware acceleration when we're not on the main thread |
| if (!HardwareRenderer.sSystemRendererDisabled && |
| Looper.getMainLooper() != Looper.myLooper()) { |
| Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " |
| + "acceleration outside of the main thread, aborting"); |
| return; |
| } |
| |
| if (mAttachInfo.mHardwareRenderer != null) { |
| mAttachInfo.mHardwareRenderer.destroy(true); |
| } |
| |
| final boolean translucent = attrs.format != PixelFormat.OPAQUE; |
| mAttachInfo.mHardwareRenderer = HardwareRenderer.create(translucent); |
| if (mAttachInfo.mHardwareRenderer != null) { |
| mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString()); |
| mAttachInfo.mHardwareAccelerated = |
| mAttachInfo.mHardwareAccelerationRequested = true; |
| } |
| } else if (fakeHwAccelerated) { |
| // The window had wanted to use hardware acceleration, but this |
| // is not allowed in its process. By setting this flag, it can |
| // still render as if it was accelerated. This is basically for |
| // the preview windows the window manager shows for launching |
| // applications, so they will look more like the app being launched. |
| mAttachInfo.mHardwareAccelerationRequested = true; |
| } |
| } |
| } |
| |
| public View getView() { |
| return mView; |
| } |
| |
| final WindowLeaked getLocation() { |
| return mLocation; |
| } |
| |
| void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { |
| synchronized (this) { |
| int oldSoftInputMode = mWindowAttributes.softInputMode; |
| // Keep track of the actual window flags supplied by the client. |
| mClientWindowLayoutFlags = attrs.flags; |
| // preserve compatible window flag if exists. |
| 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 (mWindowAttributes.packageName == null) { |
| mWindowAttributes.packageName = mBasePackageName; |
| } |
| mWindowAttributes.privateFlags |= compatibleWindowFlag; |
| |
| 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; |
| scheduleTraversals(); |
| } |
| } |
| |
| 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; |
| 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; |
| } |
| }; |
| |
| @Override |
| public void requestFitSystemWindows() { |
| checkThread(); |
| mFitSystemWindowsRequested = true; |
| scheduleTraversals(); |
| } |
| |
| @Override |
| public void requestLayout() { |
| if (!mHandlingLayoutInLayoutRequest) { |
| checkThread(); |
| mLayoutRequested = true; |
| scheduleTraversals(); |
| } |
| } |
| |
| @Override |
| public boolean isLayoutRequested() { |
| return mLayoutRequested; |
| } |
| |
| void invalidate() { |
| mDirty.set(0, 0, mWidth, mHeight); |
| 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(TAG, "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); |
| } |
| } |
| |
| 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(); |
| } |
| |
| return null; |
| } |
| |
| void setStopped(boolean stopped) { |
| if (mStopped != stopped) { |
| mStopped = stopped; |
| if (!stopped) { |
| scheduleTraversals(); |
| } |
| } |
| } |
| |
| @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 ? mView.getVisibility() : View.GONE; |
| } |
| |
| void disposeResizeBuffer() { |
| if (mResizeBuffer != null) { |
| mResizeBuffer.destroy(); |
| mResizeBuffer = null; |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| |
| void scheduleTraversals() { |
| if (!mTraversalScheduled) { |
| mTraversalScheduled = true; |
| mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); |
| mChoreographer.postCallback( |
| Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); |
| scheduleConsumeBatchedInput(); |
| } |
| } |
| |
| void unscheduleTraversals() { |
| if (mTraversalScheduled) { |
| mTraversalScheduled = false; |
| mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); |
| mChoreographer.removeCallbacks( |
| Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); |
| } |
| } |
| |
| void doTraversal() { |
| if (mTraversalScheduled) { |
| mTraversalScheduled = false; |
| mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); |
| |
| if (mProfile) { |
| Debug.startMethodTracing("ViewAncestor"); |
| } |
| |
| Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); |
| try { |
| performTraversals(); |
| } finally { |
| Trace.traceEnd(Trace.TRACE_TAG_VIEW); |
| } |
| |
| 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() { |
| final View.AttachInfo attachInfo = mAttachInfo; |
| if (attachInfo.mRecomputeGlobalAttributes) { |
| //Log.i(TAG, "Computing view hierarchy attributes!"); |
| attachInfo.mRecomputeGlobalAttributes = false; |
| boolean oldScreenOn = attachInfo.mKeepScreenOn; |
| attachInfo.mKeepScreenOn = false; |
| attachInfo.mSystemUiVisibility = 0; |
| attachInfo.mHasSystemUiListeners = false; |
| mView.dispatchCollectViewAttributes(attachInfo, 0); |
| attachInfo.mSystemUiVisibility &= ~attachInfo.mDisabledSystemUiVisibility; |
| WindowManager.LayoutParams params = mWindowAttributes; |
| attachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params); |
| if (attachInfo.mKeepScreenOn != oldScreenOn |
| || attachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility |
| || attachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) { |
| applyKeepScreenOnFlag(params); |
| params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility; |
| params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners; |
| mView.dispatchWindowSystemUiVisiblityChanged(attachInfo.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(TAG, |
| "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(TAG, "Window " + mView + ": baseSize=" + baseSize); |
| if (baseSize != 0 && desiredWindowWidth > baseSize) { |
| childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); |
| childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); |
| performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); |
| if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" |
| + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); |
| 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(TAG, "Window " + mView + ": next baseSize=" |
| + baseSize); |
| childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); |
| performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); |
| if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" |
| + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); |
| if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { |
| if (DEBUG_DIALOG) Log.v(TAG, "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) { |
| final View.AttachInfo attachInfo = mAttachInfo; |
| m.postTranslate(attachInfo.mWindowLeft, attachInfo.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) { |
| final View.AttachInfo attachInfo = mAttachInfo; |
| m.preTranslate(-attachInfo.mWindowLeft, -attachInfo.mWindowTop); |
| } |
| |
| 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 View.AttachInfo attachInfo = mAttachInfo; |
| |
| final int viewVisibility = getHostVisibility(); |
| boolean viewVisibilityChanged = mViewVisibility != viewVisibility |
| || mNewSurfaceNeeded; |
| |
| WindowManager.LayoutParams params = null; |
| if (mWindowAttributesChanged) { |
| mWindowAttributesChanged = false; |
| surfaceChanged = true; |
| params = lp; |
| } |
| CompatibilityInfo compatibilityInfo = mDisplayAdjustments.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; |
| |
| if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL |
| || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { |
| // NOTE -- system code, won't try to do compat mode. |
| Point size = new Point(); |
| mDisplay.getRealSize(size); |
| desiredWindowWidth = size.x; |
| desiredWindowHeight = size.y; |
| } else { |
| DisplayMetrics packageMetrics = |
| mView.getContext().getResources().getDisplayMetrics(); |
| desiredWindowWidth = packageMetrics.widthPixels; |
| desiredWindowHeight = packageMetrics.heightPixels; |
| } |
| |
| // 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 |
| attachInfo.mUse32BitDrawingCache = true; |
| attachInfo.mHasWindowFocus = false; |
| attachInfo.mWindowVisibility = viewVisibility; |
| attachInfo.mRecomputeGlobalAttributes = false; |
| viewVisibilityChanged = false; |
| mLastConfiguration.setTo(host.getResources().getConfiguration()); |
| 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(mLastConfiguration.getLayoutDirection()); |
| } |
| host.dispatchAttachedToWindow(attachInfo, 0); |
| attachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); |
| mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); |
| host.fitSystemWindows(mFitSystemWindowsInsets); |
| //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); |
| |
| } else { |
| desiredWindowWidth = frame.width(); |
| desiredWindowHeight = frame.height(); |
| if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { |
| if (DEBUG_ORIENTATION) Log.v(TAG, |
| "View " + host + " resized to: " + frame); |
| mFullRedrawNeeded = true; |
| mLayoutRequested = true; |
| windowSizeMayChange = true; |
| } |
| } |
| |
| if (viewVisibilityChanged) { |
| attachInfo.mWindowVisibility = viewVisibility; |
| host.dispatchWindowVisibilityChanged(viewVisibility); |
| if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { |
| 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; |
| } |
| } |
| |
| // Execute enqueued actions on every traversal in case a detached view enqueued an action |
| getRunQueue().executeActions(attachInfo.mHandler); |
| |
| boolean insetsChanged = false; |
| |
| boolean layoutRequested = mLayoutRequested && !mStopped; |
| 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 (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { |
| mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); |
| if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " |
| + mAttachInfo.mVisibleInsets); |
| } |
| if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT |
| || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { |
| windowSizeMayChange = true; |
| |
| if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL |
| || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { |
| // NOTE -- system code, won't try to do compat mode. |
| Point size = new Point(); |
| mDisplay.getRealSize(size); |
| desiredWindowWidth = size.x; |
| desiredWindowHeight = size.y; |
| } else { |
| DisplayMetrics packageMetrics = res.getDisplayMetrics(); |
| desiredWindowWidth = packageMetrics.widthPixels; |
| desiredWindowHeight = packageMetrics.heightPixels; |
| } |
| } |
| } |
| |
| // Ask host how big it wants to be |
| windowSizeMayChange |= measureHierarchy(host, lp, res, |
| desiredWindowWidth, desiredWindowHeight); |
| } |
| |
| if (collectViewAttributes()) { |
| params = lp; |
| } |
| if (attachInfo.mForceReportNewAttributes) { |
| attachInfo.mForceReportNewAttributes = false; |
| params = lp; |
| } |
| |
| if (mFirst || attachInfo.mViewVisibilityChanged) { |
| attachInfo.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 = attachInfo.mScrollContainers.size(); |
| for (int i=0; i<N; i++) { |
| if (attachInfo.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 (mFitSystemWindowsRequested) { |
| mFitSystemWindowsRequested = false; |
| mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); |
| mLastOverscanRequested = mAttachInfo.mOverscanRequested; |
| host.fitSystemWindows(mFitSystemWindowsInsets); |
| 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)); |
| |
| // 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 = |
| attachInfo.mTreeObserver.hasComputeInternalInsetsListeners() |
| || attachInfo.mHasNonEmptyGivenInternalInsets; |
| |
| boolean insetsPending = false; |
| int relayoutResult = 0; |
| |
| if (mFirst || windowShouldResize || insetsChanged || |
| viewVisibilityChanged || params != null) { |
| |
| if (viewVisibility == View.VISIBLE) { |
| // 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(TAG, "host=w:" + host.getMeasuredWidth() + ", h:" + |
| host.getMeasuredHeight() + ", params=" + params); |
| } |
| |
| if (mAttachInfo.mHardwareRenderer != 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. |
| mAttachInfo.mHardwareRenderer.pauseSurface(mSurface); |
| } |
| final int surfaceGenerationId = mSurface.getGenerationId(); |
| relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); |
| if (!mDrawDuringWindowsAnimating && |
| (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) { |
| mWindowsAnimating = true; |
| } |
| |
| if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() |
| + " overscan=" + mPendingOverscanInsets.toShortString() |
| + " content=" + mPendingContentInsets.toShortString() |
| + " visible=" + mPendingVisibleInsets.toShortString() |
| + " surface=" + mSurface); |
| |
| if (mPendingConfiguration.seq != 0) { |
| if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: " |
| + mPendingConfiguration); |
| updateConfiguration(mPendingConfiguration, !mFirst); |
| mPendingConfiguration.seq = 0; |
| } |
| |
| final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals( |
| mAttachInfo.mOverscanInsets); |
| contentInsetsChanged = !mPendingContentInsets.equals( |
| mAttachInfo.mContentInsets); |
| final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals( |
| mAttachInfo.mVisibleInsets); |
| if (contentInsetsChanged) { |
| if (mWidth > 0 && mHeight > 0 && lp != null && |
| ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility) |
| & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 && |
| mSurface != null && mSurface.isValid() && |
| !mAttachInfo.mTurnOffWindowResizeAnim && |
| mAttachInfo.mHardwareRenderer != null && |
| mAttachInfo.mHardwareRenderer.isEnabled() && |
| lp != null && !PixelFormat.formatHasAlpha(lp.format) |
| && !mBlockResizeBuffer) { |
| |
| disposeResizeBuffer(); |
| |
| if (mResizeBuffer == null) { |
| mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer( |
| mWidth, mHeight); |
| } |
| mResizeBuffer.prepare(mWidth, mHeight, false); |
| RenderNode layerRenderNode = mResizeBuffer.startRecording(); |
| HardwareCanvas layerCanvas = layerRenderNode.start(mWidth, mHeight); |
| final int restoreCount = layerCanvas.save(); |
| |
| int yoff; |
| final boolean scrolling = mScroller != null |
| && mScroller.computeScrollOffset(); |
| if (scrolling) { |
| yoff = mScroller.getCurrY(); |
| mScroller.abortAnimation(); |
| } else { |
| yoff = mScrollY; |
| } |
| |
| layerCanvas.translate(0, -yoff); |
| if (mTranslator != null) { |
| mTranslator.translateCanvas(layerCanvas); |
| } |
| |
| RenderNode renderNode = mView.mRenderNode; |
| if (renderNode != null && renderNode.isValid()) { |
| layerCanvas.drawDisplayList(renderNode, null, |
| RenderNode.FLAG_CLIP_CHILDREN); |
| } else { |
| mView.draw(layerCanvas); |
| } |
| |
| drawAccessibilityFocusedDrawableIfNeeded(layerCanvas); |
| |
| mResizeBufferStartTime = SystemClock.uptimeMillis(); |
| mResizeBufferDuration = mView.getResources().getInteger( |
| com.android.internal.R.integer.config_mediumAnimTime); |
| |
| layerCanvas.restoreToCount(restoreCount); |
| layerRenderNode.end(layerCanvas); |
| layerRenderNode.setCaching(true); |
| layerRenderNode.setLeftTopRightBottom(0, 0, mWidth, mHeight); |
| mTempRect.set(0, 0, mWidth, mHeight); |
| mResizeBuffer.endRecording(mTempRect); |
| mAttachInfo.mHardwareRenderer.flushLayerUpdates(); |
| } |
| mAttachInfo.mContentInsets.set(mPendingContentInsets); |
| if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " |
| + mAttachInfo.mContentInsets); |
| } |
| if (overscanInsetsChanged) { |
| mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets); |
| if (DEBUG_LAYOUT) Log.v(TAG, "Overscan insets changing to: " |
| + mAttachInfo.mOverscanInsets); |
| // Need to relayout with content insets. |
| contentInsetsChanged = true; |
| } |
| if (contentInsetsChanged || mLastSystemUiVisibility != |
| mAttachInfo.mSystemUiVisibility || mFitSystemWindowsRequested |
| || mLastOverscanRequested != mAttachInfo.mOverscanRequested) { |
| mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; |
| mLastOverscanRequested = mAttachInfo.mOverscanRequested; |
| mFitSystemWindowsRequested = false; |
| mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); |
| host.fitSystemWindows(mFitSystemWindowsInsets); |
| } |
| if (visibleInsetsChanged) { |
| mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); |
| if (DEBUG_LAYOUT) Log.v(TAG, "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(); |
| |
| if (mAttachInfo.mHardwareRenderer != null) { |
| try { |
| hwInitialized = mAttachInfo.mHardwareRenderer.initialize( |
| mSurface); |
| } 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 (mScroller != null) { |
| mScroller.abortAnimation(); |
| } |
| disposeResizeBuffer(); |
| // Our surface is gone |
| if (mAttachInfo.mHardwareRenderer != null && |
| mAttachInfo.mHardwareRenderer.isEnabled()) { |
| mAttachInfo.mHardwareRenderer.destroy(true); |
| } |
| } else if (surfaceGenerationId != mSurface.getGenerationId() && |
| mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) { |
| mFullRedrawNeeded = true; |
| try { |
| mAttachInfo.mHardwareRenderer.updateSurface(mSurface); |
| } catch (OutOfResourcesException e) { |
| handleOutOfResourcesException(e); |
| return; |
| } |
| } |
| } catch (RemoteException e) { |
| } |
| |
| if (DEBUG_ORIENTATION) Log.v( |
| TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface); |
| |
| attachInfo.mWindowLeft = frame.left; |
| attachInfo.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; |
| mSurfaceHolderCallback.surfaceCreated(mSurfaceHolder); |
| SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); |
| if (callbacks != null) { |
| for (SurfaceHolder.Callback c : callbacks) { |
| c.surfaceCreated(mSurfaceHolder); |
| } |
| } |
| surfaceChanged = true; |
| } |
| if (surfaceChanged) { |
| mSurfaceHolderCallback.surfaceChanged(mSurfaceHolder, |
| lp.format, mWidth, mHeight); |
| 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(); |
| mSurfaceHolderCallback.surfaceDestroyed(mSurfaceHolder); |
| if (callbacks != null) { |
| for (SurfaceHolder.Callback c : callbacks) { |
| c.surfaceDestroyed(mSurfaceHolder); |
| } |
| } |
| mSurfaceHolder.mSurfaceLock.lock(); |
| try { |
| mSurfaceHolder.mSurface = new Surface(); |
| } finally { |
| mSurfaceHolder.mSurfaceLock.unlock(); |
| } |
| } |
| } |
| |
| if (mAttachInfo.mHardwareRenderer != null && |
| mAttachInfo.mHardwareRenderer.isEnabled()) { |
| if (hwInitialized || |
| mWidth != mAttachInfo.mHardwareRenderer.getWidth() || |
| mHeight != mAttachInfo.mHardwareRenderer.getHeight()) { |
| mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight); |
| if (!hwInitialized) { |
| mAttachInfo.mHardwareRenderer.invalidate(mSurface); |
| mFullRedrawNeeded = true; |
| } |
| } |
| } |
| |
| if (!mStopped) { |
| boolean focusChangedDueToTouchMode = ensureTouchModeLocally( |
| (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); |
| if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() |
| || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { |
| int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); |
| int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); |
| |
| if (DEBUG_LAYOUT) Log.v(TAG, "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(TAG, |
| "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. |
| |
| // 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 = (attachInfo.mWindowLeft != frame.left |
| || attachInfo.mWindowTop != frame.top); |
| if (windowMoved) { |
| if (mTranslator != null) { |
| mTranslator.translateRectInScreenToAppWinFrame(frame); |
| } |
| attachInfo.mWindowLeft = frame.left; |
| attachInfo.mWindowTop = frame.top; |
| } |
| } |
| |
| final boolean didLayout = layoutRequested && !mStopped; |
| boolean triggerGlobalLayoutListener = didLayout |
| || attachInfo.mRecomputeGlobalAttributes; |
| if (didLayout) { |
| performLayout(lp, desiredWindowWidth, desiredWindowHeight); |
| |
| // 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) { |
| attachInfo.mRecomputeGlobalAttributes = false; |
| attachInfo.mTreeObserver.dispatchOnGlobalLayout(); |
| } |
| |
| if (computesInternalInsets) { |
| // Clear the original insets. |
| final ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets; |
| insets.reset(); |
| |
| // Compute new insets in place. |
| attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); |
| attachInfo.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) { |
| } |
| } |
| } |
| |
| boolean skipDraw = false; |
| |
| if (mFirst) { |
| // handle first focus request |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()=" |
| + mView.hasFocus()); |
| if (mView != null) { |
| if (!mView.hasFocus()) { |
| mView.requestFocus(View.FOCUS_FORWARD); |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view=" |
| + mView.findFocus()); |
| } else { |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view=" |
| + mView.findFocus()); |
| } |
| } |
| if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) { |
| // The first time we relayout the window, if the system is |
| // doing window animations, we want to hold of on any future |
| // draws until the animation is done. |
| mWindowsAnimating = true; |
| } |
| } else if (mWindowsAnimating) { |
| skipDraw = true; |
| } |
| |
| mFirst = false; |
| mWillDrawSoon = false; |
| mNewSurfaceNeeded = false; |
| mViewVisibility = viewVisibility; |
| |
| if (mAttachInfo.mHasWindowFocus && !isInLocalFocusMode()) { |
| final boolean imTarget = WindowManager.LayoutParams |
| .mayUseInputMethod(mWindowAttributes.flags); |
| if (imTarget != mLastWasImTarget) { |
| mLastWasImTarget = imTarget; |
| InputMethodManager imm = InputMethodManager.peekInstance(); |
| if (imm != null && imTarget) { |
| imm.startGettingWindowFocus(mView); |
| imm.onWindowFocus(mView, mView.findFocus(), |
| mWindowAttributes.softInputMode, |
| !mHasHadWindowFocus, mWindowAttributes.flags); |
| } |
| } |
| } |
| |
| // Remember if we must report the next draw. |
| if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { |
| mReportNextDraw = true; |
| } |
| |
| boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() || |
| viewVisibility != View.VISIBLE; |
| |
| if (!cancelDraw && !newSurface) { |
| if (!skipDraw || mReportNextDraw) { |
| if (mPendingTransitions != null && mPendingTransitions.size() > 0) { |
| for (int i = 0; i < mPendingTransitions.size(); ++i) { |
| mPendingTransitions.get(i).startChangingAnimations(); |
| } |
| mPendingTransitions.clear(); |
| } |
| |
| performDraw(); |
| } |
| } else { |
| if (viewVisibility == View.VISIBLE) { |
| // 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 handleOutOfResourcesException(Surface.OutOfResourcesException e) { |
| Log.e(TAG, "OutOfResourcesException initializing HW surface", e); |
| try { |
| if (!mWindowSession.outOfMemory(mWindow) && |
| Process.myUid() != Process.SYSTEM_UID) { |
| Slog.w(TAG, "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) { |
| 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 (DEBUG_ORIENTATION || DEBUG_LAYOUT) { |
| Log.v(TAG, "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 mHardwareYOffset; |
| int mResizeAlpha; |
| final Paint mResizePaint = new Paint(); |
| |
| @Override |
| public void onHardwarePreDraw(HardwareCanvas canvas) { |
| canvas.translate(0, -mHardwareYOffset); |
| } |
| |
| @Override |
| public void onHardwarePostDraw(HardwareCanvas canvas) { |
| if (mResizeBuffer != null) { |
| mResizePaint.setAlpha(mResizeAlpha); |
| canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint); |
| } |
| drawAccessibilityFocusedDrawableIfNeeded(canvas); |
| } |
| |
| /** |
| * @hide |
| */ |
| void outputDisplayList(View view) { |
| RenderNode renderNode = view.getDisplayList(); |
| if (renderNode != null) { |
| renderNode.output(); |
| } |
| } |
| |
| /** |
| * @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(TAG, "0x" + thisHash + "\tFrame time:\t" + frameTime); |
| mFpsPrevTime = nowTime; |
| if (totalTime > 1000) { |
| float fps = (float) mFpsNumFrames * 1000 / totalTime; |
| Log.v(TAG, "0x" + thisHash + "\tFPS:\t" + fps); |
| mFpsStartTime = nowTime; |
| mFpsNumFrames = 0; |
| } |
| } |
| } |
| |
| private void performDraw() { |
| if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { |
| 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); |
| } |
| |
| if (mReportNextDraw) { |
| mReportNextDraw = false; |
| if (mAttachInfo.mHardwareRenderer != null) { |
| mAttachInfo.mHardwareRenderer.fence(); |
| } |
| |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); |
| } |
| if (mSurfaceHolder != null && mSurface.isValid()) { |
| mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder); |
| SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); |
| if (callbacks != null) { |
| for (SurfaceHolder.Callback c : callbacks) { |
| if (c instanceof SurfaceHolder.Callback2) { |
| ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( |
| mSurfaceHolder); |
| } |
| } |
| } |
| } |
| try { |
| mWindowSession.finishDrawing(mWindow); |
| } catch (RemoteException e) { |
| } |
| } |
| } |
| |
| 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); |
| |
| final AttachInfo attachInfo = mAttachInfo; |
| if (attachInfo.mViewScrollChanged) { |
| attachInfo.mViewScrollChanged = false; |
| attachInfo.mTreeObserver.dispatchOnScrollChanged(); |
| } |
| |
| int yoff; |
| boolean animating = mScroller != null && mScroller.computeScrollOffset(); |
| if (animating) { |
| yoff = mScroller.getCurrY(); |
| } else { |
| yoff = mScrollY; |
| } |
| if (mCurScrollY != yoff) { |
| mCurScrollY = yoff; |
| fullRedrawNeeded = true; |
| } |
| |
| final float appScale = attachInfo.mApplicationScale; |
| final boolean scalingRequired = attachInfo.mScalingRequired; |
| |
| int resizeAlpha = 0; |
| if (mResizeBuffer != null) { |
| long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime; |
| if (deltaTime < mResizeBufferDuration) { |
| float amt = deltaTime/(float) mResizeBufferDuration; |
| amt = mResizeInterpolator.getInterpolation(amt); |
| animating = true; |
| resizeAlpha = 255 - (int)(amt*255); |
| } else { |
| disposeResizeBuffer(); |
| } |
| } |
| |
| final Rect dirty = mDirty; |
| if (mSurfaceHolder != null) { |
| // The app owns the surface, we won't draw. |
| dirty.setEmpty(); |
| if (animating) { |
| if (mScroller != null) { |
| mScroller.abortAnimation(); |
| } |
| disposeResizeBuffer(); |
| } |
| return; |
| } |
| |
| if (fullRedrawNeeded) { |
| attachInfo.mIgnoreDirtyState = true; |
| dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); |
| } |
| |
| if (DEBUG_ORIENTATION || DEBUG_DRAW) { |
| Log.v(TAG, "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); |
| } |
| |
| attachInfo.mTreeObserver.dispatchOnDraw(); |
| |
| if (!dirty.isEmpty() || mIsAnimating) { |
| if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { |
| // Draw with hardware renderer. |
| mIsAnimating = false; |
| mHardwareYOffset = yoff; |
| mResizeAlpha = resizeAlpha; |
| |
| mCurrentDirty.set(dirty); |
| dirty.setEmpty(); |
| |
| mBlockResizeBuffer = false; |
| attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, |
| animating ? null : mCurrentDirty); |
| } 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 (attachInfo.mHardwareRenderer != null && |
| !attachInfo.mHardwareRenderer.isEnabled() && |
| attachInfo.mHardwareRenderer.isRequested()) { |
| |
| try { |
| attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, |
| mSurface); |
| } catch (OutOfResourcesException e) { |
| handleOutOfResourcesException(e); |
| return; |
| } |
| |
| mFullRedrawNeeded = true; |
| scheduleTraversals(); |
| return; |
| } |
| |
| if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { |
| return; |
| } |
| } |
| } |
| |
| if (animating) { |
| mFullRedrawNeeded = true; |
| scheduleTraversals(); |
| } |
| } |
| |
| /** |
| * @return true if drawing was succesfull, false if an error occurred |
| */ |
| private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, |
| boolean scalingRequired, Rect dirty) { |
| |
| // Draw with software renderer. |
| Canvas canvas; |
| try { |
| int left = dirty.left; |
| int top = dirty.top; |
| int right = dirty.right; |
| 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(TAG, "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(TAG, "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) { |
| canvas.drawColor(0, PorterDuff.Mode.CLEAR); |
| } |
| |
| dirty.setEmpty(); |
| mIsAnimating = false; |
| attachInfo.mDrawingTime = SystemClock.uptimeMillis(); |
| mView.mPrivateFlags |= View.PFLAG_DRAWN; |
| |
| if (DEBUG_DRAW) { |
| Context cxt = mView.getContext(); |
| Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + |
| ", metrics=" + cxt.getResources().getDisplayMetrics() + |
| ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); |
| } |
| try { |
| canvas.translate(0, -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(TAG, "Could not unlock surface", e); |
| mLayoutRequested = true; // ask wm for a new surface next time. |
| //noinspection ReturnInsideFinallyBlock |
| return false; |
| } |
| |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "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) { |
| if (!mAttachInfo.mHasWindowFocus) { |
| return; |
| } |
| |
| final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext); |
| if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) { |
| return; |
| } |
| |
| final View host = mAccessibilityFocusedHost; |
| if (host == null || host.mAttachInfo == null) { |
| return; |
| } |
| |
| final Drawable drawable = getAccessibilityFocusedDrawable(); |
| if (drawable == null) { |
| return; |
| } |
| |
| final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); |
| final Rect bounds = mView.mAttachInfo.mTmpInvalRect; |
| if (provider == null) { |
| host.getBoundsOnScreen(bounds); |
| } else if (mAccessibilityFocusedVirtualView != null) { |
| mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds); |
| } else { |
| return; |
| } |
| |
| bounds.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop); |
| bounds.intersect(0, 0, mAttachInfo.mViewRootImpl.mWidth, mAttachInfo.mViewRootImpl.mHeight); |
| drawable.setBounds(bounds); |
| drawable.draw(canvas); |
| } |
| |
| private Drawable getAccessibilityFocusedDrawable() { |
| if (mAttachInfo != null) { |
| // Lazily load the accessibility focus drawable. |
| if (mAttachInfo.mAccessibilityFocusDrawable == null) { |
| 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; |
| } |
| return null; |
| } |
| |
| /** |
| * @hide |
| */ |
| public void setDrawDuringWindowsAnimating(boolean value) { |
| mDrawDuringWindowsAnimating = value; |
| if (value) { |
| handleDispatchDoneAnimating(); |
| } |
| } |
| |
| boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) { |
| final View.AttachInfo attachInfo = mAttachInfo; |
| final Rect ci = attachInfo.mContentInsets; |
| final Rect vi = attachInfo.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(TAG, "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(TAG, "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(TAG, "Need to scroll?"); |
| // Try to find the rectangle from the focus view. |
| if (focus.getGlobalVisibleRect(mVisRect, null)) { |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, "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(TAG, "Focus " + focus |
| + ": focusRect=" + mTempRect.toShortString()); |
| if (mView instanceof ViewGroup) { |
| ((ViewGroup) mView).offsetDescendantRectToMyCoords( |
| focus, mTempRect); |
| } |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, |
| "Focus in window: focusRect=" |
| + mTempRect.toShortString() |
| + " visRect=" + mVisRect.toShortString()); |
| } else { |
| mTempRect.set(rectangle); |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, |
| "Request scroll to rect: " |
| + mTempRect.toShortString() |
| + " visRect=" + mVisRect.toShortString()); |
| } |
| if (mTempRect.intersect(mVisRect)) { |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, |
| "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(TAG, |
| "Too tall; leaving scrollY=" + scrollY); |
| } else if ((mTempRect.top-scrollY) < vi.top) { |
| scrollY -= vi.top - (mTempRect.top-scrollY); |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, |
| "Top covered; scrollY=" + scrollY); |
| } else if ((mTempRect.bottom-scrollY) |
| > (mView.getHeight()-vi.bottom)) { |
| scrollY += (mTempRect.bottom-scrollY) |
| - (mView.getHeight()-vi.bottom); |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, |
| "Bottom covered; scrollY=" + scrollY); |
| } |
| handled = true; |
| } |
| } |
| } |
| } |
| |
| if (scrollY != mScrollY) { |
| if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old=" |
| + mScrollY + " , new=" + scrollY); |
| if (!immediate && mResizeBuffer == null) { |
| 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(); |
| |
| 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) { |
| // Clear accessibility focus in the view. |
| mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks(); |
| } |
| |
| // Set the new focus host and node. |
| mAccessibilityFocusedHost = view; |
| mAccessibilityFocusedVirtualView = node; |
| } |
| |
| @Override |
| public void requestChildFocus(View child, View focused) { |
| if (DEBUG_INPUT_RESIZE) { |
| Log.v(TAG, "Request child focus: focus now " + focused); |
| } |
| checkThread(); |
| scheduleTraversals(); |
| } |
| |
| @Override |
| public void clearChildFocus(View child) { |
| if (DEBUG_INPUT_RESIZE) { |
| Log.v(TAG, "Clearing child focus"); |
| } |
| checkThread(); |
| scheduleTraversals(); |
| } |
| |
| @Override |
| public ViewParent getParentForAccessibility() { |
| return null; |
| } |
| |
| @Override |
| public void focusableViewAvailable(View v) { |
| checkThread(); |
| if (mView != null) { |
| if (!mView.hasFocus()) { |
| 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); |
| 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(); |
| } |
| |
| void updateConfiguration(Configuration config, boolean force) { |
| if (DEBUG_CONFIGURATION) Log.v(TAG, |
| "Applying new config to window " |
| + mWindowAttributes.getTitle() |
| + ": " + config); |
| |
| CompatibilityInfo ci = mDisplayAdjustments.getCompatibilityInfo(); |
| if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) { |
| config = new Configuration(config); |
| ci.applyToConfiguration(mNoncompatDensity, config); |
| } |
| |
| synchronized (sConfigCallbacks) { |
| for (int i=sConfigCallbacks.size()-1; i>=0; i--) { |
| sConfigCallbacks.get(i).onConfigurationChanged(config); |
| } |
| } |
| if (mView != null) { |
| // 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. |
| config = mView.getResources().getConfiguration(); |
| if (force || mLastConfiguration.diff(config) != 0) { |
| final int lastLayoutDirection = mLastConfiguration.getLayoutDirection(); |
| final int currentLayoutDirection = config.getLayoutDirection(); |
| mLastConfiguration.setTo(config); |
| if (lastLayoutDirection != currentLayoutDirection && |
| mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) { |
| mView.setLayoutDirection(currentLayoutDirection); |
| } |
| mView.dispatchConfigurationChanged(config); |
| } |
| } |
| } |
| |
| /** |
| * 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_FINISH_INPUT_CONNECTION = 12; |
| private final static int MSG_CHECK_FOCUS = 13; |
| private final static int MSG_CLOSE_SYSTEM_DIALOGS = 14; |
| private final static int MSG_DISPATCH_DRAG_EVENT = 15; |
| private final static int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16; |
| private final static int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17; |
| private final static int MSG_UPDATE_CONFIGURATION = 18; |
| private final static int MSG_PROCESS_INPUT_EVENTS = 19; |
| private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21; |
| private final static int MSG_DISPATCH_DONE_ANIMATING = 22; |
| private final static int MSG_INVALIDATE_WORLD = 23; |
| private final static int MSG_WINDOW_MOVED = 24; |
| private final static int MSG_FLUSH_LAYER_UPDATES = 25; |
| |
| 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_FINISH_INPUT_CONNECTION: |
| return "MSG_FINISH_INPUT_CONNECTION"; |
| 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_DISPATCH_DONE_ANIMATING: |
| return "MSG_DISPATCH_DONE_ANIMATING"; |
| case MSG_WINDOW_MOVED: |
| return "MSG_WINDOW_MOVED"; |
| case MSG_FLUSH_LAYER_UPDATES: |
| return "MSG_FLUSH_LAYER_UPDATES"; |
| } |
| return super.getMessageName(message); |
| } |
| |
| @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) |
| && mPendingVisibleInsets.equals(args.arg3) |
| && args.arg4 == null) { |
| break; |
| } |
| } // fall through... |
| case MSG_RESIZED_REPORT: |
| if (mAdded) { |
| SomeArgs args = (SomeArgs) msg.obj; |
| |
| Configuration config = (Configuration) args.arg4; |
| if (config != null) { |
| updateConfiguration(config, false); |
| } |
| |
| mWinFrame.set((Rect) args.arg1); |
| mPendingOverscanInsets.set((Rect) args.arg5); |
| mPendingContentInsets.set((Rect) args.arg2); |
| mPendingVisibleInsets.set((Rect) args.arg3); |
| |
| args.recycle(); |
| |
| if (msg.what == MSG_RESIZED_REPORT) { |
| mReportNextDraw = true; |
| } |
| |
| if (mView != null) { |
| 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; |
| |
| if (mView != null) { |
| forceLayout(mView); |
| } |
| requestLayout(); |
| } |
| 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.mHardwareRenderer != null && mSurface.isValid()){ |
| mFullRedrawNeeded = true; |
| try { |
| mAttachInfo.mHardwareRenderer.initializeIfNeeded( |
| mWidth, mHeight, mSurface); |
| } catch (OutOfResourcesException e) { |
| Log.e(TAG, "OutOfResourcesException locking surface", e); |
| try { |
| if (!mWindowSession.outOfMemory(mWindow)) { |
| Slog.w(TAG, "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 (mView != null) { |
| if (hasWindowFocus && imm != null && mLastWasImTarget && |
| !isInLocalFocusMode()) { |
| imm.startGettingWindowFocus(mView); |
| } |
| mAttachInfo.mKeyDispatchState.reset(); |
| mView.dispatchWindowFocusChanged(hasWindowFocus); |
| mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); |
| } |
| |
| // 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.onWindowFocus(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; |
| } |
| |
| if (mView != null && mAccessibilityManager.isEnabled()) { |
| if (hasWindowFocus) { |
| mView.sendAccessibilityEvent( |
| AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); |
| } |
| } |
| } |
| } break; |
| case MSG_DIE: |
| doDie(); |
| break; |
| case MSG_DISPATCH_INPUT_EVENT: { |
| InputEvent event = (InputEvent)msg.obj; |
| enqueueInputEvent(event, null, 0, 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_FINISH_INPUT_CONNECTION: { |
| InputMethodManager imm = InputMethodManager.peekInstance(); |
| if (imm != null) { |
| imm.reportFinishInputConnection((InputConnection)msg.obj); |
| } |
| } 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(mLastConfiguration)) { |
| config = mLastConfiguration; |
| } |
| updateConfiguration(config, false); |
| } break; |
| case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { |
| setAccessibilityFocus(null, null); |
| } break; |
| case MSG_DISPATCH_DONE_ANIMATING: { |
| handleDispatchDoneAnimating(); |
| } break; |
| case MSG_INVALIDATE_WORLD: { |
| if (mView != null) { |
| invalidateWorld(mView); |
| } |
| } break; |
| case MSG_FLUSH_LAYER_UPDATES: { |
| flushHardwareLayerUpdates(); |
| } 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 { |
| if (!isInLocalFocusMode()) { |
| 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 (mNext != null) { |
| mNext.deliver(q); |
| } else { |
| finishInputEvent(q); |
| } |
| } |
| |
| protected boolean shouldDropInputEvent(QueuedInputEvent q) { |
| if (mView == null || !mAdded) { |
| Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent); |
| return true; |
| } else if (!mAttachInfo.mHasWindowFocus && |
| !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && |
| !isTerminalInputEvent(q.mEvent)) { |
| // If this is a focused event and the window doesn't currently have input focus, |
| // then drop this event. This could be an event that came back from the previous |
| // stage but the window has lost focus in the meantime. |
| Slog.w(TAG, "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); |
| } |
| } |
| } |
| |
| /** |
| * 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(TAG, "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) { |
| return FINISH_NOT_HANDLED; |
| } 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 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. |
| final int action = event.getAction(); |
| if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) { |
| ensureTouchMode(true); |
| } |
| |
| // 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(); |
| } |
| 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 { |
| // If delivering a new non-key event, make sure the window is |
| // now allowed to start updating. |
| handleDispatchDoneAnimating(); |
| 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); |
| } |
| } |
| } |
| |
| private int processKeyEvent(QueuedInputEvent q) { |
| final KeyEvent event = (KeyEvent)q.mEvent; |
| |
| if (event.getAction() != KeyEvent.ACTION_UP) { |
| // If delivering a new key event, make sure the window is |
| // now allowed to start updating. |
| handleDispatchDoneAnimating(); |
| } |
| |
| // Deliver the key to the view hierarchy. |
| if (mView.dispatchKeyEvent(event)) { |
| return FINISH_HANDLED; |
| } |
| |
| if (shouldDropInputEvent(q)) { |
| return FINISH_NOT_HANDLED; |
| } |
| |
| // If the Control modifier is held, try to interpret the key as a shortcut. |
| if (event.getAction() == KeyEvent.ACTION_DOWN |
| && event.isCtrlPressed() |
| && event.getRepeatCount() == 0 |
| && !KeyEvent.isModifierKey(event.getKeyCode())) { |
| 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) { |
| 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 FINISH_HANDLED; |
| } |
| } |
| |
| // Give the focused view a last chance to handle the dpad key. |
| if (mView.dispatchUnhandledMove(focused, direction)) { |
| return FINISH_HANDLED; |
| } |
| } else { |
| // find the best view to give focus to in this non-touch-mode with no-focus |
| View v = focusSearch(null, direction); |
| if (v != null && v.requestFocus(direction)) { |
| return FINISH_HANDLED; |
| } |
| } |
| } |
| } |
| return FORWARD; |
| } |
| |
| private int processPointerEvent(QueuedInputEvent q) { |
| final MotionEvent event = (MotionEvent)q.mEvent; |
| |
| if (mView.dispatchPointerEvent(event)) { |
| return FINISH_HANDLED; |
| } |
| return FORWARD; |
| } |
| |
| private int processTrackballEvent(QueuedInputEvent q) { |
| final MotionEvent event = (MotionEvent)q.mEvent; |
| |
| 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; |
| } |
| } |
| |
| /** |
| * 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(); |
| |
| 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; |
| } |
| } |
| 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(TAG, "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(TAG, "Move: movement=" + movement |
| + " accelMovement=" + accelMovement |
| + " accel=" + accel); |
| if (accelMovement > movement) { |
| if (DEBUG_TRACKBALL) Log.v(TAG, "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(TAG, "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(TAG, "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); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * 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; |
| |
| 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 it the usual notifications and then we're done |
| // for now. |
| mView.dispatchDragEvent(event); |
| } else { |
| // 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 { |
| event.mClipDescription = mDragDescription; |
| } |
| |
| // 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; |
| |
| // Now dispatch the drag/drop event |
| boolean result = mView.dispatchDragEvent(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(TAG, "Unable to note drag target change"); |
| } |
| } |
| |
| // Report the drop result when we're done |
| if (what == DragEvent.ACTION_DROP) { |
| mDragDescription = null; |
| try { |
| Log.i(TAG, "Reporting drop result: " + result); |
| mWindowSession.reportDropResult(mWindow, result); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Unable to report drop result"); |
| } |
| } |
| |
| // When the drag operation ends, release any local state object |
| // that may have been in use |
| if (what == DragEvent.ACTION_DRAG_ENDED) { |
| setLocalDragState(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); |
| } |
| if (mAttachInfo != null) { |
| int visibility = args.globalVisibility&View.SYSTEM_UI_CLEARABLE_FLAGS; |
| if (visibility != mAttachInfo.mGlobalSystemUiVisibility) { |
| mAttachInfo.mGlobalSystemUiVisibility = visibility; |
| mView.dispatchSystemUiVisibilityChanged(visibility); |
| } |
| } |
| } |
| |
| public void handleDispatchDoneAnimating() { |
| if (mWindowsAnimating) { |
| mWindowsAnimating = false; |
| if (!mDirty.isEmpty() || mIsAnimating || mFullRedrawNeeded) { |
| scheduleTraversals(); |
| } |
| } |
| } |
| |
| public void getLastTouchPoint(Point outLocation) { |
| outLocation.x = (int) mLastTouchPoint.x; |
| outLocation.y = (int) mLastTouchPoint.y; |
| } |
| |
| public void setDragFocus(View newDragTarget) { |
| if (mCurrentDragView != newDragTarget) { |
| 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(TAG, "WindowLayout in layoutWindow:" + params); |
| } |
| mPendingConfiguration.seq = 0; |
| //Log.d(TAG, ">>>>>> CALLING relayout"); |
| if (params != null && mOrigWindowType != params.type) { |
| // For compatibility with old apps, don't crash here. |
| if (mTargetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { |
| Slog.w(TAG, "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, |
| mPendingConfiguration, mSurface); |
| //Log.d(TAG, "<<<<<< BACK FROM relayout"); |
| if (restore) { |
| params.restore(); |
| } |
| |
| if (mTranslator != null) { |
| mTranslator.translateRectInScreenToAppWinFrame(mWinFrame); |
| mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets); |
| mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets); |
| mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets); |
| } |
| return relayoutResult; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void playSoundEffect(int effectId) { |
| checkThread(); |
| |
| if (mMediaDisabled) { |
| return; |
| } |
| |
| 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(TAG, "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); |
| } |
| |
| 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("mPendingInputEventCount="); |
| writer.println(mPendingInputEventCount); |
| writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled="); |
| writer.println(mProcessInputEventsScheduled); |
| writer.print(innerPrefix); writer.print("mTraversalScheduled="); |
| writer.print(mTraversalScheduled); |
| 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] += 0; /* TODO: Memory used by RenderNodes (properties + DisplayLists) */ |
| } |
| |
| 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(TAG, "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(TAG, "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); |
| |
| // Media (used by sound effects) |
| mMediaDisabled = SystemProperties.getBoolean(PROPERTY_MEDIA_DISABLED, false); |
| |
| // Hardware rendering |
| if (mAttachInfo.mHardwareRenderer != null) { |
| if (mAttachInfo.mHardwareRenderer.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() { |
| AttachInfo attachInfo = mAttachInfo; |
| HardwareRenderer hardwareRenderer = attachInfo.mHardwareRenderer; |
| |
| if (hardwareRenderer != null) { |
| if (mView != null) { |
| hardwareRenderer.destroyHardwareResources(mView); |
| } |
| hardwareRenderer.destroy(true); |
| hardwareRenderer.setRequested(false); |
| |
| attachInfo.mHardwareRenderer = null; |
| attachInfo.mHardwareAccelerated = false; |
| } |
| } |
| |
| public void dispatchFinishInputConnection(InputConnection connection) { |
| Message msg = mHandler.obtainMessage(MSG_FINISH_INPUT_CONNECTION, connection); |
| mHandler.sendMessage(msg); |
| } |
| |
| public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets, |
| Rect visibleInsets, boolean reportDraw, Configuration newConfig) { |
| if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": frame=" + frame.toShortString() |
| + " contentInsets=" + contentInsets.toShortString() |
| + " visibleInsets=" + visibleInsets.toShortString() |
| + " reportDraw=" + reportDraw); |
| 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 && newConfig != null ? new Configuration(newConfig) : newConfig; |
| args.arg5 = sameProcessCall ? new Rect(overscanInsets) : overscanInsets; |
| msg.obj = args; |
| mHandler.sendMessage(msg); |
| } |
| |
| public void dispatchMoved(int newX, int newY) { |
| if (DEBUG_LAYOUT) Log.v(TAG, "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 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); |
| } |
| } |
| |
| 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) { |
| 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); |
| |
| 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 = 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); |
| } |
| |
| 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 doConsumeBatchedInput(long frameTimeNanos) { |
| if (mConsumeBatchedInputScheduled) { |
| mConsumeBatchedInputScheduled = false; |
| if (mInputEventReceiver != null) { |
| if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) { |
| // 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. |
| 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) { |
| enqueueInputEvent(event, this, 0, true); |
| } |
| |
| @Override |
| public void onBatchedInputEventPending() { |
| 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 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) { |
| Message msg = mHandler.obtainMessage(MSG_DISPATCH_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); |
| } |
| |
| public void dispatchUnhandledKey(KeyEvent event) { |
| if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { |
| // Some fallback keys are decided by the ViewRoot as they might have special |
| // properties (e.g. are locale aware). These take precedence over fallbacks defined by |
| // the kcm. |
| 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(); |
| dispatchInputEvent(fallbackEvent); |
| } |
| } |
| } |
| |
| 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 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 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 dispatchDoneAnimating() { |
| mHandler.sendEmptyMessage(MSG_DISPATCH_DONE_ANIMATING); |
| } |
| |
| public void dispatchCheckFocus() { |
| if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) { |
| // This will result in a call to checkFocus() below. |
| mHandler.sendEmptyMessage(MSG_CHECK_FOCUS); |
| } |
| } |
| |
| /** |
| * 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 ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) { |
| 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) { |
| return false; |
| } |
| // 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) { |
| AccessibilityNodeInfo node = provider.createAccessibilityNodeInfo( |
| AccessibilityNodeInfo.getVirtualDescendantId(sourceNodeId)); |
| 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; |
| } |
| mAccessibilityManager.sendAccessibilityEvent(event); |
| return true; |
| } |
| |
| @Override |
| public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { |
| postSendWindowContentChangedCallback(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 (mAttachInfo != null) { |
| 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) { |
| final boolean scrolled = scrollToRectOrFocus(rectangle, immediate); |
| if (rectangle != null) { |
| mTempRect.set(rectangle); |
| mTempRect.offset(0, -mCurScrollY); |
| mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop); |
| try { |
| mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect, immediate); |
| } catch (RemoteException re) { |
| /* ignore */ |
| } |
| } |
| return scrolled; |
| } |
| |
| @Override |
| public void childHasTransientStateChanged(View child, boolean hasTransientState) { |
| // Do nothing. |
| } |
| |
| void changeCanvasOpacity(boolean opaque) { |
| // TODO(romainguy): recreate Canvas (software or hardware) to reflect the opacity change. |
| Log.d(TAG, "changeCanvasOpacity: opaque=" + 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, boolean reportDraw, Configuration newConfig) { |
| final ViewRootImpl viewAncestor = mViewAncestor.get(); |
| if (viewAncestor != null) { |
| viewAncestor.dispatchResized(frame, overscanInsets, contentInsets, |
| visibleInsets, reportDraw, newConfig); |
| } |
| } |
| |
| @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 ActivityManagerNative.getDefault().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 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 doneAnimating() { |
| final ViewRootImpl viewAncestor = mViewAncestor.get(); |
| if (viewAncestor != null) { |
| viewAncestor.dispatchDoneAnimating(); |
| } |
| } |
| } |
| |
| public static final class CalledFromWrongThreadException extends AndroidRuntimeException { |
| public CalledFromWrongThreadException(String msg) { |
| super(msg); |
| } |
| } |
| |
| static RunQueue getRunQueue() { |
| RunQueue rq = sRunQueues.get(); |
| if (rq != null) { |
| return rq; |
| } |
| rq = new RunQueue(); |
| sRunQueues.set(rq); |
| return rq; |
| } |
| |
| /** |
| * The run queue is used to enqueue pending work from Views when no Handler is |
| * attached. The work is executed during the next call to performTraversals on |
| * the thread. |
| * @hide |
| */ |
| static final class RunQueue { |
| private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>(); |
| |
| void post(Runnable action) { |
| postDelayed(action, 0); |
| } |
| |
| void postDelayed(Runnable action, long delayMillis) { |
| HandlerAction handlerAction = new HandlerAction(); |
| handlerAction.action = action; |
| handlerAction.delay = delayMillis; |
| |
| synchronized (mActions) { |
| mActions.add(handlerAction); |
| } |
| } |
| |
| void removeCallbacks(Runnable action) { |
| final HandlerAction handlerAction = new HandlerAction(); |
| handlerAction.action = action; |
| |
| synchronized (mActions) { |
| final ArrayList<HandlerAction> actions = mActions; |
| |
| while (actions.remove(handlerAction)) { |
| // Keep going |
| } |
| } |
| } |
| |
| void executeActions(Handler handler) { |
| synchronized (mActions) { |
| final ArrayList<HandlerAction> actions = mActions; |
| final int count = actions.size(); |
| |
| for (int i = 0; i < count; i++) { |
| final HandlerAction handlerAction = actions.get(i); |
| handler.postDelayed(handlerAction.action, handlerAction.delay); |
| } |
| |
| actions.clear(); |
| } |
| } |
| |
| private static class HandlerAction { |
| Runnable action; |
| long delay; |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| HandlerAction that = (HandlerAction) o; |
| return !(action != null ? !action.equals(that.action) : that.action != null); |
| |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = action != null ? action.hashCode() : 0; |
| result = 31 * result + (int) (delay ^ (delay >>> 32)); |
| return result; |
| } |
| } |
| } |
| |
| /** |
| * 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 != null && 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() { |
| if (mAttachInfo != null) { |
| final boolean registered = |
| mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; |
| if (!registered) { |
| mAttachInfo.mAccessibilityWindowId = |
| mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, |
| new AccessibilityInteractionConnection(ViewRootImpl.this)); |
| } |
| } |
| } |
| |
| public void ensureNoConnection() { |
| final boolean registered = |
| mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID; |
| if (registered) { |
| mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; |
| mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow); |
| } |
| } |
| } |
| |
| /** |
| * 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, |
| int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, |
| int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { |
| ViewRootImpl viewRootImpl = mViewRootImpl.get(); |
| if (viewRootImpl != null && viewRootImpl.mView != null) { |
| viewRootImpl.getAccessibilityInteractionController() |
| .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId, |
| 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 performAccessibilityAction(long accessibilityNodeId, int action, |
| Bundle arguments, int interactionId, |
| IAccessibilityInteractionConnectionCallback callback, int flags, |
| int interogatingPid, long interrogatingTid) { |
| ViewRootImpl viewRootImpl = mViewRootImpl.get(); |
| if (viewRootImpl != null && viewRootImpl.mView != null) { |
| viewRootImpl.getAccessibilityInteractionController() |
| .performAccessibilityActionClientThread(accessibilityNodeId, action, arguments, |
| interactionId, callback, flags, interogatingPid, 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, 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, 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, |
| 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, |
| 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, 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, 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, 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, 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() { |
| // 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); |
| mSource.sendAccessibilityEventUnchecked(event); |
| } else { |
| mLastEventTimeMillis = 0; |
| } |
| // In any case reset to initial state. |
| mSource.resetSubtreeAccessibilityStateChanged(); |
| mSource = null; |
| mChangeTypes = 0; |
| } |
| |
| public void runOrPost(View source, int changeType) { |
| 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) { |
| mSource.removeCallbacks(this); |
| run(); |
| } else { |
| mSource.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); |
| } |
| } |
| } |
| } |