blob: 195786dfcf2a921dc68a282efd7154ed215bdb75 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import android.Manifest;
import android.animation.LayoutTransition;
import android.annotation.NonNull;
import android.app.ActivityManagerNative;
import android.app.ResourcesManager;
import android.content.ClipDescription;
import android.content.ComponentCallbacks;
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.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.hardware.input.InputManager;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
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.AccessibilityManager.HighTextContrastChangeListener;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
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.InputMethodManager;
import android.widget.Scroller;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.PhoneFallbackEventHandler;
import com.android.internal.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;
import java.util.concurrent.CountDownLatch;
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
*
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.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;
private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV;
private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV;
/**
* Set to false if we do not want to use the multi threaded renderer. Note that by disabling
* this, WindowCallbacks will not fire.
*/
private static final boolean USE_MT_RENDERER = true;
/**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering";
// properties used by emulator to determine display shape
public static final String PROPERTY_EMULATOR_WIN_OUTSET_BOTTOM_PX =
"ro.emu.win_outset_bottom_px";
/**
* Maximum time we allow the user to roll the trackball enough to generate
* a key event, before resetting the counters.
*/
static final int MAX_TRACKBALL_DELAY = 250;
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
static boolean sFirstDrawComplete = false;
static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList();
/**
* This list must only be modified by the main thread, so a lock is only needed when changing
* the list or when accessing the list from a non-main thread.
*/
@GuardedBy("mWindowCallbacks")
final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>();
final Context mContext;
final IWindowSession mWindowSession;
@NonNull Display mDisplay;
final DisplayManager mDisplayManager;
final String mBasePackageName;
final int[] mTmpLocation = new int[2];
final TypedValue mTmpValue = new TypedValue();
final Thread mThread;
final WindowLeaked mLocation;
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
final W mWindow;
final int mTargetSdkVersion;
int mSeq;
View mView;
View mAccessibilityFocusedHost;
AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
// The view which captures mouse input, or null when no one is capturing.
View mCapturingView;
int mViewVisibility;
boolean mAppVisible = true;
// For recents to freeform transition we need to keep drawing after the app receives information
// that it became invisible. This will ignore that information and depend on the decor view
// visibility to control drawing. The decor view visibility will get adjusted when the app get
// stopped and that's when the app will stop drawing further frames.
private boolean mForceDecorViewVisibility = false;
int mOrigWindowType = -1;
/** Whether the window had focus during the most recent traversal. */
boolean mHadWindowFocus;
/**
* Whether the window lost focus during a previous traversal and has not
* yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
* accessibility events should be sent during traversal.
*/
boolean mLostWindowFocus;
// Set to true if the owner of this window is in the stopped state,
// so the window should no longer be active.
boolean mStopped = false;
// Set to true if the owner of this window is in ambient mode,
// which means it won't receive input events.
boolean mIsAmbientMode = false;
// Set to true to stop input during an Activity Transition.
boolean mPausedForTransition = false;
boolean mLastInCompatMode = false;
SurfaceHolder.Callback2 mSurfaceHolderCallback;
BaseSurfaceHolder mSurfaceHolder;
boolean mIsCreating;
boolean mDrawingAllowed;
final Region mTransparentRegion;
final Region mPreviousTransparentRegion;
int mWidth;
int mHeight;
Rect mDirty;
boolean mIsAnimating;
private boolean mDragResizing;
private boolean mInvalidateRootRequested;
private int mResizeMode;
private int mCanvasOffsetX;
private int mCanvasOffsetY;
private boolean mActivityRelaunched;
CompatibilityInfo.Translator mTranslator;
final View.AttachInfo mAttachInfo;
InputChannel mInputChannel;
InputQueue.Callback mInputQueueCallback;
InputQueue mInputQueue;
FallbackEventHandler mFallbackEventHandler;
Choreographer mChoreographer;
final Rect mTempRect; // used in the transaction to not thrash the heap.
final Rect mVisRect; // used to retrieve visible rect of focused view.
boolean mTraversalScheduled;
int mTraversalBarrier;
boolean mWillDrawSoon;
/** Set to true while in performTraversals for detecting when die(true) is called from internal
* callbacks such as onMeasure, onPreDraw, onDraw and deferring doDie() until later. */
boolean mIsInTraversal;
boolean mApplyInsetsRequested;
boolean mLayoutRequested;
boolean mFirst;
boolean mReportNextDraw;
boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded;
boolean mHasHadWindowFocus;
boolean mLastWasImTarget;
boolean mForceNextWindowRelayout;
CountDownLatch mWindowDrawCountDown;
boolean mIsDrawing;
int mLastSystemUiVisibility;
int mClientWindowLayoutFlags;
boolean mLastOverscanRequested;
// Pool of queued input events.
private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
private QueuedInputEvent mQueuedInputEventPool;
private int mQueuedInputEventPoolSize;
/* Input event queue.
* Pending input events are input events waiting to be delivered to the input stages
* and handled by the application.
*/
QueuedInputEvent mPendingInputEventHead;
QueuedInputEvent mPendingInputEventTail;
int mPendingInputEventCount;
boolean mProcessInputEventsScheduled;
boolean mUnbufferedInputDispatch;
String mPendingInputEventQueueLengthCounterName = "pq";
InputStage mFirstInputStage;
InputStage mFirstPostImeInputStage;
InputStage mSyntheticInputStage;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
// These can be accessed by any thread, must be protected with a lock.
// Surface can never be reassigned or cleared (use Surface.clear()).
final Surface mSurface = new Surface();
boolean mAdded;
boolean mAddedTouchMode;
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
final Rect mPendingOverscanInsets = new Rect();
final Rect mPendingVisibleInsets = new Rect();
final Rect mPendingStableInsets = new Rect();
final Rect mPendingContentInsets = new Rect();
final Rect mPendingOutsets = new Rect();
final Rect mPendingBackDropFrame = new Rect();
boolean mPendingAlwaysConsumeNavBar;
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
= new ViewTreeObserver.InternalInsetsInfo();
final Rect mDispatchContentInsets = new Rect();
final Rect mDispatchStableInsets = new Rect();
private WindowInsets mLastWindowInsets;
final Configuration mLastConfiguration = new Configuration();
final Configuration mPendingConfiguration = new Configuration();
boolean mScrollMayChange;
int mSoftInputMode;
WeakReference<View> mLastScrolledFocus;
int mScrollY;
int mCurScrollY;
Scroller mScroller;
static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
private ArrayList<LayoutTransition> mPendingTransitions;
final ViewConfiguration mViewConfiguration;
/* Drag/drop */
ClipDescription mDragDescription;
View mCurrentDragView;
volatile Object mLocalDragState;
final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF();
int mLastTouchSource;
private boolean mProfileRendering;
private Choreographer.FrameCallback mRenderProfiler;
private boolean mRenderProfilingEnabled;
// Variables to track frames per second, enabled via DEBUG_FPS flag
private long mFpsStartTime = -1;
private long mFpsPrevTime = -1;
private int mFpsNumFrames;
private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
private PointerIcon mCustomPointerIcon = null;
/**
* see {@link #playSoundEffect(int)}
*/
AudioManager mAudioManager;
final AccessibilityManager mAccessibilityManager;
AccessibilityInteractionController mAccessibilityInteractionController;
AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager;
HighContrastTextManager mHighContrastTextManager;
SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
HashSet<View> mTempHashSet;
private final int mDensity;
private final int mNoncompatDensity;
private boolean mInLayout = false;
ArrayList<View> mLayoutRequesters = new ArrayList<View>();
boolean mHandlingLayoutInLayoutRequest = false;
private int mViewLayoutDirectionInitial;
/** Set to true once doDie() has been called. */
private boolean mRemoved;
private boolean mNeedsHwRendererSetup;
/**
* Consistency verifier for debugging purposes.
*/
protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this, 0) : null;
static final class SystemUiVisibilityInfo {
int seq;
int globalVisibility;
int localValue;
int localChanges;
}
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityInteractionConnectionManager =
new AccessibilityInteractionConnectionManager();
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mHighContrastTextManager = new HighContrastTextManager();
mAccessibilityManager.addHighTextContrastStateChangeListener(
mHighContrastTextManager);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
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);
}
}
public void addWindowCallbacks(WindowCallbacks callback) {
if (USE_MT_RENDERER) {
synchronized (mWindowCallbacks) {
mWindowCallbacks.add(callback);
}
}
}
public void removeWindowCallbacks(WindowCallbacks callback) {
if (USE_MT_RENDERER) {
synchronized (mWindowCallbacks) {
mWindowCallbacks.remove(callback);
}
}
}
public void reportDrawFinish() {
if (mWindowDrawCountDown != null) {
mWindowDrawCountDown.countDown();
}
}
// FIXME for perf testing only
private boolean mProfile = false;
/**
* Call this to profile the next traversal call.
* FIXME for perf testing only. Remove eventually
*/
public void profile() {
mProfile = true;
}
/**
* Indicates whether we are in touch mode. Calling this method triggers an IPC
* call and should be avoided whenever possible.
*
* @return True, if the device is in touch mode, false otherwise.
*
* @hide
*/
static boolean isInTouchMode() {
IWindowSession windowSession = WindowManagerGlobal.peekWindowSession();
if (windowSession != null) {
try {
return windowSession.getInTouchMode();
} catch (RemoteException e) {
}
}
return false;
}
/**
* Notifies us that our child has been rebuilt, following
* a window preservation operation. In these cases we
* keep the same DecorView, but the activity controlling it
* is a different instance, and we need to update our
* callbacks.
*
* @hide
*/
public void notifyChildRebuilt() {
if (mView instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback =
((RootViewSurfaceTaker)mView).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
} else {
mSurfaceHolder = null;
}
mInputQueueCallback =
((RootViewSurfaceTaker)mView).willYouTakeTheInputQueue();
if (mInputQueueCallback != null) {
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
}
}
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
attrs = mWindowAttributes;
setTag();
if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
&& (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!");
}
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
setAccessibilityFocus(null, null);
if (view instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback =
((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
}
}
// Compute surface insets required to draw at specified Z value.
// TODO: Use real shadow insets for a constant max Z.
if (!attrs.hasManualSurfaceInsets) {
attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
}
CompatibilityInfo compatibilityInfo =
mDisplay.getDisplayAdjustments().getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
// If the application owns the surface, don't enable hardware acceleration
if (mSurfaceHolder == null) {
enableHardwareAcceleration(attrs);
}
boolean restore = false;
if (mTranslator != null) {
mSurface.setCompatibilityTranslator(mTranslator);
restore = true;
attrs.backup();
mTranslator.translateWindowLayout(attrs);
}
if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
if (!compatibilityInfo.supportsScreen()) {
attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = true;
}
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
mAttachInfo.mRootView = view;
mAttachInfo.mScalingRequired = mTranslator != null;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
mPendingOverscanInsets.set(0, 0, 0, 0);
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingStableInsets.set(mAttachInfo.mStableInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
mAttachInfo.mAlwaysConsumeNavBar =
(res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0;
mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar;
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- another window of type "
+ mWindowAttributes.type + " already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- permission denied for window type "
+ mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified display can not be found");
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified window type "
+ mWindowAttributes.type + " is not valid");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
}
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
view.assignParent(this);
mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
if (mAccessibilityManager.isEnabled()) {
mAccessibilityInteractionConnectionManager.ensureConnection();
}
if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}
private void setTag() {
final String[] split = mWindowAttributes.getTitle().toString().split("\\.");
if (split.length > 0) {
mTag = TAG + "[" + split[split.length - 1] + "]";
}
}
/** Whether the window is in local focus mode or not */
private boolean isInLocalFocusMode() {
return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
}
public int getWindowFlags() {
return mWindowAttributes.flags;
}
public int getDisplayId() {
return mDisplay.getDisplayId();
}
public CharSequence getTitle() {
return mWindowAttributes.getTitle();
}
void destroyHardwareResources() {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView);
mAttachInfo.mHardwareRenderer.destroy();
}
}
public void detachFunctor(long functor) {
if (mAttachInfo.mHardwareRenderer != null) {
// Fence so that any pending invokeFunctor() messages will be processed
// before we return from detachFunctor.
mAttachInfo.mHardwareRenderer.stopDrawing();
}
}
/**
* Schedules the functor for execution in either kModeProcess or
* kModeProcessNoContext, depending on whether or not there is an EGLContext.
*
* @param functor The native functor to invoke
* @param waitForCompletion If true, this will not return until the functor
* has invoked. If false, the functor may be invoked
* asynchronously.
*/
public static void invokeFunctor(long functor, boolean waitForCompletion) {
ThreadedRenderer.invokeFunctor(functor, waitForCompletion);
}
public void registerAnimatingRenderNode(RenderNode animator) {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.registerAnimatingRenderNode(animator);
} else {
if (mAttachInfo.mPendingAnimatingRenderNodes == null) {
mAttachInfo.mPendingAnimatingRenderNodes = new ArrayList<RenderNode>();
}
mAttachInfo.mPendingAnimatingRenderNodes.add(animator);
}
}
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
// Don't enable hardware acceleration when the application is in compatibility mode
if (mTranslator != null) return;
// Try to enable hardware acceleration if requested
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
if (!ThreadedRenderer.isAvailable()) {
return;
}
// Persistent processes (including the system) should not do
// accelerated rendering on low-end devices. In that case,
// sRendererDisabled will be set. In addition, the system process
// itself should never do accelerated rendering. In that case, both
// sRendererDisabled and sSystemRendererDisabled are set. When
// sSystemRendererDisabled is set, PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED
// can be used by code on the system process to escape that and enable
// HW accelerated drawing. (This is basically for the lock screen.)
final boolean fakeHwAccelerated = (attrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
final boolean forceHwAccelerated = (attrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
if (fakeHwAccelerated) {
// This is exclusively for the preview windows the window manager
// shows for launching applications, so they will look more like
// the app being launched.
mAttachInfo.mHardwareAccelerationRequested = true;
} else if (!ThreadedRenderer.sRendererDisabled
|| (ThreadedRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroy();
}
final Rect insets = attrs.surfaceInsets;
final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
|| insets.top != 0 || insets.bottom != 0;
final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
mAttachInfo.mHardwareRenderer = ThreadedRenderer.create(mContext, translucent);
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
}
}
}
}
public View getView() {
return mView;
}
final WindowLeaked getLocation() {
return mLocation;
}
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
final int oldSoftInputMode = mWindowAttributes.softInputMode;
final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
&& (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
Slog.d(mTag, "setLayoutParams: FLAG_KEEP_SCREEN_ON from true to false!");
}
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
// Preserve compatible window flag if exists.
final int compatibleWindowFlag = mWindowAttributes.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
// Transfer over system UI visibility values as they carry current state.
attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
if ((mWindowAttributesChangesFlag
& WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
// Recompute system ui visibility.
mAttachInfo.mRecomputeGlobalAttributes = true;
}
if ((mWindowAttributesChangesFlag
& WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
// Request to update light center.
mAttachInfo.mNeedsUpdateLightCenter = true;
}
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
mWindowAttributes.privateFlags |= compatibleWindowFlag;
if (mWindowAttributes.preservePreviousSurfaceInsets) {
// Restore old surface insets.
mWindowAttributes.surfaceInsets.set(
oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
} else if (mWindowAttributes.surfaceInsets.left != oldInsetLeft
|| mWindowAttributes.surfaceInsets.top != oldInsetTop
|| mWindowAttributes.surfaceInsets.right != oldInsetRight
|| mWindowAttributes.surfaceInsets.bottom != oldInsetBottom) {
mNeedsHwRendererSetup = true;
}
applyKeepScreenOnFlag(mWindowAttributes);
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
// Don't lose the mode we last auto-computed.
if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
& ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
| (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
}
mWindowAttributesChanged = true;
scheduleTraversals();
}
}
void handleAppVisibility(boolean visible) {
if (mAppVisible != visible) {
mAppVisible = visible;
scheduleTraversals();
if (!mAppVisible) {
WindowManagerGlobal.trimForeground();
}
}
}
void handleGetNewSurface() {
mNewSurfaceNeeded = true;
mFullRedrawNeeded = true;
scheduleTraversals();
}
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayChanged(int displayId) {
if (mView != null && mDisplay.getDisplayId() == displayId) {
final int oldDisplayState = mAttachInfo.mDisplayState;
final int newDisplayState = mDisplay.getState();
if (oldDisplayState != newDisplayState) {
mAttachInfo.mDisplayState = newDisplayState;
pokeDrawLockIfNeeded();
if (oldDisplayState != Display.STATE_UNKNOWN) {
final int oldScreenState = toViewScreenState(oldDisplayState);
final int newScreenState = toViewScreenState(newDisplayState);
if (oldScreenState != newScreenState) {
mView.dispatchScreenStateChanged(newScreenState);
}
if (oldDisplayState == Display.STATE_OFF) {
// Draw was suppressed so we need to for it to happen here.
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
}
}
}
@Override
public void onDisplayRemoved(int displayId) {
}
@Override
public void onDisplayAdded(int displayId) {
}
private int toViewScreenState(int displayState) {
return displayState == Display.STATE_OFF ?
View.SCREEN_STATE_OFF : View.SCREEN_STATE_ON;
}
};
void pokeDrawLockIfNeeded() {
final int displayState = mAttachInfo.mDisplayState;
if (mView != null && mAdded && mTraversalScheduled
&& (displayState == Display.STATE_DOZE
|| displayState == Display.STATE_DOZE_SUSPEND)) {
try {
mWindowSession.pokeDrawLock(mWindow);
} catch (RemoteException ex) {
// System server died, oh well.
}
}
}
@Override
public void requestFitSystemWindows() {
checkThread();
mApplyInsetsRequested = true;
scheduleTraversals();
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
@Override
public boolean isLayoutRequested() {
return mLayoutRequested;
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
void invalidateWorld(View view) {
view.invalidate();
if (view instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) view;
for (int i = 0; i < parent.getChildCount(); i++) {
invalidateWorld(parent.getChildAt(i));
}
}
}
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
public void setIsAmbientMode(boolean ambient) {
mIsAmbientMode = ambient;
}
void setWindowStopped(boolean stopped) {
if (mStopped != stopped) {
mStopped = stopped;
final ThreadedRenderer renderer = mAttachInfo.mHardwareRenderer;
if (renderer != null) {
if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
renderer.setStopped(mStopped);
}
if (!mStopped) {
scheduleTraversals();
} else {
if (renderer != null) {
renderer.destroyHardwareResources(mView);
}
}
}
}
/**
* Block the input events during an Activity Transition. The KEYCODE_BACK event is allowed
* through to allow quick reversal of the Activity Transition.
*
* @param paused true to pause, false to resume.
*/
public void setPausedForTransition(boolean paused) {
mPausedForTransition = paused;
}
@Override
public ViewParent getParent() {
return null;
}
@Override
public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
if (child != mView) {
throw new RuntimeException("child is not mine, honest!");
}
// Note: don't apply scroll offset, because we want to know its
// visibility in the virtual canvas being given to the view hierarchy.
return r.intersect(0, 0, mWidth, mHeight);
}
@Override
public void bringChildToFront(View child) {
}
int getHostVisibility() {
return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
}
/**
* Add LayoutTransition to the list of transitions to be started in the next traversal.
* This list will be cleared after the transitions on the list are start()'ed. These
* transitionsa re added by LayoutTransition itself when it sets up animations. The setup
* happens during the layout phase of traversal, which we want to complete before any of the
* animations are started (because those animations may side-effect properties that layout
* depends upon, like the bounding rectangles of the affected views). So we add the transition
* to the list and it is started just prior to starting the drawing phase of traversal.
*
* @param transition The LayoutTransition to be started on the next traversal.
*
* @hide
*/
public void requestTransitionStart(LayoutTransition transition) {
if (mPendingTransitions == null || !mPendingTransitions.contains(transition)) {
if (mPendingTransitions == null) {
mPendingTransitions = new ArrayList<LayoutTransition>();
}
mPendingTransitions.add(transition);
}
}
/**
* Notifies the HardwareRenderer that a new frame will be coming soon.
* Currently only {@link ThreadedRenderer} cares about this, and uses
* this knowledge to adjust the scheduling of off-thread animations
*/
void notifyRendererOfFramePending() {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.notifyFramePending();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
private void applyKeepScreenOnFlag(WindowManager.LayoutParams params) {
// Update window's global keep screen on flag: if a view has requested
// that the screen be kept on, then it is always set; otherwise, it is
// set to whatever the client last requested for the global state.
if (mAttachInfo.mKeepScreenOn) {
params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
} else {
params.flags = (params.flags&~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
| (mClientWindowLayoutFlags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
private boolean collectViewAttributes() {
if (mAttachInfo.mRecomputeGlobalAttributes) {
//Log.i(mTag, "Computing view hierarchy attributes!");
mAttachInfo.mRecomputeGlobalAttributes = false;
boolean oldScreenOn = mAttachInfo.mKeepScreenOn;
mAttachInfo.mKeepScreenOn = false;
mAttachInfo.mSystemUiVisibility = 0;
mAttachInfo.mHasSystemUiListeners = false;
mView.dispatchCollectViewAttributes(mAttachInfo, 0);
mAttachInfo.mSystemUiVisibility &= ~mAttachInfo.mDisabledSystemUiVisibility;
WindowManager.LayoutParams params = mWindowAttributes;
mAttachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params);
if (mAttachInfo.mKeepScreenOn != oldScreenOn
|| mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
|| mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
applyKeepScreenOnFlag(params);
params.subtreeSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
params.hasSystemUiListeners = mAttachInfo.mHasSystemUiListeners;
mView.dispatchWindowSystemUiVisiblityChanged(mAttachInfo.mSystemUiVisibility);
return true;
}
}
return false;
}
private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) {
int vis = 0;
// Translucent decor window flags imply stable system ui visibility.
if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) {
vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
}
if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0) {
vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
return vis;
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
return windowSizeMayChange;
}
/**
* Modifies the input matrix such that it maps view-local coordinates to
* on-screen coordinates.
*
* @param m input matrix to modify
*/
void transformMatrixToGlobal(Matrix m) {
m.preTranslate(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
}
/**
* Modifies the input matrix such that it maps on-screen coordinates to
* view-local coordinates.
*
* @param m input matrix to modify
*/
void transformMatrixToLocal(Matrix m) {
m.postTranslate(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
}
/* package */ WindowInsets getWindowInsets(boolean forceConstruct) {
if (mLastWindowInsets == null || forceConstruct) {
mDispatchContentInsets.set(mAttachInfo.mContentInsets);
mDispatchStableInsets.set(mAttachInfo.mStableInsets);
Rect contentInsets = mDispatchContentInsets;
Rect stableInsets = mDispatchStableInsets;
// For dispatch we preserve old logic, but for direct requests from Views we allow to
// immediately use pending insets.
if (!forceConstruct
&& (!mPendingContentInsets.equals(contentInsets) ||
!mPendingStableInsets.equals(stableInsets))) {
contentInsets = mPendingContentInsets;
stableInsets = mPendingStableInsets;
}
Rect outsets = mAttachInfo.mOutsets;
if (outsets.left > 0 || outsets.top > 0 || outsets.right > 0 || outsets.bottom > 0) {
contentInsets = new Rect(contentInsets.left + outsets.left,
contentInsets.top + outsets.top, contentInsets.right + outsets.right,
contentInsets.bottom + outsets.bottom);
}
mLastWindowInsets = new WindowInsets(contentInsets,
null /* windowDecorInsets */, stableInsets,
mContext.getResources().getConfiguration().isScreenRound(),
mAttachInfo.mAlwaysConsumeNavBar);
}
return mLastWindowInsets;
}
void dispatchApplyInsets(View host) {
host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
}
private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) {
return lp.type == TYPE_STATUS_BAR_PANEL
|| lp.type == TYPE_INPUT_METHOD
|| lp.type == TYPE_VOLUME_OVERLAY;
}
private int dipToPx(int dip) {
final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
return (int) (displayMetrics.density * dip + 0.5f);
}
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
if (host == null || !mAdded)
return;
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded);
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
surfaceChanged = true;
params = lp;
}
CompatibilityInfo compatibilityInfo =
mDisplay.getDisplayAdjustments().getCompatibilityInfo();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
params = lp;
mFullRedrawNeeded = true;
mLayoutRequested = true;
if (mLastInCompatMode) {
params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = false;
} else {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = true;
}
}
mWindowAttributesChangesFlag = 0;
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = mContext.getResources().getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mHasWindowFocus = false;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
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(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
}
if (viewVisibility == View.GONE) {
// After making a window gone, we will count it as being
// shown for the first time the next time it gets focus.
mHasHadWindowFocus = false;
}
}
// Non-visible windows can't hold accessibility focus.
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
host.clearAccessibilityFocus();
}
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
insetsChanged = true;
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
if (mFirst || mAttachInfo.mViewVisibilityChanged) {
mAttachInfo.mViewVisibilityChanged = false;
int resizeMode = mSoftInputMode &
WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
// If we are in auto resize mode, then we need to determine
// what mode to use now.
if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
final int N = mAttachInfo.mScrollContainers.size();
for (int i=0; i<N; i++) {
if (mAttachInfo.mScrollContainers.get(i).isShown()) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
}
if (resizeMode == 0) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
}
if ((lp.softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
lp.softInputMode = (lp.softInputMode &
~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
resizeMode;
params = lp;
}
}
}
if (params != null) {
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
if (!PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
}
mAttachInfo.mOverscanRequested = (params.flags
& WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
}
if (mApplyInsetsRequested) {
mApplyInsetsRequested = false;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
// we don't need to go through two layout passes when things
// change due to fitting system windows, which can happen a lot.
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
}
}
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
mLayoutRequested = false;
}
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
// bounds.
windowShouldResize |= mActivityRelaunched;
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
boolean insetsPending = false;
int relayoutResult = 0;
boolean updatedConfiguration = false;
final int surfaceGenerationId = mSurface.getGenerationId();
final boolean isViewVisible = viewVisibility == View.VISIBLE;
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
mForceNextWindowRelayout = false;
if (isViewVisible) {
// If this window is giving internal insets to the window
// manager, and it is being added or changing its visibility,
// then we want to first give the window manager "fake"
// insets to cause it to effectively ignore the content of
// the window during layout. This avoids it briefly causing
// other windows to resize/move based on the raw frame of the
// window, waiting until we can finish laying out this window
// and get back to the window manager with the ultimately
// computed insets.
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
mDrawingAllowed = true;
}
boolean hwInitialized = false;
boolean framesChanged = false;
boolean hadSurface = mSurface.isValid();
try {
if (DEBUG_LAYOUT) {
Log.i(mTag, "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.
if (mAttachInfo.mHardwareRenderer.pauseSurface(mSurface)) {
// Animations were running so we need to push a frame
// to resume them
mDirty.set(0, 0, mWidth, mHeight);
}
mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
}
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
+ " overscan=" + mPendingOverscanInsets.toShortString()
+ " content=" + mPendingContentInsets.toShortString()
+ " visible=" + mPendingVisibleInsets.toShortString()
+ " visible=" + mPendingStableInsets.toShortString()
+ " outsets=" + mPendingOutsets.toShortString()
+ " surface=" + mSurface);
if (mPendingConfiguration.seq != 0) {
if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
+ mPendingConfiguration);
updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
mPendingConfiguration.seq = 0;
updatedConfiguration = true;
}
final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
mAttachInfo.mOverscanInsets);
boolean contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
final boolean stableInsetsChanged = !mPendingStableInsets.equals(
mAttachInfo.mStableInsets);
final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
final boolean surfaceSizeChanged = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
final boolean alwaysConsumeNavBarChanged =
mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
if (contentInsetsChanged) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: "
+ mAttachInfo.mContentInsets);
}
if (overscanInsetsChanged) {
mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Overscan insets changing to: "
+ mAttachInfo.mOverscanInsets);
// Need to relayout with content insets.
contentInsetsChanged = true;
}
if (stableInsetsChanged) {
mAttachInfo.mStableInsets.set(mPendingStableInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Decor insets changing to: "
+ mAttachInfo.mStableInsets);
// Need to relayout with content insets.
contentInsetsChanged = true;
}
if (alwaysConsumeNavBarChanged) {
mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar;
contentInsetsChanged = true;
}
if (contentInsetsChanged || mLastSystemUiVisibility !=
mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
|| mLastOverscanRequested != mAttachInfo.mOverscanRequested
|| outsetsChanged) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
mAttachInfo.mOutsets.set(mPendingOutsets);
mApplyInsetsRequested = false;
dispatchApplyInsets(host);
}
if (visibleInsetsChanged) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
// If any of the insets changed, do a forceLayout on the view so that the
// measure cache is cleared. We might have a pending MSG_RESIZED_REPORT
// that is supposed to take care of it, but since pending insets are
// already modified here, it won't detect the frame change after this.
framesChanged = overscanInsetsChanged
|| contentInsetsChanged
|| stableInsetsChanged
|| visibleInsetsChanged
|| outsetsChanged;
if (mAdded && mView != null && framesChanged) {
forceLayout(mView);
}
if (!hadSurface) {
if (mSurface.isValid()) {
// If we are creating a new surface, then we need to
// completely redraw it. Also, when we get to the
// point of drawing it we will hold off and schedule
// a new traversal instead. This is so we can tell the
// window manager about all of the windows being displayed
// before actually drawing them, so it can display then
// all at once.
newSurface = true;
mFullRedrawNeeded = true;
mPreviousTransparentRegion.setEmpty();
// Only initialize up-front if transparent regions are not
// requested, otherwise defer to see if the entire window
// will be transparent
if (mAttachInfo.mHardwareRenderer != null) {
try {
hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
mSurface);
if (hwInitialized && (host.mPrivateFlags
& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
// Don't pre-allocate if transparent regions
// are requested as they may not be needed
mSurface.allocateBuffers();
}
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
}
}
} else if (!mSurface.isValid()) {
// If the surface has been removed, then reset the scroll
// positions.
if (mLastScrolledFocus != null) {
mLastScrolledFocus.clear();
}
mScrollY = mCurScrollY = 0;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
if (mScroller != null) {
mScroller.abortAnimation();
}
// Our surface is gone
if (mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.destroy();
}
} else if ((surfaceGenerationId != mSurface.getGenerationId()
|| surfaceSizeChanged)
&& mSurfaceHolder == null
&& mAttachInfo.mHardwareRenderer != null) {
mFullRedrawNeeded = true;
try {
// Need to do updateSurface (which leads to CanvasContext::setSurface and
// re-create the EGLSurface) if either the Surface changed (as indicated by
// generation id), or WindowManager changed the surface size. The latter is
// because on some chips, changing the consumer side's BufferQueue size may
// not take effect immediately unless we create a new EGLSurface.
// Note that frame size change doesn't always imply surface size change (eg.
// drag resizing uses fullscreen surface), need to check surfaceSizeChanged
// flag from WindowManager.
mAttachInfo.mHardwareRenderer.updateSurface(mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
}
final boolean freeformResizing = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
final boolean dockedResizing = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
final boolean dragResizing = freeformResizing || dockedResizing;
if (mDragResizing != dragResizing) {
if (dragResizing) {
mResizeMode = freeformResizing
? RESIZE_MODE_FREEFORM
: RESIZE_MODE_DOCKED_DIVIDER;
startDragResizing(mPendingBackDropFrame,
mWinFrame.equals(mPendingBackDropFrame), mPendingVisibleInsets,
mPendingStableInsets, mResizeMode);
} else {
// We shouldn't come here, but if we come we should end the resize.
endDragResizing();
}
}
if (!USE_MT_RENDERER) {
if (dragResizing) {
mCanvasOffsetX = mWinFrame.left;
mCanvasOffsetY = mWinFrame.top;
} else {
mCanvasOffsetX = mCanvasOffsetY = 0;
}
}
} catch (RemoteException e) {
}
if (DEBUG_ORIENTATION) Log.v(
TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// !!FIXME!! This next section handles the case where we did not get the
// window size we asked for. We should avoid this by getting a maximum size from
// the window session beforehand.
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
if (mSurfaceHolder != null) {
// The app owns the surface; tell it about what is going on.
if (mSurface.isValid()) {
// XXX .copyFrom() doesn't work!
//mSurfaceHolder.mSurface.copyFrom(mSurface);
mSurfaceHolder.mSurface = mSurface;
}
mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
mSurfaceHolder.mSurfaceLock.unlock();
if (mSurface.isValid()) {
if (!hadSurface) {
mSurfaceHolder.ungetCallbacks();
mIsCreating = true;
mSurfaceHolderCallback.surfaceCreated(mSurfaceHolder);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
surfaceChanged = true;
}
if (surfaceChanged || surfaceGenerationId != mSurface.getGenerationId()) {
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();
}
}
}
final ThreadedRenderer hardwareRenderer = mAttachInfo.mHardwareRenderer;
if (hardwareRenderer != null && hardwareRenderer.isEnabled()) {
if (hwInitialized
|| mWidth != hardwareRenderer.getWidth()
|| mHeight != hardwareRenderer.getHeight()
|| mNeedsHwRendererSetup) {
hardwareRenderer.setup(mWidth, mHeight, mAttachInfo,
mWindowAttributes.surfaceInsets);
mNeedsHwRendererSetup = false;
}
}
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || framesChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " framesChanged=" + framesChanged);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
maybeHandleWindowMove(frame);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
host.debug();
}
}
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
if (computesInternalInsets) {
// Clear the original insets.
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
insets.reset();
// Compute new insets in place.
mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
// Tell the window manager.
if (insetsPending || !mLastGivenInsets.equals(insets)) {
mLastGivenInsets.set(insets);
// Translate insets to screen coordinates if needed.
final Rect contentInsets;
final Rect visibleInsets;
final Region touchableRegion;
if (mTranslator != null) {
contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
} else {
contentInsets = insets.contentInsets;
visibleInsets = insets.visibleInsets;
touchableRegion = insets.touchableRegion;
}
try {
mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
contentInsets, visibleInsets, touchableRegion);
} catch (RemoteException e) {
}
}
}
if (mFirst) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
+ mView.hasFocus());
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
+ mView.findFocus());
} else {
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
+ mView.findFocus());
}
}
}
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
if (regainedFocus) {
mLostWindowFocus = false;
} else if (!hasWindowFocus && mHadWindowFocus) {
mLostWindowFocus = true;
}
if (changedVisibility || regainedFocus) {
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mActivityRelaunched = false;
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
if (hasWindowFocus && !isInLocalFocusMode()) {
final boolean imTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
if (imTarget != mLastWasImTarget) {
mLastWasImTarget = imTarget;
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && imTarget) {
imm.onPreWindowFocus(mView, hasWindowFocus);
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
}
}
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportNextDraw = true;
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
private void maybeHandleWindowMove(Rect frame) {
// TODO: Well, we are checking whether the frame has changed similarly
// to how this is done for the insets. This is however incorrect since
// the insets and the frame are translated. For example, the old frame
// was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
// reported frame is (2, 2 - 2, 2) which implies no change but this is not
// true since we are comparing a not translated value to a translated one.
// This scenario is rare but we may want to fix that.
final boolean windowMoved = mAttachInfo.mWindowLeft != frame.left
|| mAttachInfo.mWindowTop != frame.top;
if (windowMoved) {
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWinFrame(frame);
}
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
}
if (windowMoved || mAttachInfo.mNeedsUpdateLightCenter) {
// Update the light position for the new offsets.
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.setLightCenter(mAttachInfo);
}
mAttachInfo.mNeedsUpdateLightCenter = false;
}
}
private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
try {
if (!mWindowSession.outOfMemory(mWindow) &&
Process.myUid() != Process.SYSTEM_UID) {
Slog.w(mTag, "No processes killed for memory; killing self");
Process.killProcess(Process.myPid());
}
} catch (RemoteException ex) {
}
mLayoutRequested = true; // ask wm for a new surface next time.
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
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(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
/**
* This method is called during layout when there have been calls to requestLayout() during
* layout. It walks through the list of views that requested layout to determine which ones
* still need it, based on visibility in the hierarchy and whether they have already been
* handled (as is usually the case with ListView children).
*
* @param layoutRequesters The list of views that requested layout during layout
* @param secondLayoutRequests Whether the requests were issued during the second layout pass.
* If so, the FORCE_LAYOUT flag was not set on requesters.
* @return A list of the actual views that still need to be laid out.
*/
private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters,
boolean secondLayoutRequests) {
int numViewsRequestingLayout = layoutRequesters.size();
ArrayList<View> validLayoutRequesters = null;
for (int i = 0; i < numViewsRequestingLayout; ++i) {
View view = layoutRequesters.get(i);
if (view != null && view.mAttachInfo != null && view.mParent != null &&
(secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==
View.PFLAG_FORCE_LAYOUT)) {
boolean gone = false;
View parent = view;
// Only trigger new requests for views in a non-GONE hierarchy
while (parent != null) {
if ((parent.mViewFlags & View.VISIBILITY_MASK) == View.GONE) {
gone = true;
break;
}
if (parent.mParent instanceof View) {
parent = (View) parent.mParent;
} else {
parent = null;
}
}
if (!gone) {
if (validLayoutRequesters == null) {
validLayoutRequesters = new ArrayList<View>();
}
validLayoutRequesters.add(view);
}
}
}
if (!secondLayoutRequests) {
// If we're checking the layout flags, then we need to clean them up also
for (int i = 0; i < numViewsRequestingLayout; ++i) {
View view = layoutRequesters.get(i);
while (view != null &&
(view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
if (view.mParent instanceof View) {
view = (View) view.mParent;
} else {
view = null;
}
}
}
}
layoutRequesters.clear();
return validLayoutRequesters;
}
@Override
public void requestTransparentRegion(View child) {
// the test below should not fail unless someone is messing with us
checkThread();
if (mView == child) {
mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
// Need to make sure we re-evaluate the window attributes next
// time around, to ensure the window has the correct format.
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = 0;
requestLayout();
}
}
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
int mHardwareXOffset;
int mHardwareYOffset;
@Override
public void onHardwarePreDraw(DisplayListCanvas canvas) {
canvas.translate(-mHardwareXOffset, -mHardwareYOffset);
}
@Override
public void onHardwarePostDraw(DisplayListCanvas canvas) {
drawAccessibilityFocusedDrawableIfNeeded(canvas);
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onPostDraw(canvas);
}
}
/**
* @hide
*/
void outputDisplayList(View view) {
view.mRenderNode.output();
if (mAttachInfo.mHardwareRenderer != null) {
((ThreadedRenderer)mAttachInfo.mHardwareRenderer).serializeDisplayListTree();
}
}
/**
* @see #PROPERTY_PROFILE_RENDERING
*/
private void profileRendering(boolean enabled) {
if (mProfileRendering) {
mRenderProfilingEnabled = enabled;
if (mRenderProfiler != null) {
mChoreographer.removeFrameCallback(mRenderProfiler);
}
if (mRenderProfilingEnabled) {
if (mRenderProfiler == null) {
mRenderProfiler = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mDirty.set(0, 0, mWidth, mHeight);
scheduleTraversals();
if (mRenderProfilingEnabled) {
mChoreographer.postFrameCallback(mRenderProfiler);
}
}
};
}
mChoreographer.postFrameCallback(mRenderProfiler);
} else {
mRenderProfiler = null;
}
}
}
/**
* Called from draw() when DEBUG_FPS is enabled
*/
private void trackFPS() {
// Tracks frames per second drawn. First value in a series of draws may be bogus
// because it down not account for the intervening idle time
long nowTime = System.currentTimeMillis();
if (mFpsStartTime < 0) {
mFpsStartTime = mFpsPrevTime = nowTime;
mFpsNumFrames = 0;
} else {
++mFpsNumFrames;
String thisHash = Integer.toHexString(System.identityHashCode(this));
long frameTime = nowTime - mFpsPrevTime;
long totalTime = nowTime - mFpsStartTime;
Log.v(mTag, "0x" + thisHash + "\tFrame time:\t" + frameTime);
mFpsPrevTime = nowTime;
if (totalTime > 1000) {
float fps = (float) mFpsNumFrames * 1000 / totalTime;
Log.v(mTag, "0x" + thisHash + "\tFPS:\t" + fps);
mFpsStartTime = nowTime;
mFpsNumFrames = 0;
}
}
}
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);
}
// For whatever reason we didn't create a HardwareRenderer, end any
// hardware animations that are now dangling
if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
}
mAttachInfo.mPendingAnimatingRenderNodes.clear();
}
if (mReportNextDraw) {
mReportNextDraw = false;
// if we're using multi-thread renderer, wait for the window frame draws
if (mWindowDrawCountDown != null) {
try {
mWindowDrawCountDown.await();
} catch (InterruptedException e) {
Log.e(mTag, "Window redraw count down interruped!");
}
mWindowDrawCountDown = null;
}
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.fence();
mAttachInfo.mHardwareRenderer.setStopped(mStopped);
}
if (LOCAL_LOGV) {
Log.v(mTag, "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);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
boolean accessibilityFocusDirty = false;
final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
if (drawable != null) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
final boolean hasFocus = getAccessibilityFocusedRect(bounds);
if (!hasFocus) {
bounds.setEmpty();
}
if (!bounds.equals(drawable.getBounds())) {
accessibilityFocusDirty = true;
}
}
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
// Draw with hardware renderer.
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
if (invalidateRoot) {
mAttachInfo.mHardwareRenderer.invalidateRoot();
}
dirty.setEmpty();
// Stage the content drawn size now. It will be transferred to the renderer
// shortly before the draw commands get send to the renderer.
final boolean updated = updateContentDrawBounds();
if (mReportNextDraw) {
// report next draw overrides setStopped()
// This value is re-sync'd to the value of mStopped
// in the handling of mReportNextDraw post-draw.
mAttachInfo.mHardwareRenderer.setStopped(false);
}
if (updated) {
requestDrawWindow();
}
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (mAttachInfo.mHardwareRenderer != null &&
!mAttachInfo.mHardwareRenderer.isEnabled() &&
mAttachInfo.mHardwareRenderer.isRequested()) {
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
/**
* @return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// or
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
if (LOCAL_LOGV) {
Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
/**
* We want to draw a highlight around the current accessibility focused.
* Since adding a style for all possible view is not a viable option we
* have this specialized drawing method.
*
* Note: We are doing this here to be able to draw the highlight for
* virtual views in addition to real ones.
*
* @param canvas The canvas on which to draw.
*/
private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
if (getAccessibilityFocusedRect(bounds)) {
final Drawable drawable = getAccessibilityFocusedDrawable();
if (drawable != null) {
drawable.setBounds(bounds);
drawable.draw(canvas);
}
} else if (mAttachInfo.mAccessibilityFocusDrawable != null) {
mAttachInfo.mAccessibilityFocusDrawable.setBounds(0, 0, 0, 0);
}
}
private boolean getAccessibilityFocusedRect(Rect bounds) {
final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext);
if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
return false;
}
final View host = mAccessibilityFocusedHost;
if (host == null || host.mAttachInfo == null) {
return false;
}
final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
if (provider == null) {
host.getBoundsOnScreen(bounds, true);
} else if (mAccessibilityFocusedVirtualView != null) {
mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
} else {
return false;
}
// Transform the rect into window-relative coordinates.
final AttachInfo attachInfo = mAttachInfo;
bounds.offset(0, attachInfo.mViewRootImpl.mScrollY);
bounds.offset(-attachInfo.mWindowLeft, -attachInfo.mWindowTop);
if (!bounds.intersect(0, 0, attachInfo.mViewRootImpl.mWidth,
attachInfo.mViewRootImpl.mHeight)) {
// If no intersection, set bounds to empty.
bounds.setEmpty();
}
return !bounds.isEmpty();
}
private Drawable getAccessibilityFocusedDrawable() {
// Lazily load the accessibility focus drawable.
if (mAttachInfo.mAccessibilityFocusDrawable == null) {
final TypedValue value = new TypedValue();
final boolean resolved = mView.mContext.getTheme().resolveAttribute(
R.attr.accessibilityFocusedDrawable, value, true);
if (resolved) {
mAttachInfo.mAccessibilityFocusDrawable =
mView.mContext.getDrawable(value.resourceId);
}
}
return mAttachInfo.mAccessibilityFocusDrawable;
}
/**
* Requests that the root render node is invalidated next time we perform a draw, such that
* {@link WindowCallbacks#onPostDraw} gets called.
*/
public void requestInvalidateRootRenderNode() {
mInvalidateRootRequested = true;
}
boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
final Rect ci = mAttachInfo.mContentInsets;
final Rect vi = mAttachInfo.mVisibleInsets;
int scrollY = 0;
boolean handled = false;
if (vi.left > ci.left || vi.top > ci.top
|| vi.right > ci.right || vi.bottom > ci.bottom) {
// We'll assume that we aren't going to change the scroll
// offset, since we want to avoid that unless it is actually
// going to make the focus visible... otherwise we scroll
// all over the place.
scrollY = mScrollY;
// We can be called for two different situations: during a draw,
// to update the scroll position if the focus has changed (in which
// case 'rectangle' is null), or in response to a
// requestChildRectangleOnScreen() call (in which case 'rectangle'
// is non-null and we just want to scroll to whatever that
// rectangle is).
final View focus = mView.findFocus();
if (focus == null) {
return false;
}
View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
if (focus != lastScrolledFocus) {
// If the focus has changed, then ignore any requests to scroll
// to a rectangle; first we want to make sure the entire focus
// view is visible.
rectangle = null;
}
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Eval scroll: focus=" + focus
+ " rectangle=" + rectangle + " ci=" + ci
+ " vi=" + vi);
if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
// Optimization: if the focus hasn't changed since last
// time, and no layout has happened, then just leave things
// as they are.
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Keeping scroll y="
+ mScrollY + " vi=" + vi.toShortString());
} else {
// We need to determine if the currently focused view is
// within the visible part of the window and, if not, apply
// a pan so it can be seen.
mLastScrolledFocus = new WeakReference<View>(focus);
mScrollMayChange = false;
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Need to scroll?");
// Try to find the rectangle from the focus view.
if (focus.getGlobalVisibleRect(mVisRect, null)) {
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Root w="
+ mView.getWidth() + " h=" + mView.getHeight()
+ " ci=" + ci.toShortString()
+ " vi=" + vi.toShortString());
if (rectangle == null) {
focus.getFocusedRect(mTempRect);
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus " + focus
+ ": focusRect=" + mTempRect.toShortString());
if (mView instanceof ViewGroup) {
((ViewGroup) mView).offsetDescendantRectToMyCoords(
focus, mTempRect);
}
if (DEBUG_INPUT_RESIZE) Log.v(mTag,
"Focus in window: focusRect="
+ mTempRect.toShortString()
+ " visRect=" + mVisRect.toShortString());
} else {
mTempRect.set(rectangle);
if (DEBUG_INPUT_RESIZE) Log.v(mTag,
"Request scroll to rect: "
+ mTempRect.toShortString()
+ " visRect=" + mVisRect.toShortString());
}
if (mTempRect.intersect(mVisRect)) {
if (DEBUG_INPUT_RESIZE) Log.v(mTag,
"Focus window visible rect: "
+ mTempRect.toShortString());
if (mTempRect.height() >
(mView.getHeight()-vi.top-vi.bottom)) {
// If the focus simply is not going to fit, then
// best is probably just to leave things as-is.
if (DEBUG_INPUT_RESIZE) Log.v(mTag,
"Too tall; leaving scrollY=" + scrollY);
}
// Next, check whether top or bottom is covered based on the non-scrolled
// position, and calculate new scrollY (or set it to 0).
// We can't keep using mScrollY here. For example mScrollY is non-zero
// due to IME, then IME goes away. The current value of mScrollY leaves top
// and bottom both visible, but we still need to scroll it back to 0.
else if (mTempRect.top < vi.top) {
scrollY = mTempRect.top - vi.top;
if (DEBUG_INPUT_RESIZE) Log.v(mTag,
"Top covered; scrollY=" + scrollY);
} else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {
scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
if (DEBUG_INPUT_RESIZE) Log.v(mTag,
"Bottom covered; scrollY=" + scrollY);
} else {
scrollY = 0;
}
handled = true;
}
}
}
}
if (scrollY != mScrollY) {
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Pan scroll changed: old="
+ mScrollY + " , new=" + scrollY);
if (!immediate) {
if (mScroller == null) {
mScroller = new Scroller(mView.getContext());
}
mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
} else if (mScroller != null) {
mScroller.abortAnimation();
}
mScrollY = scrollY;
}
return handled;
}
/**
* @hide
*/
public View getAccessibilityFocusedHost() {
return mAccessibilityFocusedHost;
}
/**
* @hide
*/
public AccessibilityNodeInfo getAccessibilityFocusedVirtualView() {
return mAccessibilityFocusedVirtualView;
}
void setAccessibilityFocus(View view, AccessibilityNodeInfo node) {
// If we have a virtual view with accessibility focus we need
// to clear the focus and invalidate the virtual view bounds.
if (mAccessibilityFocusedVirtualView != null) {
AccessibilityNodeInfo focusNode = mAccessibilityFocusedVirtualView;
View focusHost = mAccessibilityFocusedHost;
// Wipe the state of the current accessibility focus since
// the call into the provider to clear accessibility focus
// will fire an accessibility event which will end up calling
// this method and we want to have clean state when this
// invocation happens.
mAccessibilityFocusedHost = null;
mAccessibilityFocusedVirtualView = null;
// Clear accessibility focus on the host after clearing state since
// this method may be reentrant.
focusHost.clearAccessibilityFocusNoCallbacks(
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
AccessibilityNodeProvider provider = focusHost.getAccessibilityNodeProvider();
if (provider != null) {
// Invalidate the area of the cleared accessibility focus.
focusNode.getBoundsInParent(mTempRect);
focusHost.invalidate(mTempRect);
// Clear accessibility focus in the virtual node.
final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
focusNode.getSourceNodeId());
provider.performAction(virtualNodeId,
AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
}
focusNode.recycle();
}
if (mAccessibilityFocusedHost != null) {
// Clear accessibility focus in the view.
mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks(
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
}
// Set the new focus host and node.
mAccessibilityFocusedHost = view;
mAccessibilityFocusedVirtualView = node;
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.invalidateRoot();
}
}
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Request child focus: focus now " + focused);
}
checkThread();
scheduleTraversals();
}
@Override
public void clearChildFocus(View child) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Clearing child focus");
}
checkThread();
scheduleTraversals();
}
@Override
public ViewParent getParentForAccessibility() {
return null;
}
@Override
public void focusableViewAvailable(View v) {
checkThread();
if (mView != null) {
if (!mView.hasFocus()) {
v.requestFocus();
} else {
// the one case where will transfer focus away from the current one
// is if the current view is a view group that prefers to give focus
// to its children first AND the view is a descendant of it.
View focused = mView.findFocus();
if (focused instanceof ViewGroup) {
ViewGroup group = (ViewGroup) focused;
if (group.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
&& isViewDescendantOf(v, focused)) {
v.requestFocus();
}
}
}
}
}
@Override
public void recomputeViewAttributes(View child) {
checkThread();
if (mView == child) {
mAttachInfo.mRecomputeGlobalAttributes = true;
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
}
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
try {
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
unscheduleTraversals();
}
void updateConfiguration(Configuration config, boolean force) {
if (DEBUG_CONFIGURATION) Log.v(mTag,
"Applying new config to window "
+ mWindowAttributes.getTitle()
+ ": " + config);
CompatibilityInfo ci = mDisplay.getDisplayAdjustments().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.
final Resources localResources = mView.getResources();
config = localResources.getConfiguration();
if (force || mLastConfiguration.diff(config) != 0) {
// Update the display with new DisplayAdjustments.
mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(
mDisplay.getDisplayId(), localResources.getDisplayAdjustments());
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_CHECK_FOCUS = 13;
private final static int MSG_CLOSE_SYSTEM_DIALOGS = 14;
private final static int MSG_DISPATCH_DRAG_EVENT = 15;
private final static int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
private final static int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
private final static int MSG_UPDATE_CONFIGURATION = 18;
private final static int MSG_PROCESS_INPUT_EVENTS = 19;
private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
private final static int MSG_INVALIDATE_WORLD = 22;
private final static int MSG_WINDOW_MOVED = 23;
private final static int MSG_SYNTHESIZE_INPUT_EVENT = 24;
private final static int MSG_DISPATCH_WINDOW_SHOWN = 25;
private final static int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
private final static int MSG_UPDATE_POINTER_ICON = 27;
final class ViewRootHandler extends Handler {
@Override
public String getMessageName(Message message) {
switch (message.what) {
case MSG_INVALIDATE:
return "MSG_INVALIDATE";
case MSG_INVALIDATE_RECT:
return "MSG_INVALIDATE_RECT";
case MSG_DIE:
return "MSG_DIE";
case MSG_RESIZED:
return "MSG_RESIZED";
case MSG_RESIZED_REPORT:
return "MSG_RESIZED_REPORT";
case MSG_WINDOW_FOCUS_CHANGED:
return "MSG_WINDOW_FOCUS_CHANGED";
case MSG_DISPATCH_INPUT_EVENT:
return "MSG_DISPATCH_INPUT_EVENT";
case MSG_DISPATCH_APP_VISIBILITY:
return "MSG_DISPATCH_APP_VISIBILITY";
case MSG_DISPATCH_GET_NEW_SURFACE:
return "MSG_DISPATCH_GET_NEW_SURFACE";
case MSG_DISPATCH_KEY_FROM_IME:
return "MSG_DISPATCH_KEY_FROM_IME";
case MSG_CHECK_FOCUS:
return "MSG_CHECK_FOCUS";
case MSG_CLOSE_SYSTEM_DIALOGS:
return "MSG_CLOSE_SYSTEM_DIALOGS";
case MSG_DISPATCH_DRAG_EVENT:
return "MSG_DISPATCH_DRAG_EVENT";
case MSG_DISPATCH_DRAG_LOCATION_EVENT:
return "MSG_DISPATCH_DRAG_LOCATION_EVENT";
case MSG_DISPATCH_SYSTEM_UI_VISIBILITY:
return "MSG_DISPATCH_SYSTEM_UI_VISIBILITY";
case MSG_UPDATE_CONFIGURATION:
return "MSG_UPDATE_CONFIGURATION";
case MSG_PROCESS_INPUT_EVENTS:
return "MSG_PROCESS_INPUT_EVENTS";
case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST:
return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
case MSG_WINDOW_MOVED:
return "MSG_WINDOW_MOVED";
case MSG_SYNTHESIZE_INPUT_EVENT:
return "MSG_SYNTHESIZE_INPUT_EVENT";
case MSG_DISPATCH_WINDOW_SHOWN:
return "MSG_DISPATCH_WINDOW_SHOWN";
case MSG_UPDATE_POINTER_ICON:
return "MSG_UPDATE_POINTER_ICON";
}
return super.getMessageName(message);
}
@Override
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
if (msg.what == MSG_REQUEST_KEYBOARD_SHORTCUTS && msg.obj == null) {
// Debugging for b/27963013
throw new NullPointerException(
"Attempted to call MSG_REQUEST_KEYBOARD_SHORTCUTS with null receiver:");
}
return super.sendMessageAtTime(msg, uptimeMillis);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
case MSG_INVALIDATE_RECT:
final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
info.target.invalidate(info.left, info.top, info.right, info.bottom);
info.recycle();
break;
case MSG_PROCESS_INPUT_EVENTS:
mProcessInputEventsScheduled = false;
doProcessInputEvents();
break;
case MSG_DISPATCH_APP_VISIBILITY:
handleAppVisibility(msg.arg1 != 0);
break;
case MSG_DISPATCH_GET_NEW_SURFACE:
handleGetNewSurface();
break;
case MSG_RESIZED: {
// Recycled in the fall through...
SomeArgs args = (SomeArgs) msg.obj;
if (mWinFrame.equals(args.arg1)
&& mPendingOverscanInsets.equals(args.arg5)
&& mPendingContentInsets.equals(args.arg2)
&& mPendingStableInsets.equals(args.arg6)
&& mPendingVisibleInsets.equals(args.arg3)
&& mPendingOutsets.equals(args.arg7)
&& mPendingBackDropFrame.equals(args.arg8)
&& args.arg4 == null
&& args.argi1 == 0) {
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);
}
final boolean framesChanged = !mWinFrame.equals(args.arg1)
|| !mPendingOverscanInsets.equals(args.arg5)
|| !mPendingContentInsets.equals(args.arg2)
|| !mPendingStableInsets.equals(args.arg6)
|| !mPendingVisibleInsets.equals(args.arg3)
|| !mPendingOutsets.equals(args.arg7);
mWinFrame.set((Rect) args.arg1);
mPendingOverscanInsets.set((Rect) args.arg5);
mPendingContentInsets.set((Rect) args.arg2);
mPendingStableInsets.set((Rect) args.arg6);
mPendingVisibleInsets.set((Rect) args.arg3);
mPendingOutsets.set((Rect) args.arg7);
mPendingBackDropFrame.set((Rect) args.arg8);
mForceNextWindowRelayout = args.argi1 != 0;
mPendingAlwaysConsumeNavBar = args.argi2 != 0;
args.recycle();
if (msg.what == MSG_RESIZED_REPORT) {
mReportNextDraw = true;
}
if (mView != null && framesChanged) {
forceLayout(mView);
}
requestLayout();
}
break;
case MSG_WINDOW_MOVED:
if (mAdded) {
final int w = mWinFrame.width();
final int h = mWinFrame.height();
final int l = msg.arg1;
final int t = msg.arg2;
mWinFrame.left = l;
mWinFrame.right = l + w;
mWinFrame.top = t;
mWinFrame.bottom = t + h;
mPendingBackDropFrame.set(mWinFrame);
// Suppress layouts during resizing - a correct layout will happen when resizing
// is done, and this just increases system load.
boolean isDockedDivider = mWindowAttributes.type == TYPE_DOCK_DIVIDER;
boolean suppress = (mDragResizing && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER)
|| isDockedDivider;
if (!suppress) {
if (mView != null) {
forceLayout(mView);
}
requestLayout();
} else {
maybeHandleWindowMove(mWinFrame);
}
}
break;
case MSG_WINDOW_FOCUS_CHANGED: {
if (mAdded) {
boolean hasWindowFocus = msg.arg1 != 0;
mAttachInfo.mHasWindowFocus = hasWindowFocus;
profileRendering(hasWindowFocus);
if (hasWindowFocus) {
boolean inTouchMode = msg.arg2 != 0;
ensureTouchModeLocally(inTouchMode);
if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){
mFullRedrawNeeded = true;
try {
final WindowManager.LayoutParams lp = mWindowAttributes;
final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
Log.e(mTag, "OutOfResourcesException locking surface", e);
try {
if (!mWindowSession.outOfMemory(mWindow)) {
Slog.w(mTag, "No processes killed for memory; killing self");
Process.killProcess(Process.myPid());
}
} catch (RemoteException ex) {
}
// Retry in a bit.
sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
return;
}
}
}
mLastWasImTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
imm.onPreWindowFocus(mView, hasWindowFocus);
}
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
}
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
// Clear the forward bit. We can just do this directly, since
// the window manager doesn't care about it.
mWindowAttributes.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
((WindowManager.LayoutParams)mView.getLayoutParams())
.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
mHasHadWindowFocus = true;
}
}
} break;
case MSG_DIE:
doDie();
break;
case MSG_DISPATCH_INPUT_EVENT: {
SomeArgs args = (SomeArgs)msg.obj;
InputEvent event = (InputEvent)args.arg1;
InputEventReceiver receiver = (InputEventReceiver)args.arg2;
enqueueInputEvent(event, receiver, 0, true);
args.recycle();
} break;
case MSG_SYNTHESIZE_INPUT_EVENT: {
InputEvent event = (InputEvent)msg.obj;
enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
} break;
case MSG_DISPATCH_KEY_FROM_IME: {
if (LOCAL_LOGV) Log.v(
TAG, "Dispatching key "
+ msg.obj + " from IME to " + mView);
KeyEvent event = (KeyEvent)msg.obj;
if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
// The IME is trying to say this event is from the
// system! Bad bad bad!
//noinspection UnusedAssignment
event = KeyEvent.changeFlags(event, event.getFlags() &
~KeyEvent.FLAG_FROM_SYSTEM);
}
enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
} break;
case MSG_CHECK_FOCUS: {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
imm.checkFocus();
}
} break;
case MSG_CLOSE_SYSTEM_DIALOGS: {
if (mView != null) {
mView.onCloseSystemDialogs((String)msg.obj);
}
} break;
case MSG_DISPATCH_DRAG_EVENT:
case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
DragEvent event = (DragEvent)msg.obj;
event.mLocalState = mLocalDragState; // only present when this app called startDrag()
handleDragEvent(event);
} break;
case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
} break;
case MSG_UPDATE_CONFIGURATION: {
Configuration config = (Configuration)msg.obj;
if (config.isOtherSeqNewer(mLastConfiguration)) {
config = mLastConfiguration;
}
updateConfiguration(config, false);
} break;
case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
setAccessibilityFocus(null, null);
} break;
case MSG_INVALIDATE_WORLD: {
if (mView != null) {
invalidateWorld(mView);
}
} break;
case MSG_DISPATCH_WINDOW_SHOWN: {
handleDispatchWindowShown();
} break;
case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
final IResultReceiver receiver = (IResultReceiver) msg.obj;
final int deviceId = msg.arg1;
handleRequestKeyboardShortcuts(receiver, deviceId);
} break;
case MSG_UPDATE_POINTER_ICON: {
MotionEvent event = (MotionEvent) msg.obj;
resetPointerIcon(event);
} break;
}
}
}
final ViewRootHandler mHandler = new ViewRootHandler();
/**
* Something in the current window tells us we need to change the touch mode. For
* example, we are not in touch mode, and the user touches the screen.
*
* If the touch mode has changed, tell the window manager, and handle it locally.
*
* @param inTouchMode Whether we want to be in touch mode.
* @return True if the touch mode changed and focus changed was changed as a result
*/
boolean ensureTouchMode(boolean inTouchMode) {
if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current "
+ "touch mode is " + mAttachInfo.mInTouchMode);
if (mAttachInfo.mInTouchMode == inTouchMode) return false;
// tell the window manager
try {
mWindowSession.setInTouchMode(inTouchMode);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
// handle the change
return ensureTouchModeLocally(inTouchMode);
}
/**
* Ensure that the touch mode for this window is set, and if it is changing,
* take the appropriate action.
* @param inTouchMode Whether we want to be in touch mode.
* @return True if the touch mode changed and focus changed was changed as a result
*/
private boolean ensureTouchModeLocally(boolean inTouchMode) {
if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current "
+ "touch mode is " + mAttachInfo.mInTouchMode);
if (mAttachInfo.mInTouchMode == inTouchMode) return false;
mAttachInfo.mInTouchMode = inTouchMode;
mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
}
private boolean enterTouchMode() {
if (mView != null && mView.hasFocus()) {
// note: not relying on mFocusedView here because this could
// be when the window is first being added, and mFocused isn't
// set yet.
final View focused = mView.findFocus();
if (focused != null && !focused.isFocusableInTouchMode()) {
final ViewGroup ancestorToTakeFocus = findAncestorToTakeFocusInTouchMode(focused);
if (ancestorToTakeFocus != null) {
// there is an ancestor that wants focus after its
// descendants that is focusable in touch mode.. give it
// focus
return ancestorToTakeFocus.requestFocus();
} else {
// There's nothing to focus. Clear and propagate through the
// hierarchy, but don't attempt to place new focus.
focused.clearFocusInternal(null, true, false);
return true;
}
}
}
return false;
}
/**
* Find an ancestor of focused that wants focus after its descendants and is
* focusable in touch mode.
* @param focused The currently focused view.
* @return An appropriate view, or null if no such view exists.
*/
private static ViewGroup findAncestorToTakeFocusInTouchMode(View focused) {
ViewParent parent = focused.getParent();
while (parent instanceof ViewGroup) {
final ViewGroup vgParent = (ViewGroup) parent;
if (vgParent.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
&& vgParent.isFocusableInTouchMode()) {
return vgParent;
}
if (vgParent.isRootNamespace()) {
return null;
} else {
parent = vgParent.getParent();
}
}
return null;
}
private boolean leaveTouchMode() {
if (mView != null) {
if (mView.hasFocus()) {
View focusedView = mView.findFocus();
if (!(focusedView instanceof ViewGroup)) {
// some view has focus, let it keep it
return false;
} else if (((ViewGroup) focusedView).getDescendantFocusability() !=
ViewGroup.FOCUS_AFTER_DESCENDANTS) {
// some view group has focus, and doesn't prefer its children
// over itself for focus, so let them keep it.
return false;
}
}
// find the best view to give focus to in this brave new non-touch-mode
// world
final View focused = focusSearch(null, View.FOCUS_DOWN);
if (focused != null) {
return focused.requestFocus(View.FOCUS_DOWN);
}
}
return false;
}
/**
* Base class for implementing a stage in the chain of responsibility
* for processing input events.
* <p>
* Events are delivered to the stage by the {@link #deliver} method. The stage
* then has the choice of finishing the event or forwarding it to the next stage.
* </p>
*/
abstract class InputStage {
private final InputStage mNext;
protected static final int FORWARD = 0;
protected static final int FINISH_HANDLED = 1;
protected static final int FINISH_NOT_HANDLED = 2;
/**
* Creates an input stage.
* @param next The next stage to which events should be forwarded.
*/
public InputStage(InputStage next) {
mNext = next;
}
/**
* Delivers an event to be processed.
*/
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q));
}
}
/**
* Marks the the input event as finished then forwards it to the next stage.
*/
protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
if (handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
}
forward(q);
}
/**
* Forwards the event to the next stage.
*/
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
/**
* Applies a result code from {@link #onProcess} to the specified event.
*/
protected void apply(QueuedInputEvent q, int result) {
if (result == FORWARD) {
forward(q);
} else if (result == FINISH_HANDLED) {
finish(q, true);
} else if (result == FINISH_NOT_HANDLED) {
finish(q, false);
} else {
throw new IllegalArgumentException("Invalid result: " + result);
}
}
/**
* Called when an event is ready to be processed.
* @return A result code indicating how the event was handled.
*/
protected int onProcess(QueuedInputEvent q) {
return FORWARD;
}
/**
* Called when an event is being delivered to the next stage.
*/
protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
if (mNext != null) {
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
protected boolean shouldDropInputEvent(QueuedInputEvent q) {
if (mView == null || !mAdded) {
Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
return true;
} else if ((!mAttachInfo.mHasWindowFocus
&& !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped
|| (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
|| (mPausedForTransition && !isBack(q.mEvent))) {
// This is a focus event and the window doesn't currently have input focus or
// has stopped. This could be an event that came back from the previous stage
// but the window has lost focus or stopped in the meantime.
if (isTerminalInputEvent(q.mEvent)) {
// Don't drop terminal input events, however mark them as canceled.
q.mEvent.cancel();
Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent);
return false;
}
// Drop non-terminal input events.
Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent);
return true;
}
return false;
}
void dump(String prefix, PrintWriter writer) {
if (mNext != null) {
mNext.dump(prefix, writer);
}
}
private boolean isBack(InputEvent event) {
if (event instanceof KeyEvent) {
return ((KeyEvent) event).getKeyCode() == KeyEvent.KEYCODE_BACK;
} else {
return false;
}
}
}
/**
* Base class for implementing an input pipeline stage that supports
* asynchronous and out-of-order processing of input events.
* <p>
* In addition to what a normal input stage can do, an asynchronous
* input stage may also defer an input event that has been delivered to it
* and finish or forward it later.
* </p>
*/
abstract class AsyncInputStage extends InputStage {
private final String mTraceCounter;
private QueuedInputEvent mQueueHead;
private QueuedInputEvent mQueueTail;
private int mQueueLength;
protected static final int DEFER = 3;
/**
* Creates an asynchronous input stage.
* @param next The next stage to which events should be forwarded.
* @param traceCounter The name of a counter to record the size of
* the queue of pending events.
*/
public AsyncInputStage(InputStage next, String traceCounter) {
super(next);
mTraceCounter = traceCounter;
}
/**
* Marks the event as deferred, which is to say that it will be handled
* asynchronously. The caller is responsible for calling {@link #forward}
* or {@link #finish} later when it is done handling the event.
*/
protected void defer(QueuedInputEvent q) {
q.mFlags |= QueuedInputEvent.FLAG_DEFERRED;
enqueue(q);
}
@Override
protected void forward(QueuedInputEvent q) {
// Clear the deferred flag.
q.mFlags &= ~QueuedInputEvent.FLAG_DEFERRED;
// Fast path if the queue is empty.
QueuedInputEvent curr = mQueueHead;
if (curr == null) {
super.forward(q);
return;
}
// Determine whether the event must be serialized behind any others
// before it can be delivered to the next stage. This is done because
// deferred events might be handled out of order by the stage.
final int deviceId = q.mEvent.getDeviceId();
QueuedInputEvent prev = null;
boolean blocked = false;
while (curr != null && curr != q) {
if (!blocked && deviceId == curr.mEvent.getDeviceId()) {
blocked = true;
}
prev = curr;
curr = curr.mNext;
}
// If the event is blocked, then leave it in the queue to be delivered later.
// Note that the event might not yet be in the queue if it was not previously
// deferred so we will enqueue it if needed.
if (blocked) {
if (curr == null) {
enqueue(q);
}
return;
}
// The event is not blocked. Deliver it immediately.
if (curr != null) {
curr = curr.mNext;
dequeue(q, prev);
}
super.forward(q);
// Dequeuing this event may have unblocked successors. Deliver them.
while (curr != null) {
if (deviceId == curr.mEvent.getDeviceId()) {
if ((curr.mFlags & QueuedInputEvent.FLAG_DEFERRED) != 0) {
break;
}
QueuedInputEvent next = curr.mNext;
dequeue(curr, prev);
super.forward(curr);
curr = next;
} else {
prev = curr;
curr = curr.mNext;
}
}
}
@Override
protected void apply(QueuedInputEvent q, int result) {
if (result == DEFER) {
defer(q);
} else {
super.apply(q, result);
}
}
private void enqueue(QueuedInputEvent q) {
if (mQueueTail == null) {
mQueueHead = q;
mQueueTail = q;
} else {
mQueueTail.mNext = q;
mQueueTail = q;
}
mQueueLength += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
}
private void dequeue(QueuedInputEvent q, QueuedInputEvent prev) {
if (prev == null) {
mQueueHead = q.mNext;
} else {
prev.mNext = q.mNext;
}
if (mQueueTail == q) {
mQueueTail = prev;
}
q.mNext = null;
mQueueLength -= 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
}
@Override
void dump(String prefix, PrintWriter writer) {
writer.print(prefix);
writer.print(getClass().getName());
writer.print(": mQueueLength=");
writer.println(mQueueLength);
super.dump(prefix, writer);
}
}
/**
* Delivers pre-ime input events to a native activity.
* Does not support pointer events.
*/
final class NativePreImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {
public NativePreImeInputStage(InputStage next, String traceCounter) {
super(next, traceCounter);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
mInputQueue.sendInputEvent(q.mEvent, q, true, this);
return DEFER;
}
return FORWARD;
}
@Override
public void onFinishedInputEvent(Object token, boolean handled) {
QueuedInputEvent q = (QueuedInputEvent)token;
if (handled) {
finish(q, true);
return;
}
forward(q);
}
}
/**
* Delivers pre-ime input events to the view hierarchy.
* Does not support pointer events.
*/
final class ViewPreImeInputStage extends InputStage {
public ViewPreImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
return FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEventPreIme(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
}
/**
* Delivers input events to the ime.
* Does not support pointer events.
*/
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {
public ImeInputStage(InputStage next, String traceCounter) {
super(next, traceCounter);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (mLastWasImTarget && !isInLocalFocusMode()) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
final InputEvent event = q.mEvent;
if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event);
int result = imm.dispatchInputEvent(event, q, this, mHandler);
if (result == InputMethodManager.DISPATCH_HANDLED) {
return FINISH_HANDLED;
} else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
// The IME could not handle it, so skip along to the next InputStage
return FORWARD;
} else {
return DEFER; // callback will be invoked later
}
}
}
return FORWARD;
}
@Override
public void onFinishedInputEvent(Object token, boolean handled) {
QueuedInputEvent q = (QueuedInputEvent)token;
if (handled) {
finish(q, true);
return;
}
forward(q);
}
}
/**
* Performs early processing of post-ime input events.
*/
final class EarlyPostImeInputStage extends InputStage {
public EarlyPostImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
}
}
return FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// If 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();
mLastTouchSource = event.getSource();
}
return FORWARD;
}
}
/**
* Delivers post-ime input events to a native activity.
*/
final class NativePostImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {
public NativePostImeInputStage(InputStage next, String traceCounter) {
super(next, traceCounter);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (mInputQueue != null) {
mInputQueue.sendInputEvent(q.mEvent, q, false, this);
return DEFER;
}
return FORWARD;
}
@Override
public void onFinishedInputEvent(Object token, boolean handled) {
QueuedInputEvent q = (QueuedInputEvent)token;
if (handled) {
finish(q, true);
return;
}
forward(q);
}
}
/**
* Delivers post-ime input events to the view hierarchy.
*/
final class ViewPostImeInputStage extends InputStage {
public ViewPostImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
@Override
protected void onDeliverToNext(QueuedInputEvent q) {
if (mUnbufferedInputDispatch
&& q.mEvent instanceof MotionEvent
&& ((MotionEvent)q.mEvent).isTouchEvent()
&& isTerminalInputEvent(q.mEvent)) {
mUnbufferedInputDispatch = false;
scheduleConsumeBatchedInput();
}
super.onDeliverToNext(q);
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// 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;
mAttachInfo.mUnbufferedDispatchRequested = false;
final View eventTarget =
(event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
mCapturingView : mView;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = eventTarget.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
private void maybeUpdatePointerIcon(MotionEvent event) {
if (event.getPointerCount() == 1 && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
|| event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
// Other apps or the window manager may change the icon type outside of
// this app, therefore the icon type has to be reset on enter/exit event.
mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
}
if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
if (!updatePointerIcon(event) &&
event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
}
}
}
}
private int processTrackballEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
if (mView.dispatchTrackballEvent(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
private int processGenericMotionEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
// Deliver the event to the view.
if (mView.dispatchGenericMotionEvent(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
}
private void resetPointerIcon(MotionEvent event) {
mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
updatePointerIcon(event);
}
private boolean updatePointerIcon(MotionEvent event) {
final int pointerIndex = 0;
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
if (mView == null) {
// E.g. click outside a popup to dismiss it
Slog.d(mTag, "updatePointerIcon called after view was removed");
return false;
}
if (x < 0 || x >= mView.getWidth() || y < 0 || y >= mView.getHeight()) {
// E.g. when moving window divider with mouse
Slog.d(mTag, "updatePointerIcon called with position out of bounds");
return false;
}
final PointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
final int pointerType = (pointerIcon != null) ?
pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;
if (mPointerIconType != pointerType) {
mPointerIconType = pointerType;
if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
mCustomPointerIcon = null;
InputManager.getInstance().setPointerIconType(pointerType);
return true;
}
}
if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
!pointerIcon.equals(mCustomPointerIcon)) {
mCustomPointerIcon = pointerIcon;
InputManager.getInstance().setCustomPointerIcon(mCustomPointerIcon);
}
return true;
}
/**
* Performs synthesis of new input events from unhandled input events.
*/
final class SyntheticInputStage extends InputStage {
private final SyntheticTrackballHandler mTrackball = new SyntheticTrackballHandler();
private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
private final SyntheticTouchNavigationHandler mTouchNavigation =
new SyntheticTouchNavigationHandler();
private final SyntheticKeyboardHandler mKeyboard = new SyntheticKeyboardHandler();
public SyntheticInputStage() {
super(null);
}
@Override
protected int onProcess(QueuedInputEvent q) {
q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
if (q.mEvent instanceof MotionEvent) {
final MotionEvent event = (MotionEvent)q.mEvent;
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
mTrackball.process(event);
return FINISH_HANDLED;
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
mJoystick.process(event);
return FINISH_HANDLED;
} else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
== InputDevice.SOURCE_TOUCH_NAVIGATION) {
mTouchNavigation.process(event);
return FINISH_HANDLED;
}
} else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) {
mKeyboard.process((KeyEvent)q.mEvent);
return FINISH_HANDLED;
}
return FORWARD;
}
@Override
protected void onDeliverToNext(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) {
// Cancel related synthetic events if any prior stage has handled the event.
if (q.mEvent instanceof MotionEvent) {
final MotionEvent event = (MotionEvent)q.mEvent;
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
mTrackball.cancel(event);
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
mJoystick.cancel(event);
} else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
== InputDevice.SOURCE_TOUCH_NAVIGATION) {
mTouchNavigation.cancel(event);
}
}
}
super.onDeliverToNext(q);
}
}
/**
* Creates dpad events from unhandled trackball movements.
*/
final class SyntheticTrackballHandler {
private final TrackballAxis mX = new TrackballAxis();
private final TrackballAxis mY = new TrackballAxis();
private long mLastTime;
public void process(MotionEvent event) {
// Translate the trackball event into DPAD keys and try to deliver those.
long curTime = SystemClock.uptimeMillis();
if ((mLastTime + MAX_TRACKBALL_DELAY) < curTime) {
// It has been too long since the last movement,
// so restart at the beginning.
mX.reset(0);
mY.reset(0);
mLastTime = curTime;
}
final int action = event.getAction();
final int metaState = event.getMetaState();
switch (action) {
case MotionEvent.ACTION_DOWN:
mX.reset(2);
mY.reset(2);
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
break;
case MotionEvent.ACTION_UP:
mX.reset(2);
mY.reset(2);
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
break;
}
if (DEBUG_TRACKBALL) Log.v(mTag, "TB X=" + mX.position + " step="
+ mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
+ " move=" + event.getX()
+ " / Y=" + mY.position + " step="
+ mY.step + " dir=" + mY.dir + " acc=" + mY.acceleration
+ " move=" + event.getY());
final float xOff = mX.collect(event.getX(), event.getEventTime(), "X");
final float yOff = mY.collect(event.getY(), event.getEventTime(), "Y");
// Generate DPAD events based on the trackball movement.
// We pick the axis that has moved the most as the direction of
// the DPAD. When we generate DPAD events for one axis, then the
// other axis is reset -- we don't want to perform DPAD jumps due
// to slight movements in the trackball when making major movements
// along the other axis.
int keycode = 0;
int movement = 0;
float accel = 1;
if (xOff > yOff) {
movement = mX.generate();
if (movement != 0) {
keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
: KeyEvent.KEYCODE_DPAD_LEFT;
accel = mX.acceleration;
mY.reset(2);
}
} else if (yOff > 0) {
movement = mY.generate();
if (movement != 0) {
keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
: KeyEvent.KEYCODE_DPAD_UP;
accel = mY.acceleration;
mX.reset(2);
}
}
if (keycode != 0) {
if (movement < 0) movement = -movement;
int accelMovement = (int)(movement * accel);
if (DEBUG_TRACKBALL) Log.v(mTag, "Move: movement=" + movement
+ " accelMovement=" + accelMovement
+ " accel=" + accel);
if (accelMovement > movement) {
if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+ keycode);
movement--;
int repeatCount = accelMovement - movement;
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
}
while (movement > 0) {
if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
+ keycode);
movement--;
curTime = SystemClock.uptimeMillis();
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_DOWN, keycode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
enqueueInputEvent(new KeyEvent(curTime, curTime,
KeyEvent.ACTION_UP, keycode, 0, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
InputDevice.SOURCE_KEYBOARD));
}
mLastTime = curTime;
}
}
public void cancel(MotionEvent event) {
mLastTime = Integer.MIN_VALUE;
// If we reach this, we consumed a trackball event.
// Because we will not translate the trackball event into a key event,
// touch mode will not exit, so we exit touch mode here.
if (mView != null && mAdded) {
ensureTouchMode(false);
}
}
}
/**
* Maintains state information for a single trackball axis, generating
* discrete (DPAD) movements based on raw trackball motion.
*/
static final class TrackballAxis {
/**
* The maximum amount of acceleration we will apply.
*/
static final float MAX_ACCELERATION = 20;
/**
* The maximum amount of time (in milliseconds) between events in order
* for us to consider the user to be doing fast trackball movements,
* and thus apply an acceleration.
*/
static final long FAST_MOVE_TIME = 150;
/**
* Scaling factor to the time (in milliseconds) between events to how
* much to multiple/divide the current acceleration. When movement
* is < FAST_MOVE_TIME this multiplies the acceleration; when >
* FAST_MOVE_TIME it divides it.
*/
static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
float position;
float acceleration = 1;
long lastMoveTime = 0;
int step;
int dir;
int nonAccelMovement;
void reset(int _step) {
position = 0;
acceleration = 1;
lastMoveTime = 0;
step = _step;
dir = 0;
}
/**
* Add trackball movement into the state. If the direction of movement
* has been reversed, the state is reset before adding the
* movement (so that you don't have to compensate for any previously
* collected movement before see the result of the movement in the
* new direction).
*
* @return Returns the absolute value of the amount of movement
* collected so far.
*/
float collect(float off, long time, String axis) {
long normTime;
if (off > 0) {
normTime = (long)(off * FAST_MOVE_TIME);
if (dir < 0) {
if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
position = 0;
step = 0;
acceleration = 1;
lastMoveTime = 0;
}
dir = 1;
} else if (off < 0) {
normTime = (long)((-off) * FAST_MOVE_TIME);
if (dir > 0) {
if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
position = 0;
step = 0;
acceleration = 1;
lastMoveTime = 0;
}
dir = -1;
} else {
normTime = 0;
}
// The number of milliseconds between each movement that is
// considered "normal" and will not result in any acceleration
// or deceleration, scaled by the offset we have here.
if (normTime > 0) {
long delta = time - lastMoveTime;
lastMoveTime = time;
float acc = acceleration;
if (delta < normTime) {
// The user is scrolling rapidly, so increase acceleration.
float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
if (scale > 1) acc *= scale;
if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+ off + " normTime=" + normTime + " delta=" + delta
+ " scale=" + scale + " acc=" + acc);
acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
} else {
// The user is scrolling slowly, so decrease acceleration.
float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
if (scale > 1) acc /= scale;
if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+ off + " normTime=" + normTime + " delta=" + delta
+ " scale=" + scale + " acc=" + acc);
acceleration = acc > 1 ? acc : 1;
}
}
position += off;
return Math.abs(position);
}
/**
* Generate the number of discrete movement events appropriate for
* the currently collected trackball movement.
*
* @return Returns the number of discrete movements, either positive
* or negative, or 0 if there is not enough trackball movement yet
* for a discrete movement.
*/
int generate() {
int movement = 0;
nonAccelMovement = 0;
do {
final int dir = position >= 0 ? 1 : -1;
switch (step) {
// If we are going to execute the first step, then we want
// to do this as soon as possible instead of waiting for
// a full movement, in order to make things look responsive.
case 0:
if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
return movement;
}
movement += dir;
nonAccelMovement += dir;
step = 1;
break;
// If we have generated the first movement, then we need
// to wait for the second complete trackball motion before
// generating the second discrete movement.
case 1:
if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
return movement;
}
movement += dir;
nonAccelMovement += dir;
position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
step = 2;
break;
// After the first two, we generate discrete movements
// consistently with the trackball, applying an acceleration
// if the trackball is moving quickly. This is a simple
// acceleration on top of what we already compute based
// on how quickly the wheel is being turned, to apply
// a longer increasing acceleration to continuous movement
// in one direction.
default:
if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
return movement;
}
movement += dir;
position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
float acc = acceleration;
acc *= 1.1f;
acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
break;
}
} while (true);
}
}
/**
* Creates dpad events from unhandled joystick movements.
*/
final class SyntheticJoystickHandler extends Handler {
private final static String TAG = "SyntheticJoystickHandler";
private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
private int mLastXDirection;
private int mLastYDirection;
private int mLastXKeyCode;
private int mLastYKeyCode;
public SyntheticJoystickHandler() {
super(true);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
KeyEvent oldEvent = (KeyEvent)msg.obj;
KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
SystemClock.uptimeMillis(),
oldEvent.getRepeatCount() + 1);
if (mAttachInfo.mHasWindowFocus) {
enqueueInputEvent(e);
Message m = obtainMessage(msg.what, e);
m.setAsynchronous(true);
sendMessageDelayed(m, ViewConfiguration.getKeyRepeatDelay());
}
} break;
}
}
public void process(MotionEvent event) {
switch(event.getActionMasked()) {
case MotionEvent.ACTION_CANCEL:
cancel(event);
break;
case MotionEvent.ACTION_MOVE:
update(event, true);
break;
default:
Log.w(mTag, "Unexpected action: " + event.getActionMasked());
}
}
private void cancel(MotionEvent event) {
removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
update(event, false);
}
private void update(MotionEvent event, boolean synthesizeNewKeys) {
final long time = event.getEventTime();
final int metaState = event.getMetaState();
final int deviceId = event.getDeviceId();
final int source = event.getSource();
int xDirection = joystickAxisValueToDirection(
event.getAxisValue(MotionEvent.AXIS_HAT_X));
if (xDirection == 0) {
xDirection = joystickAxisValueToDirection(event.getX());
}
int yDirection = joystickAxisValueToDirection(
event.getAxisValue(MotionEvent.AXIS_HAT_Y));
if (yDirection == 0) {
yDirection = joystickAxisValueToDirection(event.getY());
}
if (xDirection != mLastXDirection) {
if (mLastXKeyCode != 0) {
removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
enqueueInputEvent(new KeyEvent(time, time,
KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
mLastXKeyCode = 0;
}
mLastXDirection = xDirection;
if (xDirection != 0 && synthesizeNewKeys) {
mLastXKeyCode = xDirection > 0
? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
final KeyEvent e = new KeyEvent(time, time,
KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
enqueueInputEvent(e);
Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
m.setAsynchronous(true);
sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
}
}
if (yDirection != mLastYDirection) {
if (mLastYKeyCode != 0) {
removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
enqueueInputEvent(new KeyEvent(time, time,
KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
mLastYKeyCode = 0;
}
mLastYDirection = yDirection;
if (yDirection != 0 && synthesizeNewKeys) {
mLastYKeyCode = yDirection > 0
? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
final KeyEvent e = new KeyEvent(time, time,
KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
enqueueInputEvent(e);
Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
m.setAsynchronous(true);
sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
}
}
}
private int joystickAxisValueToDirection(float value) {
if (value >= 0.5f) {
return 1;
} else if (value <= -0.5f) {
return -1;
} else {
return 0;
}
}
}
/**
* Creates dpad events from unhandled touch navigation movements.
*/
final class SyntheticTouchNavigationHandler extends Handler {
private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler";
private static final boolean LOCAL_DEBUG = false;
// Assumed nominal width and height in millimeters of a touch navigation pad,
// if no resolution information is available from the input system.
private static final float DEFAULT_WIDTH_MILLIMETERS = 48;
private static final float DEFAULT_HEIGHT_MILLIMETERS = 48;
/* TODO: These constants should eventually be moved to ViewConfiguration. */
// The nominal distance traveled to move by one unit.
private static final int TICK_DISTANCE_MILLIMETERS = 12;
// Minimum and maximum fling velocity in ticks per second.
// The minimum velocity should be set such that we perform enough ticks per
// second that the fling appears to be fluid. For example, if we set the minimum
// to 2 ticks per second, then there may be up to half a second delay between the next
// to last and last ticks which is noticeably discrete and jerky. This value should
// probably not be set to anything less than about 4.
// If fling accuracy is a problem then consider tuning the tick distance instead.
private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f;
private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f;
// Fling velocity decay factor applied after each new key is emitted.
// This parameter controls the deceleration and overall duration of the fling.
// The fling stops automatically when its velocity drops below the minimum
// fling velocity defined above.
private static final float FLING_TICK_DECAY = 0.8f;
/* The input device that we are tracking. */
private int mCurrentDeviceId = -1;
private int mCurrentSource;
private boolean mCurrentDeviceSupported;
/* Configuration for the current input device. */
// The scaled tick distance. A movement of this amount should generally translate
// into a single dpad event in a given direction.
private float mConfigTickDistance;
// The minimum and maximum scaled fling velocity.
private float mConfigMinFlingVelocity;
private float mConfigMaxFlingVelocity;
/* Tracking state. */
// The velocity tracker for detecting flings.
private VelocityTracker mVelocityTracker;
// The active pointer id, or -1 if none.
private int mActivePointerId = -1;
// Location where tracking started.
private float mStartX;
private float mStartY;
// Most recently observed position.
private float mLastX;
private float mLastY;
// Accumulated movement delta since the last direction key was sent.
private float mAccumulatedX;
private float mAccumulatedY;
// Set to true if any movement was delivered to the app.
// Implies that tap slop was exceeded.
private boolean mConsumedMovement;
// The most recently sent key down event.
// The keycode remains set until the direction changes or a fling ends
// so that repeated key events may be generated as required.
private long mPendingKeyDownTime;
private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
private int mPendingKeyRepeatCount;
private int mPendingKeyMetaState;
// The current fling velocity while a fling is in progress.
private boolean mFlinging;
private float mFlingVelocity;
public SyntheticTouchNavigationHandler() {
super(true);
}
public void process(MotionEvent event) {
// Update the current device information.
final long time = event.getEventTime();
final int deviceId = event.getDeviceId();
final int source = event.getSource();
if (mCurrentDeviceId != deviceId || mCurrentSource != source) {
finishKeys(time);
finishTracking(time);
mCurrentDeviceId = deviceId;
mCurrentSource = source;
mCurrentDeviceSupported = false;
InputDevice device = event.getDevice();
if (device != null) {
// In order to support an input device, we must know certain
// characteristics about it, such as its size and resolution.
InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X);
InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y);
if (xRange != null && yRange != null) {
mCurrentDeviceSupported = true;
// Infer the resolution if it not actually known.
float xRes = xRange.getResolution();
if (xRes <= 0) {
xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS;
}
float yRes = yRange.getResolution();
if (yRes <= 0) {
yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS;
}
float nominalRes = (xRes + yRes) * 0.5f;
// Precompute all of the configuration thresholds we will need.
mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes;
mConfigMinFlingVelocity =
MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
mConfigMaxFlingVelocity =
MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
if (LOCAL_DEBUG) {
Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId
+ " (" + Integer.toHexString(mCurrentSource) + "): "
+ ", mConfigTickDistance=" + mConfigTickDistance
+ ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity
+ ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity);
}
}
}
}
if (!mCurrentDeviceSupported) {
return;
}
// Handle the event.
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
boolean caughtFling = mFlinging;
finishKeys(time);
finishTracking(time);
mActivePointerId = event.getPointerId(0);
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(event);
mStartX = event.getX();
mStartY = event.getY();
mLastX = mStartX;
mLastY = mStartY;
mAccumulatedX = 0;
mAccumulatedY = 0;
// If we caught a fling, then pretend that the tap slop has already
// been exceeded to suppress taps whose only purpose is to stop the fling.
mConsumedMovement = caughtFling;
break;
}
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP: {
if (mActivePointerId < 0) {
break;
}
final int index = event.findPointerIndex(mActivePointerId);
if (index < 0) {
finishKeys(time);
finishTracking(time);
break;
}
mVelocityTracker.addMovement(event);
final float x = event.getX(index);
final float y = event.getY(index);
mAccumulatedX += x - mLastX;
mAccumulatedY += y - mLastY;
mLastX = x;
mLastY = y;
// Consume any accumulated movement so far.
final int metaState = event.getMetaState();
consumeAccumulatedMovement(time, metaState);
// Detect taps and flings.
if (action == MotionEvent.ACTION_UP) {
if (mConsumedMovement && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
// It might be a fling.
mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity);
final float vx = mVelocityTracker.getXVelocity(mActivePointerId);
final float vy = mVelocityTracker.getYVelocity(mActivePointerId);
if (!startFling(time, vx, vy)) {
finishKeys(time);
}
}
finishTracking(time);
}
break;
}
case MotionEvent.ACTION_CANCEL: {
finishKeys(time);
finishTracking(time);
break;
}
}
}
public void cancel(MotionEvent event) {
if (mCurrentDeviceId == event.getDeviceId()
&& mCurrentSource == event.getSource()) {
final long time = event.getEventTime();
finishKeys(time);
finishTracking(time);
}
}
private void finishKeys(long time) {
cancelFling();
sendKeyUp(time);
}
private void finishTracking(long time) {
if (mActivePointerId >= 0) {
mActivePointerId = -1;
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
private void consumeAccumulatedMovement(long time, int metaState) {
final float absX = Math.abs(mAccumulatedX);
final float absY = Math.abs(mAccumulatedY);
if (absX >= absY) {
if (absX >= mConfigTickDistance) {
mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX,
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT);
mAccumulatedY = 0;
mConsumedMovement = true;
}
} else {
if (absY >= mConfigTickDistance) {
mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY,
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN);
mAccumulatedX = 0;
mConsumedMovement = true;
}
}
}
private float consumeAccumulatedMovement(long time, int metaState,
float accumulator, int negativeKeyCode, int positiveKeyCode) {
while (accumulator <= -mConfigTickDistance) {
sendKeyDownOrRepeat(time, negativeKeyCode, metaState);
accumulator += mConfigTickDistance;
}
while (accumulator >= mConfigTickDistance) {
sendKeyDownOrRepeat(time, positiveKeyCode, metaState);
accumulator -= mConfigTickDistance;
}
return accumulator;
}
private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) {
if (mPendingKeyCode != keyCode) {
sendKeyUp(time);
mPendingKeyDownTime = time;
mPendingKeyCode = keyCode;
mPendingKeyRepeatCount = 0;
} else {
mPendingKeyRepeatCount += 1;
}
mPendingKeyMetaState = metaState;
// Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1
// but it doesn't quite make sense when simulating the events in this way.
if (LOCAL_DEBUG) {
Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode
+ ", repeatCount=" + mPendingKeyRepeatCount
+ ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
}
enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount,
mPendingKeyMetaState, mCurrentDeviceId,
KeyEvent.FLAG_FALLBACK, mCurrentSource));
}
private void sendKeyUp(long time) {
if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
if (LOCAL_DEBUG) {
Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode
+ ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
}
enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState,
mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK,
mCurrentSource));
mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
}
}
private boolean startFling(long time, float vx, float vy) {
if (LOCAL_DEBUG) {
Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy
+ ", min=" + mConfigMinFlingVelocity);
}
// Flings must be oriented in the same direction as the preceding movements.
switch (mPendingKeyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (-vx >= mConfigMinFlingVelocity
&& Math.abs(vy) < mConfigMinFlingVelocity) {
mFlingVelocity = -vx;
break;
}
return false;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (vx >= mConfigMinFlingVelocity
&& Math.abs(vy) < mConfigMinFlingVelocity) {
mFlingVelocity = vx;
break;
}
return false;
case KeyEvent.KEYCODE_DPAD_UP:
if (-vy >= mConfigMinFlingVelocity
&& Math.abs(vx) < mConfigMinFlingVelocity) {
mFlingVelocity = -vy;
break;
}
return false;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (vy >= mConfigMinFlingVelocity
&& Math.abs(vx) < mConfigMinFlingVelocity) {
mFlingVelocity = vy;
break;
}
return false;
}
// Post the first fling event.
mFlinging = postFling(time);
return mFlinging;
}
private boolean postFling(long time) {
// The idea here is to estimate the time when the pointer would have
// traveled one tick distance unit given the current fling velocity.
// This effect creates continuity of motion.
if (mFlingVelocity >= mConfigMinFlingVelocity) {
long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000);
postAtTime(mFlingRunnable, time + delay);
if (LOCAL_DEBUG) {
Log.d(LOCAL_TAG, "Posted fling: velocity="
+ mFlingVelocity + ", delay=" + delay
+ ", keyCode=" + mPendingKeyCode);
}
return true;
}
return false;
}
private void cancelFling() {
if (mFlinging) {
removeCallbacks(mFlingRunnable);
mFlinging = false;
}
}
private final Runnable mFlingRunnable = new Runnable() {
@Override
public void run() {
final long time = SystemClock.uptimeMillis();
sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState);
mFlingVelocity *= FLING_TICK_DECAY;
if (!postFling(time)) {
mFlinging = false;
finishKeys(time);
}
}
};
}
final class SyntheticKeyboardHandler {
public void process(KeyEvent event) {
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
return;
}
final KeyCharacterMap kcm = event.getKeyCharacterMap();
final int keyCode = event.getKeyCode();
final int metaState = event.getMetaState();
// Check for fallback actions specified by the key character map.
KeyCharacterMap.FallbackAction fallbackAction =
kcm.getFallbackAction(keyCode, metaState);
if (fallbackAction != null) {
final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
KeyEvent fallbackEvent = KeyEvent.obtain(
event.getDownTime(), event.getEventTime(),
event.getAction(), fallbackAction.keyCode,
event.getRepeatCount(), fallbackAction.metaState,
event.getDeviceId(), event.getScanCode(),
flags, event.getSource(), null);
fallbackAction.recycle();
enqueueInputEvent(fallbackEvent);
}
}
}
/**
* Returns true if the key is used for keyboard navigation.
* @param keyEvent The key event.
* @return True if the key is used for keyboard navigation.
*/
private static boolean isNavigationKey(KeyEvent keyEvent) {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_PAGE_UP:
case KeyEvent.KEYCODE_PAGE_DOWN:
case KeyEvent.KEYCODE_MOVE_HOME:
case KeyEvent.KEYCODE_MOVE_END:
case KeyEvent.KEYCODE_TAB:
case KeyEvent.KEYCODE_SPACE:
case KeyEvent.KEYCODE_ENTER:
return true;
}
return false;
}
/**
* Returns true if the key is used for typing.
* @param keyEvent The key event.
* @return True if the key is used for typing.
*/
private static boolean isTypingKey(KeyEvent keyEvent) {
return keyEvent.getUnicodeChar() > 0;
}
/**
* See if the key event means we should leave touch mode (and leave touch mode if so).
* @param event The key event.
* @return Whether this key event should be consumed (meaning the act of
* leaving touch mode alone is considered the event).
*/
private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) {
// Only relevant in touch mode.
if (!mAttachInfo.mInTouchMode) {
return false;
}
// Only consider leaving touch mode on DOWN or MULTIPLE actions, never on UP.
final int action = event.getAction();
if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_MULTIPLE) {
return false;
}
// Don't leave touch mode if the IME told us not to.
if ((event.getFlags() & KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
return false;
}
// If the key can be used for keyboard navigation then leave touch mode
// and select a focused view if needed (in ensureTouchMode).
// When a new focused view is selected, we consume the navigation key because
// navigation doesn't make much sense unless a view already has focus so
// the key's purpose is to set focus.
if (isNavigationKey(event)) {
return ensureTouchMode(false);
}
// If the key can be used for typing then leave touch mode
// and select a focused view if needed (in ensureTouchMode).
// Always allow the view to process the typing key.
if (isTypingKey(event)) {
ensureTouchMode(false);
return false;
}
return false;
}
/* drag/drop */
void setLocalDragState(Object obj) {
mLocalDragState = obj;
}
private void handleDragEvent(DragEvent event) {
// From the root, only drag start/end/location are dispatched. entered/exited
// are determined and dispatched by the viewgroup hierarchy, who then report
// that back here for ultimate reporting back to the framework.
if (mView != null && mAdded) {
final int what = event.mAction;
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(mTag, "Unable to note drag target change");
}
}
// Report the drop result when we're done
if (what == DragEvent.ACTION_DROP) {
mDragDescription = null;
try {
Log.i(mTag, "Reporting drop result: " + result);
mWindowSession.reportDropResult(mWindow, result);
} catch (RemoteException e) {
Log.e(mTag, "Unable to report drop result");
}
}
// When the drag operation ends, reset drag-related state
if (what == DragEvent.ACTION_DRAG_ENDED) {
setLocalDragState(null);
mAttachInfo.mDragToken = null;
if (mAttachInfo.mDragSurface != null) {
mAttachInfo.mDragSurface.release();
mAttachInfo.mDragSurface = null;
}
}
}
}
event.recycle();
}
public void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
if (mSeq != args.seq) {
// The sequence has changed, so we need to update our value and make
// sure to do a traversal afterward so the window manager is given our
// most recent data.
mSeq = args.seq;
mAttachInfo.mForceReportNewAttributes = true;
scheduleTraversals();
}
if (mView == null) return;
if (args.localChanges != 0) {
mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
}
int visibility = args.globalVisibility&View.SYSTEM_UI_CLEARABLE_FLAGS;
if (visibility != mAttachInfo.mGlobalSystemUiVisibility) {
mAttachInfo.mGlobalSystemUiVisibility = visibility;
mView.dispatchSystemUiVisibilityChanged(visibility);
}
}
public void handleDispatchWindowShown() {
mAttachInfo.mTreeObserver.dispatchOnWindowShown();
}
public void handleRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
Bundle data = new Bundle();
ArrayList<KeyboardShortcutGroup> list = new ArrayList<>();
if (mView != null) {
mView.requestKeyboardShortcuts(list, deviceId);
}
data.putParcelableArrayList(WindowManager.PARCEL_KEY_SHORTCUTS_ARRAY, list);
try {
receiver.send(0, data);
} catch (RemoteException e) {
}
}
public void getLastTouchPoint(Point outLocation) {
outLocation.x = (int) mLastTouchPoint.x;
outLocation.y = (int) mLastTouchPoint.y;
}
public int getLastTouchSource() {
return mLastTouchSource;
}
public void setDragFocus(View newDragTarget) {
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(mTag, "WindowLayout in layoutWindow:" + params);
}
mPendingConfiguration.seq = 0;
//Log.d(mTag, ">>>>>> CALLING relayout");
if (params != null && mOrigWindowType != params.type) {
// For compatibility with old apps, don't crash here.
if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Slog.w(mTag, "Window type can not be changed after "
+ "the window is added; ignoring change of " + mView);
params.type = mOrigWindowType;
}
}
int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration,
mSurface);
mPendingAlwaysConsumeNavBar =
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
//Log.d(mTag, "<<<<<< BACK FROM relayout");
if (restore) {
params.restore();
}
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets);
mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets);
}
return relayoutResult;
}
/**
* {@inheritDoc}
*/
@Override
public void playSoundEffect(int effectId) {
checkThread();
try {
final AudioManager audioManager = getAudioManager();
switch (effectId) {
case SoundEffectConstants.CLICK:
audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
return;
case SoundEffectConstants.NAVIGATION_DOWN:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
return;
case SoundEffectConstants.NAVIGATION_LEFT:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
return;
case SoundEffectConstants.NAVIGATION_RIGHT:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
return;
case SoundEffectConstants.NAVIGATION_UP:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
return;
default:
throw new IllegalArgumentException("unknown effect id " + effectId +
" not defined in " + SoundEffectConstants.class.getCanonicalName());
}
} catch (IllegalStateException e) {
// Exception thrown by getAudioManager() when mView is null
Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
e.printStackTrace();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean performHapticFeedback(int effectId, boolean always) {
try {
return mWindowSession.performHapticFeedback(mWindow, effectId, always);
} catch (RemoteException e) {
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public View focusSearch(View focused, int direction) {
checkThread();
if (!(mView instanceof ViewGroup)) {
return null;
}
return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
}
public void debug() {
mView.debug();
}
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
String innerPrefix = prefix + " ";
writer.print(prefix); writer.println("ViewRoot:");
writer.print(innerPrefix); writer.print("mAdded="); writer.print(mAdded);
writer.print(" mRemoved="); writer.println(mRemoved);
writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled=");
writer.println(mConsumeBatchedInputScheduled);
writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled=");
writer.println(mConsumeBatchedInputImmediatelyScheduled);
writer.print(innerPrefix); writer.print("mPendingInputEventCount=");
writer.println(mPendingInputEventCount);
writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled=");
writer.println(mProcessInputEventsScheduled);
writer.print(innerPrefix); writer.print("mTraversalScheduled=");
writer.print(mTraversalScheduled);
writer.print(innerPrefix); writer.print("mIsAmbientMode=");
writer.print(mIsAmbientMode);
if (mTraversalScheduled) {
writer.print(" (barrier="); writer.print(mTraversalBarrier); writer.println(")");
} else {
writer.println();
}
mFirstInputStage.dump(innerPrefix, writer);
mChoreographer.dump(prefix, writer);
writer.print(prefix); writer.println("View Hierarchy:");
dumpViewHierarchy(innerPrefix, writer, mView);
}
private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
writer.print(prefix);
if (view == null) {
writer.println("null");
return;
}
writer.println(view.toString());
if (!(view instanceof ViewGroup)) {
return;
}
ViewGroup grp = (ViewGroup)view;
final int N = grp.getChildCount();
if (N <= 0) {
return;
}
prefix = prefix + " ";
for (int i=0; i<N; i++) {
dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
}
}
public void dumpGfxInfo(int[] info) {
info[0] = info[1] = 0;
if (mView != null) {
getGfxInfo(mView, info);
}
}
private static void getGfxInfo(View view, int[] info) {
RenderNode renderNode = view.mRenderNode;
info[0]++;
if (renderNode != null) {
info[1] += renderNode.getDebugSize();
}
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
int count = group.getChildCount();
for (int i = 0; i < count; i++) {
getGfxInfo(group.getChildAt(i), info);
}
}
}
/**
* @param immediate True, do now if not in traversal. False, put on queue and do later.
* @return True, request has been queued. False, request has been completed.
*/
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
mSurface.release();
}
}
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
public void requestUpdateConfiguration(Configuration config) {
Message msg = mHandler.obtainMessage(MSG_UPDATE_CONFIGURATION, config);
mHandler.sendMessage(msg);
}
public void loadSystemProperties() {
mHandler.post(new Runnable() {
@Override
public void run() {
// Profiling
mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
profileRendering(mAttachInfo.mHasWindowFocus);
// Hardware rendering
if (mAttachInfo.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() {
ThreadedRenderer hardwareRenderer = mAttachInfo.mHardwareRenderer;
if (hardwareRenderer != null) {
if (mView != null) {
hardwareRenderer.destroyHardwareResources(mView);
}
hardwareRenderer.destroy();
hardwareRenderer.setRequested(false);
mAttachInfo.mHardwareRenderer = null;
mAttachInfo.mHardwareAccelerated = false;
}
}
public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
Configuration newConfig, Rect backDropFrame, boolean forceLayout,
boolean alwaysConsumeNavBar) {
if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString()
+ " contentInsets=" + contentInsets.toShortString()
+ " visibleInsets=" + visibleInsets.toShortString()
+ " reportDraw=" + reportDraw
+ " backDropFrame=" + backDropFrame);
// Tell all listeners that we are resizing the window so that the chrome can get
// updated as fast as possible on a separate thread,
if (mDragResizing) {
boolean fullscreen = frame.equals(backDropFrame);
synchronized (mWindowCallbacks) {
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onWindowSizeIsChanging(backDropFrame, fullscreen,
visibleInsets, stableInsets);
}
}
}
Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(frame);
mTranslator.translateRectInScreenToAppWindow(overscanInsets);
mTranslator.translateRectInScreenToAppWindow(contentInsets);
mTranslator.translateRectInScreenToAppWindow(visibleInsets);
}
SomeArgs args = SomeArgs.obtain();
final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
args.arg1 = sameProcessCall ? new Rect(frame) : frame;
args.arg2 = sameProcessCall ? new Rect(contentInsets) : contentInsets;
args.arg3 = sameProcessCall ? new Rect(visibleInsets) : visibleInsets;
args.arg4 = sameProcessCall && newConfig != null ? new Configuration(newConfig) : newConfig;
args.arg5 = sameProcessCall ? new Rect(overscanInsets) : overscanInsets;
args.arg6 = sameProcessCall ? new Rect(stableInsets) : stableInsets;
args.arg7 = sameProcessCall ? new Rect(outsets) : outsets;
args.arg8 = sameProcessCall ? new Rect(backDropFrame) : backDropFrame;
args.argi1 = forceLayout ? 1 : 0;
args.argi2 = alwaysConsumeNavBar ? 1 : 0;
msg.obj = args;
mHandler.sendMessage(msg);
}
public void dispatchMoved(int newX, int newY) {
if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
if (mTranslator != null) {
PointF point = new PointF(newX, newY);
mTranslator.translatePointInScreenToAppWindow(point);
newX = (int) (point.x + 0.5);
newY = (int) (point.y + 0.5);
}
Message msg = mHandler.obtainMessage(MSG_WINDOW_MOVED, newX, newY);
mHandler.sendMessage(msg);
}
/**
* Represents a pending input event that is waiting in a queue.
*
* Input events are processed in serial order by the timestamp specified by
* {@link InputEvent#getEventTimeNano()}. In general, the input dispatcher delivers
* one input event to the application at a time and waits for the application
* to finish handling it before delivering the next one.
*
* However, because the application or IME can synthesize and inject multiple
* key events at a time without going through the input dispatcher, we end up
* needing a queue on the application's side.
*/
private static final class QueuedInputEvent {
public static final int FLAG_DELIVER_POST_IME = 1 << 0;
public static final int FLAG_DEFERRED = 1 << 1;
public static final int FLAG_FINISHED = 1 << 2;
public static final int FLAG_FINISHED_HANDLED = 1 << 3;
public static final int FLAG_RESYNTHESIZED = 1 << 4;
public static final int FLAG_UNHANDLED = 1 << 5;
public QueuedInputEvent mNext;
public InputEvent mEvent;
public InputEventReceiver mReceiver;
public int mFlags;
public boolean shouldSkipIme() {
if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
return true;
}
return mEvent instanceof MotionEvent
&& mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
}
public boolean shouldSendToSynthesizer() {
if ((mFlags & FLAG_UNHANDLED) != 0) {
return true;
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("QueuedInputEvent{flags=");
boolean hasPrevious = false;
hasPrevious = flagToString("DELIVER_POST_IME", FLAG_DELIVER_POST_IME, hasPrevious, sb);
hasPrevious = flagToString("DEFERRED", FLAG_DEFERRED, hasPrevious, sb);
hasPrevious = flagToString("FINISHED", FLAG_FINISHED, hasPrevious, sb);
hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb);
hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb);
hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb);
if (!hasPrevious) {
sb.append("0");
}
sb.append(", hasNextQueuedEvent=" + (mEvent != null ? "true" : "false"));
sb.append(", hasInputEventReceiver=" + (mReceiver != null ? "true" : "false"));
sb.append(", mEvent=" + mEvent + "}");
return sb.toString();
}
private boolean flagToString(String name, int flag,
boolean hasPrevious, StringBuilder sb) {
if ((mFlags & flag) != 0) {
if (hasPrevious) {
sb.append("|");
}
sb.append(name);
return true;
}
return hasPrevious;
}
}
private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
InputEventReceiver receiver, int flags) {
QueuedInputEvent q = mQueuedInputEventPool;
if (q != null) {
mQueuedInputEventPoolSize -= 1;
mQueuedInputEventPool = q.mNext;
q.mNext = null;
} else {
q = new QueuedInputEvent();
}
q.mEvent = event;
q.mReceiver = receiver;
q.mFlags = flags;
return q;
}
private void recycleQueuedInputEvent(QueuedInputEvent q) {
q.mEvent = null;
q.mReceiver = null;
if (mQueuedInputEventPoolSize < MAX_QUEUED_INPUT_EVENT_POOL_SIZE) {
mQueuedInputEventPoolSize += 1;
q.mNext = mQueuedInputEventPool;
mQueuedInputEventPool = q;
}
}
void enqueueInputEvent(InputEvent event) {
enqueueInputEvent(event, null, 0, false);
}
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
private void scheduleProcessInputEvents() {
if (!mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = true;
Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
long eventTime = q.mEvent.getEventTimeNano();
long oldestEventTime = eventTime;
if (q.mEvent instanceof MotionEvent) {
MotionEvent me = (MotionEvent)q.mEvent;
if (me.getHistorySize() > 0) {
oldestEventTime = me.getHistoricalEventTimeNano(0);
}
}
mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
private void finishInputEvent(QueuedInputEvent q) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (q.mReceiver != null) {
boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
q.mReceiver.finishInputEvent(q.mEvent, handled);
} else {
q.mEvent.recycleIfNeededAfterDispatch();
}
recycleQueuedInputEvent(q);
}
private void adjustInputEventForCompatibility(InputEvent e) {
if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
MotionEvent motion = (MotionEvent) e;
final int mask =
MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
final int buttonState = motion.getButtonState();
final int compatButtonState = (buttonState & mask) >> 4;
if (compatButtonState != 0) {
motion.setButtonState(buttonState | compatButtonState);
}
}
}
static boolean isTerminalInputEvent(InputEvent event) {
if (event instanceof KeyEvent) {
final KeyEvent keyEvent = (KeyEvent)event;
return keyEvent.getAction() == KeyEvent.ACTION_UP;
} else {
final MotionEvent motionEvent = (MotionEvent)event;
final int action = motionEvent.getAction();
return action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_HOVER_EXIT;
}
}
void scheduleConsumeBatchedInput() {
if (!mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = true;
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
mConsumedBatchedInputRunnable, null);
}
}
void unscheduleConsumeBatchedInput() {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
mChoreographer.removeCallbacks(Choreographer.CALLBACK_INPUT,
mConsumedBatchedInputRunnable, null);
}
}
void scheduleConsumeBatchedInputImmediately() {
if (!mConsumeBatchedInputImmediatelyScheduled) {
unscheduleConsumeBatchedInput();
mConsumeBatchedInputImmediatelyScheduled = true;
mHandler.post(mConsumeBatchedInputImmediatelyRunnable);
}
}
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
&& frameTimeNanos != -1) {
// If we consumed a batch here, we want to go ahead and schedule the
// consumption of batched input events on the next frame. Otherwise, we would
// wait until we have more input events pending and might get starved by other
// things occurring in the process. If the frame time is -1, however, then
// we're in a non-batching mode, so there's no need to schedule this.
scheduleConsumeBatchedInput();
}
}
doProcessInputEvents();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
@Override
public void onBatchedInputEventPending() {
if (mUnbufferedInputDispatch) {
super.onBatchedInputEventPending();
} else {
scheduleConsumeBatchedInput();
}
}
@Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
}
}
WindowInputEventReceiver mInputEventReceiver;
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
}
final ConsumeBatchedInputRunnable mConsumedBatchedInputRunnable =
new ConsumeBatchedInputRunnable();
boolean mConsumeBatchedInputScheduled;
final class ConsumeBatchedInputImmediatelyRunnable implements Runnable {
@Override
public void run() {
doConsumeBatchedInput(-1);
}
}
final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable =
new ConsumeBatchedInputImmediatelyRunnable();
boolean mConsumeBatchedInputImmediatelyScheduled;
final class InvalidateOnAnimationRunnable implements Runnable {
private boolean mPosted;
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<AttachInfo.InvalidateInfo> mViewRects =
new ArrayList<AttachInfo.InvalidateInfo>();
private View[] mTempViews;
private AttachInfo.InvalidateInfo[] mTempViewRects;
public void addView(View view) {
synchronized (this) {
mViews.add(view);
postIfNeededLocked();
}
}
public void addViewRect(AttachInfo.InvalidateInfo info) {
synchronized (this) {
mViewRects.add(info);
postIfNeededLocked();
}
}
public void removeView(View view) {
synchronized (this) {
mViews.remove(view);
for (int i = mViewRects.size(); i-- > 0; ) {
AttachInfo.InvalidateInfo info = mViewRects.get(i);
if (info.target == view) {
mViewRects.remove(i);
info.recycle();
}
}
if (mPosted && mViews.isEmpty() && mViewRects.isEmpty()) {
mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, this, null);
mPosted = false;
}
}
}
@Override
public void run() {
final int viewCount;
final int viewRectCount;
synchronized (this) {
mPosted = false;
viewCount = mViews.size();
if (viewCount != 0) {
mTempViews = mViews.toArray(mTempViews != null
? mTempViews : new View[viewCount]);
mViews.clear();
}
viewRectCount = mViewRects.size();
if (viewRectCount != 0) {
mTempViewRects = mViewRects.toArray(mTempViewRects != null
? mTempViewRects : new AttachInfo.InvalidateInfo[viewRectCount]);
mViewRects.clear();
}
}
for (int i = 0; i < viewCount; i++) {
mTempViews[i].invalidate();
mTempViews[i] = null;
}
for (int i = 0; i < viewRectCount; i++) {
final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
info.target.invalidate(info.left, info.top, info.right, info.bottom);
info.recycle();
}
}
private void postIfNeededLocked() {
if (!mPosted) {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
mPosted = true;
}
}
}
final InvalidateOnAnimationRunnable mInvalidateOnAnimationRunnable =
new InvalidateOnAnimationRunnable();
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
public void dispatchInvalidateRectDelayed(AttachInfo.InvalidateInfo info,
long delayMilliseconds) {
final Message msg = mHandler.obtainMessage(MSG_INVALIDATE_RECT, info);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
public void dispatchInvalidateOnAnimation(View view) {
mInvalidateOnAnimationRunnable.addView(view);
}
public void dispatchInvalidateRectOnAnimation(AttachInfo.InvalidateInfo info) {
mInvalidateOnAnimationRunnable.addViewRect(info);
}
public void cancelInvalidate(View view) {
mHandler.removeMessages(MSG_INVALIDATE, view);
// fixme: might leak the AttachInfo.InvalidateInfo objects instead of returning
// them to the pool
mHandler.removeMessages(MSG_INVALIDATE_RECT, view);
mInvalidateOnAnimationRunnable.removeView(view);
}
public void dispatchInputEvent(InputEvent event) {
dispatchInputEvent(event, null);
}
public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = event;
args.arg2 = receiver;
Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
public void synthesizeInputEvent(InputEvent event) {
Message msg = mHandler.obtainMessage(MSG_SYNTHESIZE_INPUT_EVENT, event);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
public void dispatchKeyFromIme(KeyEvent event) {
Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
/**
* Reinject unhandled {@link InputEvent}s in order to synthesize fallbacks events.
*
* Note that it is the responsibility of the caller of this API to recycle the InputEvent it
* passes in.
*/
public void dispatchUnhandledInputEvent(InputEvent event) {
if (event instanceof MotionEvent) {
event = MotionEvent.obtain((MotionEvent) event);
}
synthesizeInputEvent(event);
}
public void dispatchAppVisibility(boolean visible) {
Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY);
msg.arg1 = visible ? 1 : 0;
mHandler.sendMessage(msg);
}
public void dispatchGetNewSurface() {
Message msg = mHandler.obtainMessage(MSG_DISPATCH_GET_NEW_SURFACE);
mHandler.sendMessage(msg);
}
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
Message msg = Message.obtain();
msg.what = MSG_WINDOW_FOCUS_CHANGED;
msg.arg1 = hasFocus ? 1 : 0;
msg.arg2 = inTouchMode ? 1 : 0;
mHandler.sendMessage(msg);
}
public void dispatchWindowShown() {
mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN);
}
public void dispatchCloseSystemDialogs(String reason) {
Message msg = Message.obtain();
msg.what = MSG_CLOSE_SYSTEM_DIALOGS;
msg.obj = reason;
mHandler.sendMessage(msg);
}
public void dispatchDragEvent(DragEvent event) {
final int what;
if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
what = MSG_DISPATCH_DRAG_LOCATION_EVENT;
mHandler.removeMessages(what);
} else {
what = MSG_DISPATCH_DRAG_EVENT;
}
Message msg = mHandler.obtainMessage(what, event);
mHandler.sendMessage(msg);
}
public void updatePointerIcon(float x, float y) {
final int what = MSG_UPDATE_POINTER_ICON;
mHandler.removeMessages(what);
final long now = SystemClock.uptimeMillis();
final MotionEvent event = MotionEvent.obtain(
0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
Message msg = mHandler.obtainMessage(what, event);
mHandler.sendMessage(msg);
}
public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
int localValue, int localChanges) {
SystemUiVisibilityInfo args = new SystemUiVisibilityInfo();
args.seq = seq;
args.globalVisibility = globalVisibility;
args.localValue = localValue;
args.localChanges = localChanges;
mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
}
public void dispatchCheckFocus() {
if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
// This will result in a call to checkFocus() below.
mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);
}
}
public void dispatchRequestKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
mHandler.obtainMessage(
MSG_REQUEST_KEYBOARD_SHORTCUTS, deviceId, 0, receiver).sendToTarget();
}
/**
* Post a callback to send a
* {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
* This event is send at most once every
* {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
*/
private void postSendWindowContentChangedCallback(View source, int changeType) {
if (mSendWindowContentChangedAccessibilityEvent == null) {
mSendWindowContentChangedAccessibilityEvent =
new SendWindowContentChangedAccessibilityEvent();
}
mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType);
}
/**
* Remove a posted callback to send a
* {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
*/
private void removeSendWindowContentChangedCallback() {
if (mSendWindowContentChangedAccessibilityEvent != null) {
mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
}
}
@Override
public boolean showContextMenuForChild(View originalView) {
return false;
}
@Override
public boolean showContextMenuForChild(View originalView, float x, float y) {
return false;
}
@Override
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
return null;
}
@Override
public ActionMode startActionModeForChild(
View originalView, ActionMode.Callback callback, int type) {
return null;
}
@Override
public void createContextMenu(ContextMenu menu) {
}
@Override
public void childDrawableStateChanged(View child) {
}
@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
if (mView == null || mStopped || mPausedForTransition) {
return false;
}
// Intercept accessibility focus events fired by virtual nodes to keep
// track of accessibility focus position in such nodes.
final int eventType = event.getEventType();
switch (eventType) {
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
final long sourceNodeId = event.getSourceNodeId();
final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
sourceNodeId);
View source = mView.findViewByAccessibilityId(accessibilityViewId);
if (source != null) {
AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
if (provider != null) {
final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
sourceNodeId);
final AccessibilityNodeInfo node;
if (virtualNodeId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
node = provider.createAccessibilityNodeInfo(
AccessibilityNodeProvider.HOST_VIEW_ID);
} else {
node = provider.createAccessibilityNodeInfo(virtualNodeId);
}
setAccessibilityFocus(source, node);
}
}
} break;
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
final long sourceNodeId = event.getSourceNodeId();
final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
sourceNodeId);
View source = mView.findViewByAccessibilityId(accessibilityViewId);
if (source != null) {
AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
if (provider != null) {
setAccessibilityFocus(null, null);
}
}
} break;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
handleWindowContentChangedEvent(event);
} break;
}
mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}
/**
* Updates the focused virtual view, when necessary, in response to a
* content changed event.
* <p>
* This is necessary to get updated bounds after a position change.
*
* @param event an accessibility event of type
* {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
*/
private void handleWindowContentChangedEvent(AccessibilityEvent event) {
final View focusedHost = mAccessibilityFocusedHost;
if (focusedHost == null || mAccessibilityFocusedVirtualView == null) {
// No virtual view focused, nothing to do here.
return;
}
final AccessibilityNodeProvider provider = focusedHost.getAccessibilityNodeProvider();
if (provider == null) {
// Error state: virtual view with no provider. Clear focus.
mAccessibilityFocusedHost = null;
mAccessibilityFocusedVirtualView = null;
focusedHost.clearAccessibilityFocusNoCallbacks(0);
return;
}
// We only care about change types that may affect the bounds of the
// focused virtual view.
final int changes = event.getContentChangeTypes();
if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) == 0
&& changes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) {
return;
}
final long eventSourceNodeId = event.getSourceNodeId();
final int changedViewId = AccessibilityNodeInfo.getAccessibilityViewId(eventSourceNodeId);
// Search up the tree for subtree containment.
boolean hostInSubtree = false;
View root = mAccessibilityFocusedHost;
while (root != null && !hostInSubtree) {
if (changedViewId == root.getAccessibilityViewId()) {
hostInSubtree = true;
} else {
final ViewParent parent = root.getParent();
if (parent instanceof View) {
root = (View) parent;
} else {
root = null;
}
}
}
// We care only about changes in subtrees containing the host view.
if (!hostInSubtree) {
return;
}
final long focusedSourceNodeId = mAccessibilityFocusedVirtualView.getSourceNodeId();
int focusedChildId = AccessibilityNodeInfo.getVirtualDescendantId(focusedSourceNodeId);
if (focusedChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
// TODO: Should we clear the focused virtual view?
focusedChildId = AccessibilityNodeProvider.HOST_VIEW_ID;
}
// Refresh the node for the focused virtual view.
final Rect oldBounds = mTempRect;
mAccessibilityFocusedVirtualView.getBoundsInScreen(oldBounds);
mAccessibilityFocusedVirtualView = provider.createAccessibilityNodeInfo(focusedChildId);
if (mAccessibilityFocusedVirtualView == null) {
// Error state: The node no longer exists. Clear focus.
mAccessibilityFocusedHost = null;
focusedHost.clearAccessibilityFocusNoCallbacks(0);
// This will probably fail, but try to keep the provider's internal
// state consistent by clearing focus.
provider.performAction(focusedChildId,
AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), null);
invalidateRectOnScreen(oldBounds);
} else {
// The node was refreshed, invalidate bounds if necessary.
final Rect newBounds = mAccessibilityFocusedVirtualView.getBoundsInScreen();
if (!oldBounds.equals(newBounds)) {
oldBounds.union(newBounds);
invalidateRectOnScreen(oldBounds);
}
}
}
@Override
public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
postSendWindowContentChangedCallback(source, changeType);
}
@Override
public boolean canResolveLayoutDirection() {
return true;
}
@Override
public boolean isLayoutDirectionResolved() {
return true;
}
@Override
public int getLayoutDirection() {
return View.LAYOUT_DIRECTION_RESOLVED_DEFAULT;
}
@Override
public boolean canResolveTextDirection() {
return true;
}
@Override
public boolean isTextDirectionResolved() {
return true;
}
@Override
public int getTextDirection() {
return View.TEXT_DIRECTION_RESOLVED_DEFAULT;
}
@Override
public boolean canResolveTextAlignment() {
return true;
}
@Override
public boolean isTextAlignmentResolved() {
return true;
}
@Override
public int getTextAlignment() {
return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
private View getCommonPredecessor(View first, View second) {
if (mTempHashSet == null) {
mTempHashSet = new HashSet<View>();
}
HashSet<View> seen = mTempHashSet;
seen.clear();
View firstCurrent = first;
while (firstCurrent != null) {
seen.add(firstCurrent);
ViewParent firstCurrentParent = firstCurrent.mParent;
if (firstCurrentParent instanceof View) {
firstCurrent = (View) firstCurrentParent;
} else {
firstCurrent = null;
}
}
View secondCurrent = second;
while (secondCurrent != null) {
if (seen.contains(secondCurrent)) {
seen.clear();
return secondCurrent;
}
ViewParent secondCurrentParent = secondCurrent.mParent;
if (secondCurrentParent instanceof View) {
secondCurrent = (View) secondCurrentParent;
} else {
secondCurrent = null;
}
}
seen.clear();
return null;
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// ViewAncestor never intercepts touch event, so this can be a no-op
}
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
if (rectangle == null) {
return scrollToRectOrFocus(null, immediate);
}
rectangle.offset(child.getLeft() - child.getScrollX(),
child.getTop() - child.getScrollY());
final boolean scrolled = scrollToRectOrFocus(rectangle, immediate);
mTempRect.set(rectangle);
mTempRect.offset(0, -mCurScrollY);
mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
try {
mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect);
} catch (RemoteException re) {
/* ignore */
}
return scrolled;
}
@Override
public void childHasTransientStateChanged(View child, boolean hasTransientState) {
// Do nothing.
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return false;
}
@Override
public void onStopNestedScroll(View target) {
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
@Override
public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
return false;
}
/**
* Force the window to report its next draw.
* <p>
* This method is only supposed to be used to speed up the interaction from SystemUI and window
* manager when waiting for the first frame to be drawn when turning on the screen. DO NOT USE
* unless you fully understand this interaction.
* @hide
*/
public void setReportNextDraw() {
mReportNextDraw = true;
invalidate();
}
void changeCanvasOpacity(boolean opaque) {
Log.d(mTag, "changeCanvasOpacity: opaque=" + opaque);
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.setOpaque(opaque);
}
}
class TakenSurfaceHolder extends BaseSurfaceHolder {
@Override
public boolean onAllowLockCanvas() {
return mDrawingAllowed;
}
@Override
public void onRelayoutContainer() {
// Not currently interesting -- from changing between fixed and layout size.
}
@Override
public void setFormat(int format) {
((RootViewSurfaceTaker)mView).setSurfaceFormat(format);
}
@Override
public void setType(int type) {
((RootViewSurfaceTaker)mView).setSurfaceType(type);
}
@Override
public void onUpdateSurface() {
// We take care of format and type changes on our own.
throw new IllegalStateException("Shouldn't be here");
}
@Override
public boolean isCreating() {
return mIsCreating;
}
@Override
public void setFixedSize(int width, int height) {
throw new UnsupportedOperationException(
"Currently only support sizing from layout");
}
@Override
public void setKeepScreenOn(boolean screenOn) {
((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn);
}
}
static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
mWindowSession = viewAncestor.mWindowSession;
}
@Override
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
Configuration newConfig, Rect backDropFrame, boolean forceLayout,
boolean alwaysConsumeNavBar) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
visibleInsets, stableInsets, outsets, reportDraw, newConfig, backDropFrame,
forceLayout, alwaysConsumeNavBar);
}
}
@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 updatePointerIcon(float x, float y) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.updatePointerIcon(x, y);
}
}
@Override
public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
int localValue, int localChanges) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchSystemUiVisibilityChanged(seq, globalVisibility,
localValue, localChanges);
}
}
@Override
public void dispatchWindowShown() {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchWindowShown();
}
}
@Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchRequestKeyboardShortcuts(receiver, deviceId);
}
}
}
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
public CalledFromWrongThreadException(String msg) {
super(msg);
}
}
static HandlerActionQueue getRunQueue() {
HandlerActionQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new HandlerActionQueue();
sRunQueues.set(rq);
return rq;
}
/**
* Start a drag resizing which will inform all listeners that a window resize is taking place.
*/
private void startDragResizing(Rect initialBounds, boolean fullscreen, Rect systemInsets,
Rect stableInsets, int resizeMode) {
if (!mDragResizing) {
mDragResizing = true;
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds, fullscreen,
systemInsets, stableInsets, resizeMode);
}
mFullRedrawNeeded = true;
}
}
/**
* End a drag resize which will inform all listeners that a window resize has ended.
*/
private void endDragResizing() {
if (mDragResizing) {
mDragResizing = false;
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onWindowDragResizeEnd();
}
mFullRedrawNeeded = true;
}
}
private boolean updateContentDrawBounds() {
boolean updated = false;
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
updated |= mWindowCallbacks.get(i).onContentDrawn(
mWindowAttributes.surfaceInsets.left,
mWindowAttributes.surfaceInsets.top,
mWidth, mHeight);
}
return updated | (mDragResizing && mReportNextDraw);
}
private void requestDrawWindow() {
if (mReportNextDraw) {
mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size());
}
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw);
}
}
/**
* Tells this instance that its corresponding activity has just relaunched. In this case, we
* need to force a relayout of the window to make sure we get the correct bounds from window
* manager.
*/
public void reportActivityRelaunched() {
mActivityRelaunched = true;
}
/**
* Class for managing the accessibility interaction connection
* based on the global accessibility state.
*/
final class AccessibilityInteractionConnectionManager
implements AccessibilityStateChangeListener {
@Override
public void onAccessibilityStateChanged(boolean enabled) {
if (enabled) {
ensureConnection();
if (mAttachInfo.mHasWindowFocus) {
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
View focusedView = mView.findFocus();
if (focusedView != null && focusedView != mView) {
focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
}
} else {
ensureNoConnection();
mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget();
}
}
public void ensureConnection() {
final boolean registered =
mAttachInfo.mAccessibilityWindowId != 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);
}
}
}
final class HighContrastTextManager implements HighTextContrastChangeListener {
HighContrastTextManager() {
mAttachInfo.mHighContrastText = mAccessibilityManager.isHighTextContrastEnabled();
}
@Override
public void onHighTextContrastStateChanged(boolean enabled) {
mAttachInfo.mHighContrastText = enabled;
// Destroy Displaylists so they can be recreated with high contrast recordings
destroyHardwareResources();
// Schedule redraw, which will rerecord + redraw all text
invalidate();
}
}
/**
* This class is an interface this ViewAncestor provides to the
* AccessibilityManagerService to the latter can interact with
* the view hierarchy in this ViewAncestor.
*/
static final class AccessibilityInteractionConnection
extends IAccessibilityInteractionConnection.Stub {
private final WeakReference<ViewRootImpl> mViewRootImpl;
AccessibilityInteractionConnection(ViewRootImpl viewRootImpl) {
mViewRootImpl = new WeakReference<ViewRootImpl>(viewRootImpl);
}
@Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
interactiveRegion, interactionId, callback, flags, interrogatingPid,
interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfosResult(null, interactionId);
} catch (RemoteException re) {
/* best effort - ignore */
}
}
}
@Override
public void performAccessibilityAction(long accessibilityNodeId, int action,
Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.performAccessibilityActionClientThread(accessibilityNodeId, action, arguments,
interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
callback.setPerformAccessibilityActionResult(false, interactionId);
} catch (RemoteException re) {
/* best effort - ignore */
}
}
}
@Override
public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
String viewId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByViewIdClientThread(accessibilityNodeId,
viewId, interactiveRegion, interactionId, callback, flags,
interrogatingPid, interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
/* best effort - ignore */
}
}
}
@Override
public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
interactiveRegion, interactionId, callback, flags, interrogatingPid,
interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfosResult(null, interactionId);
} catch (RemoteException re) {
/* best effort - ignore */
}
}
}
@Override
public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findFocusClientThread(accessibilityNodeId, focusType, interactiveRegion,
interactionId, callback, flags, interrogatingPid, interrogatingTid,
spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
/* best effort - ignore */
}
}
}
@Override
public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.focusSearchClientThread(accessibilityNodeId, direction, interactiveRegion,
interactionId, callback, flags, interrogatingPid, interrogatingTid,
spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
/* best effort - ignore */
}
}
}
}
private class SendWindowContentChangedAccessibilityEvent implements Runnable {
private int mChangeTypes = 0;
public View mSource;
public long mLastEventTimeMillis;
@Override
public void run() {
// 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);
}
}
}
}