//处理焦点*@param focusedRect The starting point of the search.*@param direction Directiontolook.*@returnThe next focusable view, or nullif none exists.*/publicViewfindNextFocusFromRect(ViewGroup root,Rect focusedRect,int direction){
mFocusedRect.set(focusedRect);returnfindNextFocus(root,null, mFocusedRect, direction);}privateViewfindNextFocus(ViewGroup root,View focused,Rect focusedRect,int direction){//Log.d(TAG,Log.getStackTraceString(new Throwable()));if(JoyarHelper.DBG){Log.d(TAG,"findNextFocus root:"+ root +" focused:"+ focused);}View next =null;ViewGroup effectiveRoot =getEffectiveRoot(root, focused);ViewGroup rv =JoyarHelper.getInstance().FocusFinderFindNextFocus(this, root, focused, focusedRect, direction);if(rv !=null){
effectiveRoot = rv;}if(focused !=null){
next =findNextUserSpecifiedFocus(effectiveRoot, focused, direction);}if(next !=null){return next;}ArrayList<View> focusables = mTempList;try{
focusables.clear();
effectiveRoot.addFocusables(focusables, direction);if(!focusables.isEmpty()){if(JoyarHelper.DBG){Log.d(TAG,"######## findNextFocus focusables start size:"+ focusables.size()+" ########");for(View f : focusables){Log.d(TAG, f.toString());}Log.d(TAG,"######## findNextFocus focusables end ########");}
next =findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);if(JoyarHelper.DBG){Log.d("FocusFinder","find next1:"+ next +" focused:"+ focused);}if(next ==null&& focused !=null){
next =JoyarHelper.getInstance().FocusFinderFindSmallFocusView(focusables, focused);}if(JoyarHelper.DBG){Log.d("FocusFinder","find next2:"+ next);}}}finally{
focusables.clear();}return next;}booleanbeamBeats(int direction,Rect source,Rect rect1,Rect rect2){finalboolean rect1InSrcBeam =beamsOverlap(direction, source, rect1);finalboolean rect2InSrcBeam =beamsOverlap(direction, source, rect2);// if rect1 isn't exclusively in the src beam, it doesn't winif(rect2InSrcBeam ||!rect1InSrcBeam){returnfalse;}if(JoyarHelper.getInstance().FocusFinderBeamBeats(this, direction, source, rect1, rect2)){returntrue;}// we know rect1 is in the beam, and rect2 is not// if rect1 is to the direction of, and rect2 is not, rect1 wins.// for example, for direction left, if rect1 is to the left of the source// and rect2 is below, then we always prefer the in beam rect1, since rect2// could be reached by going down.if(!isToDirectionOf(direction, source, rect2)){returntrue;}/**
* Is destRect a candidate for the next focus given the direction? This
* checks whether the dest is at least partially to the direction of (e.g left of)
* from source.
*
* Includes an edge case for an empty rect (which is used in some cases when
* searching from a point on the screen).
*/booleanisCandidate(Rect srcRect,Rect destRect,int direction){if(JoyarHelper.getInstance().FocusFinderIsCaredCandidate(this, srcRect, destRect, direction)){returnJoyarHelper.getInstance().FocusFinderIsCandidate(this, srcRect, destRect, direction);}switch(direction){caseView.FOCUS_LEFT:return(srcRect.right > destRect.right || srcRect.left >= destRect.right)&& srcRect.left > destRect.left;caseView.FOCUS_RIGHT:return(srcRect.left < destRect.left || srcRect.right <= destRect.left)&& srcRect.right < destRect.right;caseView.FOCUS_UP:if(JoyarHelper.DBG){Log.d(TAG,"isCandidate bottom:"+(srcRect.bottom > destRect.bottom)+"top and bottom:"+(srcRect.top >= destRect.bottom)+" top and top:"+(srcRect.top > destRect.top));}return(srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)&& srcRect.top > destRect.top;caseView.FOCUS_DOWN:return(srcRect.top < destRect.top || srcRect.bottom <= destRect.top)&& srcRect.bottom < destRect.bottom;}thrownewIllegalArgumentException("direction must be one of "+"{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}
sThrowOnInvalidFloatProperties = targetSdkVersion >=Build.VERSION_CODES.P;
sCanFocusZeroSized = targetSdkVersion <Build.VERSION_CODES.P;
sAlwaysAssignFocus = targetSdkVersion <Build.VERSION_CODES.P;
sAcceptZeroSizeDragShadow = targetSdkVersion <Build.VERSION_CODES.P;
sCompatibilityDone =true;}JoyarHelper.getInstance().ViewAddPerformClickViews(this);}publicvoidsetOnClickListener(@NullableOnClickListener l){boolean handle =JoyarHelper.getInstance().ViewSetOnClickListener(this, l);if(!handle &&!isClickable()){setClickable(true);}getListenerInfo().mOnClickListener = l;}publicbooleanperformClick(){if(JoyarHelper.getInstance().ViewPerformClick(this)){returnfalse;}// We still need to call this method to handle the cases where performClick() was called// externally, instead of through performClickInternal()notifyAutofillManagerOnClick();finalboolean result;finalListenerInfo li = mListenerInfo;if(li !=null&& li.mOnClickListener !=null){playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);JoyarHelper.getInstance().ViewOnClick(this);
result =true;}elseif(JoyarHelper.getInstance().ViewOnPerformClick(this)){
result =true;}else{
result =false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);notifyEnterOrExitForAutoFillIfNeeded(true);return result;/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/publicvoidsetOnTouchListener(OnTouchListener l){JoyarHelper.getInstance().ViewSetOnTouchListener(this, l);getListenerInfo().mOnTouchListener = l;}voidhandleFocusGainInternal(@FocusRealDirectionint direction,Rect previouslyFocusedRect){if(DBG){System.out.println(this+" requestFocus()");}if((mPrivateFlags &PFLAG_FOCUSED)==0){
mPrivateFlags |=PFLAG_FOCUSED;View oldFocus =(mAttachInfo !=null)?getRootView().findFocus():null;if(mParent !=null){JoyarHelper.getInstance().ViewRequestChildFocusPre(this, mParent);if(JoyarHelper.getInstance().isInNf()){JoyarHelper.getInstance().ViewGroupRequestChildFocus(mParent,this,this);}else{
mParent.requestChildFocus(this,this);}JoyarHelper.getInstance().ViewRequestChildFocusSuper(this, mParent,this,this);updateFocusedInCluster(oldFocus, direction);}if(mAttachInfo !=null){
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus,this);}onFocusChanged(true, direction, previouslyFocusedRect);refreshDrawableState();}invalidate(true);ListenerInfo li = mListenerInfo;if(li !=null&& li.mOnFocusChangeListener !=null){
li.mOnFocusChangeListener.onFocusChange(this, gainFocus);}if(mAttachInfo !=null){
mAttachInfo.mKeyDispatchState.reset(this);}JoyarHelper.getInstance().ViewOnFocusChanged(this, gainFocus, direction);notifyEnterOrExitForAutoFillIfNeeded(gainFocus);}@RemotableViewMethodpublicvoidsetVisibility(@Visibilityint visibility){JoyarHelper.getInstance().ViewSetVisibility(this, visibility);setFlags(visibility,VISIBILITY_MASK);}*@param focusable Iftrue,this view can receive the focus.**@see #setFocusableInTouchMode(boolean)*@see #setFocusable(int)*@attr ref android.R.styleable#View_focusable*/publicvoidsetFocusable(boolean focusable){if(JoyarHelper.getInstance().ViewSetFocusable(this, focusable)){return;}setFocusable(focusable ?FOCUSABLE:NOT_FOCUSABLE);}publicvoidsetFocusableInTouchMode(boolean focusableInTouchMode){if(JoyarHelper.getInstance().ViewSetFocusableInTouchMode(this, focusableInTouchMode)){return;}// Focusable in touch mode should always be set before the focusable flag// otherwise, setting the focusable flag will trigger a focusableViewAvailable()// which, in touch mode, will not successfully request focus on this view// because the focusable in touch mode flag is not setsetFlags(focusableInTouchMode ?FOCUSABLE_IN_TOUCH_MODE:0,FOCUSABLE_IN_TOUCH_MODE);// Clear FOCUSABLE_AUTO if set.if(focusableInTouchMode){// Clears FOCUSABLE_AUTO if set.setFlags(FOCUSABLE,FOCUSABLE_MASK);publicViewfocusSearch(@FocusRealDirectionint direction){if(mParent !=null){//wangny todoJoyarHelper.getInstance().ViewFocusSearch(this, direction);return mParent.focusSearch(this, direction);}else{returnnull;}}privatebooleanrequestFocusNoSearch(int direction,Rect previouslyFocusedRect){// need to be focusableif(JoyarHelper.DBG){Log.d(VIEW_LOG_TAG,"requestFocusNoSearch view:"+this+" int parent:"+ mParent);}if(!canTakeFocus()){returnfalse;}// need to be focusable in touch mode if in touch modeif(isInTouchMode()&&(FOCUSABLE_IN_TOUCH_MODE!=(mViewFlags &FOCUSABLE_IN_TOUCH_MODE))){returnfalse;}// need to not have any parents blocking usif(hasAncestorThatBlocksDescendantFocus()){returnfalse;}if(JoyarHelper.getInstance().isInNf()&&JoyarHelper.getInstance().ViewIsFullscreenView(this)){if(JoyarHelper.DBG){Log.d(VIEW_LOG_TAG,"cause in nf and fullscreen view, skip it!");}returnfalse;}if(!isLayoutValid()){
mPrivateFlags |=PFLAG_WANTS_FOCUS;}else{clearParentsWantFocus();}if(JoyarHelper.DBG){Log.d(VIEW_LOG_TAG,"finally gain focus view:"+this+" parent:"+ mParent);}handleFocusGainInternal(direction, previouslyFocusedRect);returntrue;}
mPrivateFlags |=PFLAG_DRAWN|PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &=~PFLAG_DIRTY_MASK;// Fast path for layouts with no backgroundsif((mPrivateFlags &PFLAG_SKIP_DRAW)==PFLAG_SKIP_DRAW){dispatchDraw(canvas);drawAutofilledHighlight(canvas);if(mOverlay !=null&&!mOverlay.isEmpty()){
mOverlay.getOverlayView().draw(canvas);}Rect tmpRect =newRect(mScrollX, mScrollY, mScrollX + mRight - mLeft, mScrollY + mBottom - mTop);JoyarHelper.getInstance().ViewOnDraw(this, canvas, tmpRect);if(debugDraw()){debugDrawFocus(canvas);}}else{draw(canvas);}}}finally{
renderNode.end(canvas);setDisplayListProperties(renderNode);if(mOverlay !=null&&!mOverlay.isEmpty()){
mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas);Rect tmpRect =newRect(mScrollX, mScrollY, mScrollX + mRight - mLeft, mScrollY + mBottom - mTop);JoyarHelper.getInstance().ViewOnDraw(this, canvas, tmpRect);if(debugDraw()){debugDrawFocus(canvas);}// we're done...return;}drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif(mOverlay !=null&&!mOverlay.isEmpty()){
mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);Rect tmpRect =newRect(mScrollX, mScrollY, mScrollX + mRight - mLeft, mScrollY + mBottom - mTop);JoyarHelper.getInstance().ViewOnDraw(this, canvas, tmpRect);if(debugDraw()){debugDrawFocus(canvas);}}
ViewGroup.java
}if(mParent !=null){JoyarHelper.getInstance().ViewRequestChildFocusPre(this,getParent());if(JoyarHelper.getInstance().isInNf()){JoyarHelper.getInstance().ViewGroupRequestChildFocus(mParent,this, focused);}else{
mParent.requestChildFocus(this, focused);}JoyarHelper.getInstance().ViewRequestChildFocusSuper(this,getParent(),this, focused);}}voidsetDefaultFocus(View child){// Stop at any higher view which is explicitly focused-by-defaultif(mDefaultFocus !=null&& mDefaultFocus.isFocusedByDefault()){return;}
mDefaultFocus = child;if(mParent instanceofViewGroup){((ViewGroup) mParent).setDefaultFocus(this);}}/**
* Clears the default-focus chain from {@param child} up to the first parent which has another
* default-focusable branch below it or until there is no default-focus chain.
*
* @param child
*/voidclearDefaultFocus(View child){// Stop at any higher view which is explicitly focused-by-defaultif(mDefaultFocus != child && mDefaultFocus !=null&& mDefaultFocus.isFocusedByDefault()){return;}
mDefaultFocus =null;// Search child siblings for default focusables.for(int i =0; i < mChildrenCount;++i){View sibling = mChildren[i];if(sibling.isFocusedByDefault()){
mDefaultFocus = sibling;return;}elseif(mDefaultFocus ==null&& sibling.hasDefaultFocus()){
mDefaultFocus = sibling;}}if(mParent instanceofViewGroup){((ViewGroup) mParent).clearDefaultFocus(this);}}@OverridebooleanhasDefaultFocus(){return mDefaultFocus !=null||super.hasDefaultFocus();}/**
* Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
* it.
* <br>
* This is intended to be run on {@code child}'s immediate parent. This is necessary because
* the chain is sometimes cleared after {@code child} has been detached.
*/voidclearFocusedInCluster(View child){if(mFocusedInCluster != child){return;}clearFocusedInCluster();}/**
* Removes the focusedInCluster chain from this up to the cluster containing it.
*/voidclearFocusedInCluster(){View top =findKeyboardNavigationCluster();ViewParent parent =this;do{((ViewGroup) parent).mFocusedInCluster =null;if(parent == top){break;}
parent = parent.getParent();}while(parent instanceofViewGroup);}@OverridepublicvoidfocusableViewAvailable(View v){if(mParent !=null// shortcut: don't report a new focusable view if we block our descendants from// getting focus or if we're not visible&&(getDescendantFocusability()!=FOCUS_BLOCK_DESCENDANTS)&&((mViewFlags &VISIBILITY_MASK)==VISIBLE)&&(isFocusableInTouchMode()||!shouldBlockFocusForTouchscreen())// shortcut: don't report a new focusable view if we already are focused// (and we don't prefer our descendants)//// note: knowing that mFocused is non-null is not a good enough reason// to break the traversal since in that case we'd actually have to find// the focused view and make sure it wasn't FOCUS_AFTER_DESCENDANTS and// an ancestor of v; this will get checked for at ViewAncestor&&!(isFocused()&&getDescendantFocusability()!=FOCUS_AFTER_DESCENDANTS)){
mParent.focusableViewAvailable(v);}}@OverridepublicbooleanshowContextMenuForChild(View originalView){if(isShowingContextMenuWithCoords()){// We're being called for compatibility. Return false and let the version// with coordinates recurse up.returnfalse;}return mParent !=null&& mParent.showContextMenuForChild(originalView);}/**
* @hide used internally for compatibility with existing app code only
*/publicfinalbooleanisShowingContextMenuWithCoords(){return(mGroupFlags &FLAG_SHOW_CONTEXT_MENU_WITH_COORDS)!=0;}@OverridepublicbooleanshowContextMenuForChild(View originalView,float x,float y){try{
mGroupFlags |=FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;if(showContextMenuForChild(originalView)){returntrue;}}finally{
mGroupFlags &=~FLAG_SHOW_CONTEXT_MENU_WITH_COORDS;}return mParent !=null&& mParent.showContextMenuForChild(originalView, x, y);}@OverridepublicActionModestartActionModeForChild(View originalView,ActionMode.Callback callback){if((mGroupFlags &FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED)==0){// This is the original call.try{
mGroupFlags |=FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;returnstartActionModeForChild(originalView, callback,ActionMode.TYPE_PRIMARY);}finally{
mGroupFlags &=~FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;}}else{// We are being called from the new method with type.returnSENTINEL_ACTION_MODE;}}@OverridepublicActionModestartActionModeForChild(View originalView,ActionMode.Callback callback,int type){if((mGroupFlags &FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED)==0&& type ==ActionMode.TYPE_PRIMARY){ActionMode mode;try{
mGroupFlags |=FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
mode =startActionModeForChild(originalView, callback);}finally{
mGroupFlags &=~FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;}if(mode !=SENTINEL_ACTION_MODE){return mode;}}if(mParent !=null){try{return mParent.startActionModeForChild(originalView, callback, type);}catch(AbstractMethodError ame){// Custom view parents might not implement this method.return mParent.startActionModeForChild(originalView, callback);}}returnnull;}/**
* @hide
*/@OverridepublicbooleandispatchActivityResult(String who,int requestCode,int resultCode,Intent data){if(super.dispatchActivityResult(who, requestCode, resultCode, data)){returntrue;}int childCount =getChildCount();for(int i =0; i < childCount; i++){View child =getChildAt(i);if(child.dispatchActivityResult(who, requestCode, resultCode, data)){returntrue;}}returnfalse;}/**
* Find the nearest view in the specified direction that wants to take
* focus.
*
* @param focused The view that currently has focus
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
* FOCUS_RIGHT, or 0 for not applicable.
*/@OverridepublicViewfocusSearch(View focused,int direction){//wangny todoJoyarHelper.getInstance().ViewGroupFocusSearch(this, focused, direction);if(isRootNamespace()){// root namespace means we should consider ourselves the top of the// tree for focus searching; otherwise we could be focus searching// into other tabs. see LocalActivityManager and TabHost for more info.returnFocusFinder.getInstance().findNextFocus(this, focused, direction);}elseif(mParent !=null){return mParent.focusSearch(focused, direction);}returnnull;}@OverridepublicbooleanrequestChildRectangleOnScreen(View child,Rect rectangle,boolean immediate){returnfalse;}@OverridepublicbooleanrequestSendAccessibilityEvent(View child,AccessibilityEvent event){ViewParent parent = mParent;if(parent ==null){returnfalse;}finalboolean propagate =onRequestSendAccessibilityEvent(child, event);if(!propagate){returnfalse;}return parent.requestSendAccessibilityEvent(this, event);}/**
* Called when a child has requested sending an {@link AccessibilityEvent} and
* gives an opportunity to its parent to augment the event.
* <p>
* If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
* {@link android.view.View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
* {@link android.view.View.AccessibilityDelegate#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)}
* is responsible for handling this call.
* </p>
*
* @param child The child which requests sending the event.
* @param event The event to be sent.
* @return True if the event should be sent.
*
* @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
*/publicbooleanonRequestSendAccessibilityEvent(View child,AccessibilityEvent event){if(mAccessibilityDelegate !=null){return mAccessibilityDelegate.onRequestSendAccessibilityEvent(this, child, event);}else{returnonRequestSendAccessibilityEventInternal(child, event);}}/**
* @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent)
*
* Note: Called from the default {@link View.AccessibilityDelegate}.
*
* @hide
*/publicbooleanonRequestSendAccessibilityEventInternal(View child,AccessibilityEvent event){returntrue;}/**
* Called when a child view has changed whether or not it is tracking transient state.
*/@OverridepublicvoidchildHasTransientStateChanged(View child,boolean childHasTransientState){finalboolean oldHasTransientState =hasTransientState();if(childHasTransientState){
mChildCountWithTransientState++;}else{
mChildCountWithTransientState--;}finalboolean newHasTransientState =hasTransientState();if(mParent !=null&& oldHasTransientState != newHasTransientState){try{
mParent.childHasTransientStateChanged(this, newHasTransientState);}catch(AbstractMethodError e){Log.e(TAG, mParent.getClass().getSimpleName()+" does not fully implement ViewParent", e);}}}@OverridepublicbooleanhasTransientState(){return mChildCountWithTransientState >0||super.hasTransientState();}@OverridepublicbooleandispatchUnhandledMove(View focused,int direction){return mFocused !=null&&
mFocused.dispatchUnhandledMove(focused, direction);}@OverridepublicvoidclearChildFocus(View child){if(DBG){System.out.println(this+" clearChildFocus()");}
mFocused =null;if(mParent !=null){
mParent.clearChildFocus(this);}}@OverridepublicvoidclearFocus(){if(DBG){System.out.println(this+" clearFocus()");}if(mFocused ==null){super.clearFocus();}else{View focused = mFocused;
mFocused =null;
focused.clearFocus();}}@OverridevoidunFocus(View focused){if(DBG){System.out.println(this+" unFocus()");}if(mFocused ==null){super.unFocus(focused);}else{
mFocused.unFocus(focused);
mFocused =null;}}/**
* Returns the focused child of this view, if any. The child may have focus
* or contain focus.
*
* @return the focused child or null.
*/publicViewgetFocusedChild(){return mFocused;}ViewgetDeepestFocusedChild(){View v =this;while(v !=null){if(v.isFocused()){return v;}
v = v instanceofViewGroup?((ViewGroup) v).getFocusedChild():null;}returnnull;}/**
* Returns true if this view has or contains focus
*
* @return true if this view has or contains focus
*/@OverridepublicbooleanhasFocus(){return(mPrivateFlags &PFLAG_FOCUSED)!=0|| mFocused !=null;}/*
* (non-Javadoc)
*
* @see android.view.View#findFocus()
*/@OverridepublicViewfindFocus(){if(DBG){System.out.println("Find focus in "+this+": flags="+isFocused()+", child="+ mFocused);}if(isFocused()){returnthis;}if(mFocused !=null){return mFocused.findFocus();}returnnull;}@OverridebooleanhasFocusable(boolean allowAutoFocus,boolean dispatchExplicit){// This should probably be super.hasFocusable, but that would change// behavior. Historically, we have not checked the ancestor views for// shouldBlockFocusForTouchscreen() in ViewGroup.hasFocusable.// Invisible and gone views are never focusable.if((mViewFlags &VISIBILITY_MASK)!=VISIBLE){returnfalse;}// Only use effective focusable value when allowed.if((allowAutoFocus ||getFocusable()!=FOCUSABLE_AUTO)&&isFocusable()){returntrue;}// Determine whether we have a focused descendant.finalint descendantFocusability =getDescendantFocusability();if(descendantFocusability !=FOCUS_BLOCK_DESCENDANTS){returnhasFocusableChild(dispatchExplicit);}returnfalse;}booleanhasFocusableChild(boolean dispatchExplicit){// Determine whether we have a focusable descendant.finalint count = mChildrenCount;finalView[] children = mChildren;for(int i =0; i < count; i++){finalView child = children[i];// In case the subclass has overridden has[Explicit]Focusable, dispatch// to the expected one for each child even though we share logic here.if((dispatchExplicit && child.hasExplicitFocusable())||(!dispatchExplicit && child.hasFocusable())){returntrue;}}returnfalse;}@OverridepublicvoidaddFocusables(ArrayList<View> views,int direction,int focusableMode){finalint focusableCount = views.size();finalint descendantFocusability =getDescendantFocusability();finalboolean blockFocusForTouchscreen =shouldBlockFocusForTouchscreen();finalboolean focusSelf =(isFocusableInTouchMode()||!blockFocusForTouchscreen);if(descendantFocusability ==FOCUS_BLOCK_DESCENDANTS){if(focusSelf){super.addFocusables(views, direction, focusableMode);}return;}if(blockFocusForTouchscreen){
focusableMode |=FOCUSABLES_TOUCH_MODE;}if((descendantFocusability ==FOCUS_BEFORE_DESCENDANTS)&& focusSelf){super.addFocusables(views, direction, focusableMode);}int count =0;finalView[] children =newView[mChildrenCount];for(int i =0; i < mChildrenCount;++i){View child = mChildren[i];if((child.mViewFlags &VISIBILITY_MASK)==VISIBLE){
children[count++]= child;}}FocusFinder.sort(children,0, count,this,isLayoutRtl());for(int i =0; i < count;++i){if(!JoyarHelper.getInstance().ViewGroupAddFocusables(this, children[i], views, direction, focusableMode)){
children[i].addFocusables(views, direction, focusableMode);}}// When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if// there aren't any focusable descendants. this is// to avoid the focus search finding layouts when a more precise search// among the focusable children would be more interesting.if((descendantFocusability ==FOCUS_AFTER_DESCENDANTS)&& focusSelf
&& focusableCount == views.size()){super.addFocusables(views, direction, focusableMode);}
ViewRootImpl.java
direction =View.FOCUS_LEFT;}break;caseKeyEvent.KEYCODE_DPAD_RIGHT:if(event.hasNoModifiers()){
direction =View.FOCUS_RIGHT;}break;caseKeyEvent.KEYCODE_DPAD_UP:if(event.hasNoModifiers()){
direction =JoyarHelper.getInstance().ViewRootImplPerformFocusNavigation(event,View.FOCUS_UP);//direction = View.FOCUS_UP;}break;caseKeyEvent.KEYCODE_DPAD_DOWN:if(event.hasNoModifiers()){
direction =JoyarHelper.getInstance().ViewRootImplPerformFocusNavigation(event,View.FOCUS_DOWN);//direction = View.FOCUS_DOWN;}break;caseKeyEvent.KEYCODE_TAB:if(event.hasNoModifiers()){
direction =View.FOCUS_FORWARD;}elseif(event.hasModifiers(KeyEvent.META_SHIFT_ON)){
direction =View.FOCUS_BACKWARD;}break;}JoyarHelper.getInstance().ViewRootImplOnPerformFocusNavigation(event, direction);if(direction !=0){View focused = mView.findFocus();if(JoyarHelper.DBG){Log.d(TAG,"mView findFocus:"+ focused);}if(focused !=null){View v = focused.focusSearch(direction);if(JoyarHelper.DBG){Log.d(TAG,"focused focusSearch:"+ v);}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 instanceofViewGroup){((ViewGroup) mView).offsetDescendantRectToMyCoords(
focused, mTempRect);((ViewGroup) mView).offsetRectIntoDescendantCoords(
v, mTempRect);}if(v.requestFocus(direction, mTempRect)){playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));returntrue;}}// Give the focused view a last chance to handle the dpad key.if(mView.dispatchUnhandledMove(focused, direction)){returntrue;}}else{if(mView.restoreDefaultFocus()){returntrue;}}}returnfalse;}privatebooleanperformKeyboardGroupNavigation(int direction){finalView focused = mView.findFocus();if(focused ==null&& mView.restoreDefaultFocus()){returntrue;}View cluster = focused ==null?keyboardNavigationClusterSearch(null, direction): focused.keyboardNavigationClusterSearch(null, direction);// Since requestFocus only takes "real" focus directions (and therefore also// restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN.int realDirection = direction;if(direction ==View.FOCUS_FORWARD|| direction ==View.FOCUS_BACKWARD){
realDirection =View.FOCUS_DOWN;}if(cluster !=null&& cluster.isRootNamespace()){// the default cluster. Try to find a non-clustered view to focus.if(cluster.restoreFocusNotInCluster()){playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));returntrue;}// otherwise skip to next actual cluster
cluster =keyboardNavigationClusterSearch(null, direction);}if(cluster !=null&& cluster.restoreFocusInCluster(realDirection)){playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));returntrue;}returnfalse;}privateintprocessKeyEvent(QueuedInputEvent q){finalKeyEvent event =(KeyEvent)q.mEvent;if(mUnhandledKeyManager.preViewDispatch(event)){returnFINISH_HANDLED;}// Deliver the key to the view hierarchy.if(mView.dispatchKeyEvent(event)&&!JoyarHelper.getInstance().ViewRootImplProcessKeyEvent(mView, event)){returnFINISH_HANDLED;}if(shouldDropInputEvent(q)){returnFINISH_NOT_HANDLED;}// This dispatch is for windows that don't have a Window.Callback. Otherwise,// the Window.Callback usually will have already called this (see// DecorView.superDispatchKeyEvent) leaving this call a no-op.if(mUnhandledKeyManager.dispatch(mView, event)){returnFINISH_HANDLED;}int groupNavigationDirection =0;if(event.getAction()==KeyEvent.ACTION_DOWN&& event.getKeyCode()==KeyEvent.KEYCODE_TAB){if(KeyEvent.metaStateHasModifiers(event.getMetaState(),KeyEvent.META_META_ON)){
groupNavigationDirection =View.FOCUS_FORWARD;}elseif(KeyEvent.metaStateHasModifiers(event.getMetaState(),KeyEvent.META_META_ON|KeyEvent.META_SHIFT_ON)){
groupNavigationDirection =View.FOCUS_BACKWARD;}}// If a modifier is held, try to interpret the key as a shortcut.if(event.getAction()==KeyEvent.ACTION_DOWN&&!KeyEvent.metaStateHasNoModifiers(event.getMetaState())&& event.getRepeatCount()==0&&!KeyEvent.isModifierKey(event.getKeyCode())&& groupNavigationDirection ==0){if(mView.dispatchKeyShortcutEvent(event)){returnFINISH_HANDLED;}if(shouldDropInputEvent(q)){returnFINISH_NOT_HANDLED;}}// Apply the fallback event policy.if(mFallbackEventHandler.dispatchKeyEvent(event)){returnFINISH_HANDLED;}if(shouldDropInputEvent(q)){returnFINISH_NOT_HANDLED;}// Handle automatic focus changes.if(event.getAction()==KeyEvent.ACTION_DOWN){if(groupNavigationDirection !=0){if(performKeyboardGroupNavigation(groupNavigationDirection)){returnFINISH_HANDLED;}}else{if(performFocusNavigation(event)){returnFINISH_HANDLED;}}}returnFORWARD;}privateintprocessPointerEvent(QueuedInputEvent q){finalMotionEvent event =(MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested =false;
mAttachInfo.mHandlingPointerEvent =true;boolean handled = mView.dispatchPointerEvent(event);maybeUpdatePointerIcon(event);maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent =false;if(mAttachInfo.mUnbufferedDispatchRequested &&!mUnbufferedInputDispatch){
mUnbufferedInputDispatch =true;if(mConsumeBatchedInputScheduled){scheduleConsumeBatchedInputImmediately();}}return handled ?FINISH_HANDLED:FORWARD;}privatevoidmaybeUpdatePointerIcon(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;}}}}privateintprocessTrackballEvent(QueuedInputEvent q){finalMotionEvent event =(MotionEvent)q.mEvent;if(event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)){if(!hasPointerCapture()|| mView.dispatchCapturedPointerEvent(event)){returnFINISH_HANDLED;}}if(mView.dispatchTrackballEvent(event)){returnFINISH_HANDLED;}returnFORWARD;}privateintprocessGenericMotionEvent(QueuedInputEvent q){finalMotionEvent event =(MotionEvent)q.mEvent;// Deliver the event to the view.if(mView.dispatchGenericMotionEvent(event)){returnFINISH_HANDLED;}returnFORWARD;}}privatevoidresetPointerIcon(MotionEvent event){
mPointerIconType =PointerIcon.TYPE_NOT_SPECIFIED;updatePointerIcon(event);}privatebooleanupdatePointerIcon(MotionEvent event){finalint pointerIndex =0;finalfloat x = event.getX(pointerIndex);finalfloat y = event.getY(pointerIndex);if(mView ==null){// E.g. click outside a popup to dismiss itSlog.d(mTag,"updatePointerIcon called after view was removed");returnfalse;}if(x <0|| x >= mView.getWidth()|| y <0|| y >= mView.getHeight()){// E.g. when moving window divider with mouseSlog.d(mTag,"updatePointerIcon called with position out of bounds");returnfalse;}finalPointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);finalint pointerType =(pointerIcon !=null)?
pointerIcon.getType():PointerIcon.TYPE_DEFAULT;if(mPointerIconType != pointerType){
mPointerIconType = pointerType;
mCustomPointerIcon =null;if(mPointerIconType !=PointerIcon.TYPE_CUSTOM){InputManager.getInstance().setPointerIconType(pointerType);returntrue;}}if(mPointerIconType ==PointerIcon.TYPE_CUSTOM&&!pointerIcon.equals(mCustomPointerIcon)){
mCustomPointerIcon = pointerIcon;InputManager.getInstance().setCustomPointerIcon(mCustomPointerIcon);}returntrue;}privatevoidmaybeUpdateTooltip(MotionEvent event){if(event.getPointerCount()!=1){return;}finalint action = event.getActionMasked();if(action !=MotionEvent.ACTION_HOVER_ENTER&& action !=MotionEvent.ACTION_HOVER_MOVE&& action !=MotionEvent.ACTION_HOVER_EXIT){return;}AccessibilityManager manager =AccessibilityManager.getInstance(mContext);if(manager.isEnabled()&& manager.isTouchExplorationEnabled()){return;}if(mView ==null){Slog.d(mTag,"maybeUpdateTooltip called after view was removed");return;}
mView.dispatchTooltipHoverEvent(event);}/**
* Performs synthesis of new input events from unhandled input events.
*/finalclassSyntheticInputStageextendsInputStage{privatefinalSyntheticTrackballHandler mTrackball =newSyntheticTrackballHandler();privatefinalSyntheticJoystickHandler mJoystick =newSyntheticJoystickHandler();privatefinalSyntheticTouchNavigationHandler mTouchNavigation =newSyntheticTouchNavigationHandler();privatefinalSyntheticKeyboardHandler mKeyboard =newSyntheticKeyboardHandler();publicSyntheticInputStage(){super(null);}@OverrideprotectedintonProcess(QueuedInputEvent q){
q.mFlags |=QueuedInputEvent.FLAG_RESYNTHESIZED;if(q.mEvent instanceofMotionEvent){finalMotionEvent event =(MotionEvent)q.mEvent;finalint source = event.getSource();if((source &InputDevice.SOURCE_CLASS_TRACKBALL)!=0){
mTrackball.process(event);returnFINISH_HANDLED;}elseif((source &InputDevice.SOURCE_CLASS_JOYSTICK)!=0){
mJoystick.process(event);returnFINISH_HANDLED;}elseif((source &InputDevice.SOURCE_TOUCH_NAVIGATION)==InputDevice.SOURCE_TOUCH_NAVIGATION){
mTouchNavigation.process(event);returnFINISH_HANDLED;}}elseif((q.mFlags &QueuedInputEvent.FLAG_UNHANDLED)!=0){
mKeyboard.process((KeyEvent)q.mEvent);returnFINISH_HANDLED;}returnFORWARD;}@OverrideprotectedvoidonDeliverToNext(QueuedInputEvent q){if((q.mFlags &QueuedInputEvent.FLAG_RESYNTHESIZED)==0){// Cancel related synthetic events if any prior stage has handled the event.if(q.mEvent instanceofMotionEvent){finalMotionEvent event =(MotionEvent)q.mEvent;finalint source = event.getSource();if((source &InputDevice.SOURCE_CLASS_TRACKBALL)!=0){
mTrackball.cancel();}elseif((source &InputDevice.SOURCE_CLASS_JOYSTICK)!=0){
mJoystick.cancel();}elseif((source &InputDevice.SOURCE_TOUCH_NAVIGATION)==InputDevice.SOURCE_TOUCH_NAVIGATION){
mTouchNavigation.cancel(event);}}}super.onDeliverToNext(q);}@OverrideprotectedvoidonWindowFocusChanged(boolean hasWindowFocus){if(!hasWindowFocus){
mJoystick.cancel();}}@OverrideprotectedvoidonDetachedFromWindow(){
mJoystick.cancel();}}/**
* Creates dpad events from unhandled trackball movements.
*/finalclassSyntheticTrackballHandler{privatefinalTrackballAxis mX =newTrackballAxis();privatefinalTrackballAxis mY =newTrackballAxis();privatelong mLastTime;publicvoidprocess(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;}finalint action = event.getAction();finalint metaState = event.getMetaState();switch(action){caseMotionEvent.ACTION_DOWN:
mX.reset(2);
mY.reset(2);enqueueInputEvent(newKeyEvent(curTime, curTime,KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_DPAD_CENTER,0, metaState,KeyCharacterMap.VIRTUAL_KEYBOARD,0,KeyEvent.FLAG_FALLBACK,InputDevice.SOURCE_KEYBOARD));break;caseMotionEvent.ACTION_UP:
mX.reset(2);
mY.reset(2);enqueueInputEvent(newKeyEvent(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());finalfloat xOff = mX.collect(event.getX(), event.getEventTime(),"X");finalfloat 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);}}elseif(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(newKeyEvent(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(newKeyEvent(curTime, curTime,KeyEvent.ACTION_DOWN, keycode,0, metaState,KeyCharacterMap.VIRTUAL_KEYBOARD,0,KeyEvent.FLAG_FALLBACK,InputDevice.SOURCE_KEYBOARD));enqueueInputEvent(newKeyEvent(curTime, curTime,KeyEvent.ACTION_UP, keycode,0, metaState,KeyCharacterMap.VIRTUAL_KEYBOARD,0,KeyEvent.FLAG_FALLBACK,InputDevice.SOURCE_KEYBOARD));}
mLastTime = curTime;}}publicvoidcancel(){
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.
*/staticfinalclassTrackballAxis{/**
* The maximum amount of acceleration we will apply.
*/staticfinalfloatMAX_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.
*/staticfinallongFAST_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.
*/staticfinalfloatACCEL_MOVE_SCALING_FACTOR=(1.0f/40);staticfinalfloatFIRST_MOVEMENT_THRESHOLD=0.5f;staticfinalfloatSECOND_CUMULATIVE_MOVEMENT_THRESHOLD=2.0f;staticfinalfloatSUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD=1.0f;float position;float acceleration =1;long lastMoveTime =0;int step;int dir;int nonAccelMovement;voidreset(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.
*/floatcollect(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;}elseif(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;returnMath.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.
*/intgenerate(){int movement =0;
nonAccelMovement =0;do{finalint 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.case0: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.case1: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.
*/finalclassSyntheticJoystickHandlerextendsHandler{privatefinalstaticintMSG_ENQUEUE_X_AXIS_KEY_REPEAT=1;privatefinalstaticintMSG_ENQUEUE_Y_AXIS_KEY_REPEAT=2;privatefinalJoystickAxesState mJoystickAxesState =newJoystickAxesState();privatefinalSparseArray<KeyEvent> mDeviceKeyEvents =newSparseArray<>();publicSyntheticJoystickHandler(){super(true);}@OverridepublicvoidhandleMessage(Message msg){switch(msg.what){caseMSG_ENQUEUE_X_AXIS_KEY_REPEAT:caseMSG_ENQUEUE_Y_AXIS_KEY_REPEAT:{if(mAttachInfo.mHasWindowFocus){KeyEvent oldEvent =(KeyEvent) msg.obj;KeyEvent e =KeyEvent.changeTimeRepeat(oldEvent,SystemClock.uptimeMillis(), oldEvent.getRepeatCount()+1);enqueueInputEvent(e);Message m =obtainMessage(msg.what, e);
m.setAsynchronous(true);sendMessageDelayed(m,ViewConfiguration.getKeyRepeatDelay());}}break;}}publicvoidprocess(MotionEvent event){switch(event.getActionMasked()){caseMotionEvent.ACTION_CANCEL:cancel();break;caseMotionEvent.ACTION_MOVE:update(event);break;default:Log.w(mTag,"Unexpected action: "+ event.getActionMasked());}}privatevoidcancel(){removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);for(int i =0; i < mDeviceKeyEvents.size(); i++){finalKeyEvent keyEvent = mDeviceKeyEvents.valueAt(i);if(keyEvent !=null){enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent,SystemClock.uptimeMillis(),0));}}
mDeviceKeyEvents.clear();
mJoystickAxesState.resetState();}privatevoidupdate(MotionEvent event){finalint historySize = event.getHistorySize();for(int h =0; h < historySize; h++){finallong time = event.getHistoricalEventTime(h);
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_X,
event.getHistoricalAxisValue(MotionEvent.AXIS_X,0, h));
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_Y,
event.getHistoricalAxisValue(MotionEvent.AXIS_Y,0, h));
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_HAT_X,
event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X,0, h));
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_HAT_Y,
event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y,0, h));}finallong time = event.getEventTime();
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_X,
event.getAxisValue(MotionEvent.AXIS_X));
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_Y,
event.getAxisValue(MotionEvent.AXIS_Y));
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_HAT_X,
event.getAxisValue(MotionEvent.AXIS_HAT_X));
mJoystickAxesState.updateStateForAxis(event, time,MotionEvent.AXIS_HAT_Y,
event.getAxisValue(MotionEvent.AXIS_HAT_Y));}finalclassJoystickAxesState{// State machine: from neutral state (no button press) can go into// button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event.// From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state,// emitting an ACTION_UP event.privatestaticfinalintSTATE_UP_OR_LEFT=-1;privatestaticfinalintSTATE_NEUTRAL=0;privatestaticfinalintSTATE_DOWN_OR_RIGHT=1;finalint[] mAxisStatesHat ={STATE_NEUTRAL,STATE_NEUTRAL};// {AXIS_HAT_X, AXIS_HAT_Y}finalint[] mAxisStatesStick ={STATE_NEUTRAL,STATE_NEUTRAL};// {AXIS_X, AXIS_Y}voidresetState(){
mAxisStatesHat[0]=STATE_NEUTRAL;
mAxisStatesHat[1]=STATE_NEUTRAL;
mAxisStatesStick[0]=STATE_NEUTRAL;
mAxisStatesStick[1]=STATE_NEUTRAL;}voidupdateStateForAxis(MotionEvent event,long time,int axis,float value){// Emit KeyEvent if necessary// axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Yfinalint axisStateIndex;finalint repeatMessage;if(isXAxis(axis)){
axisStateIndex =0;
repeatMessage =MSG_ENQUEUE_X_AXIS_KEY_REPEAT;}elseif(isYAxis(axis)){
axisStateIndex =1;
repeatMessage =MSG_ENQUEUE_Y_AXIS_KEY_REPEAT;}else{Log.e(mTag,"Unexpected axis "+ axis +" in updateStateForAxis!");return;}finalint newState =joystickAxisValueToState(value);finalint currentState;if(axis ==MotionEvent.AXIS_X|| axis ==MotionEvent.AXIS_Y){
currentState = mAxisStatesStick[axisStateIndex];}else{
currentState = mAxisStatesHat[axisStateIndex];}if(currentState == newState){return;}finalint metaState = event.getMetaState();finalint deviceId = event.getDeviceId();finalint source = event.getSource();if(currentState ==STATE_DOWN_OR_RIGHT|| currentState ==STATE_UP_OR_LEFT){// send a button release eventfinalint keyCode =joystickAxisAndStateToKeycode(axis, currentState);if(keyCode !=KeyEvent.KEYCODE_UNKNOWN){enqueueInputEvent(newKeyEvent(time, time,KeyEvent.ACTION_UP, keyCode,0, metaState, deviceId,0,KeyEvent.FLAG_FALLBACK, source));// remove the corresponding pending UP event if focus lost/view detached
mDeviceKeyEvents.put(deviceId,null);}removeMessages(repeatMessage);}if(newState ==STATE_DOWN_OR_RIGHT|| newState ==STATE_UP_OR_LEFT){// send a button down eventfinalint keyCode =joystickAxisAndStateToKeycode(axis, newState);if(keyCode !=KeyEvent.KEYCODE_UNKNOWN){KeyEvent keyEvent =newKeyEvent(time, time,KeyEvent.ACTION_DOWN, keyCode,0, metaState, deviceId,0,KeyEvent.FLAG_FALLBACK, source);enqueueInputEvent(keyEvent);Message m =obtainMessage(repeatMessage, keyEvent);
m.setAsynchronous(true);sendMessageDelayed(m,ViewConfiguration.getKeyRepeatTimeout());// store the corresponding ACTION_UP event so that it can be sent// if focus is lost or root view is removed
mDeviceKeyEvents.put(deviceId,newKeyEvent(time, time,KeyEvent.ACTION_UP, keyCode,0, metaState, deviceId,0,KeyEvent.FLAG_FALLBACK|KeyEvent.FLAG_CANCELED,
source));}}if(axis ==MotionEvent.AXIS_X|| axis ==MotionEvent.AXIS_Y){
mAxisStatesStick[axisStateIndex]= newState;}else{
mAxisStatesHat[axisStateIndex]= newState;}}privatebooleanisXAxis(int axis){return axis ==MotionEvent.AXIS_X|| axis ==MotionEvent.AXIS_HAT_X;}privatebooleanisYAxis(int axis){return axis ==MotionEvent.AXIS_Y|| axis ==MotionEvent.AXIS_HAT_Y;}privateintjoystickAxisAndStateToKeycode(int axis,int state){if(isXAxis(axis)&& state ==STATE_UP_OR_LEFT){returnKeyEvent.KEYCODE_DPAD_LEFT;}if(isXAxis(axis)&& state ==STATE_DOWN_OR_RIGHT){returnKeyEvent.KEYCODE_DPAD_RIGHT;}if(isYAxis(axis)&& state ==STATE_UP_OR_LEFT){returnKeyEvent.KEYCODE_DPAD_UP;}if(isYAxis(axis)&& state ==STATE_DOWN_OR_RIGHT){returnKeyEvent.KEYCODE_DPAD_DOWN;}Log.e(mTag,"Unknown axis "+ axis +" or direction "+ state);returnKeyEvent.KEYCODE_UNKNOWN;// should never happen}privateintjoystickAxisValueToState(float value){if(value >=0.5f){returnSTATE_DOWN_OR_RIGHT;}elseif(value <=-0.5f){returnSTATE_UP_OR_LEFT;}else{returnSTATE_NEUTRAL;}}}}/**
* Creates dpad events from unhandled touch navigation movements.
*/finalclassSyntheticTouchNavigationHandlerextendsHandler{privatestaticfinalStringLOCAL_TAG="SyntheticTouchNavigationHandler";privatestaticfinalbooleanLOCAL_DEBUG=false;// Assumed nominal width and height in millimeters of a touch navigation pad,// if no resolution information is available from the input system.privatestaticfinalfloatDEFAULT_WIDTH_MILLIMETERS=48;privatestaticfinalfloatDEFAULT_HEIGHT_MILLIMETERS=48;/* TODO: These constants should eventually be moved to ViewConfiguration. */// The nominal distance traveled to move by one unit.privatestaticfinalintTICK_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.privatestaticfinalfloatMIN_FLING_VELOCITY_TICKS_PER_SECOND=6f;privatestaticfinalfloatMAX_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.privatestaticfinalfloatFLING_TICK_DECAY=0.8f;/* The input device that we are tracking. */privateint mCurrentDeviceId =-1;privateint mCurrentSource;privateboolean 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.privatefloat mConfigTickDistance;// The minimum and maximum scaled fling velocity.privatefloat mConfigMinFlingVelocity;privatefloat mConfigMaxFlingVelocity;/* Tracking state. */// The velocity tracker for detecting flings.privateVelocityTracker mVelocityTracker;// The active pointer id, or -1 if none.privateint mActivePointerId =-1;// Location where tracking started.privatefloat mStartX;privatefloat mStartY;// Most recently observed position.privatefloat mLastX;privatefloat mLastY;// Accumulated movement delta since the last direction key was sent.privatefloat mAccumulatedX;privatefloat mAccumulatedY;// Set to true if any movement was delivered to the app.// Implies that tap slop was exceeded.privateboolean 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.privatelong mPendingKeyDownTime;privateint mPendingKeyCode =KeyEvent.KEYCODE_UNKNOWN;privateint mPendingKeyRepeatCount;privateint mPendingKeyMetaState;// The current fling velocity while a fling is in progress.privateboolean mFlinging;privatefloat mFlingVelocity;publicSyntheticTouchNavigationHandler(){super(true);}publicvoidprocess(MotionEvent event){// Update the current device information.finallong time = event.getEventTime();finalint deviceId = event.getDeviceId();finalint 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.finalint action = event.getActionMasked();switch(action){caseMotionEvent.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;}caseMotionEvent.ACTION_MOVE:caseMotionEvent.ACTION_UP:{if(mActivePointerId <0){break;}finalint index = event.findPointerIndex(mActivePointerId);if(index <0){finishKeys(time);finishTracking(time);break;}
mVelocityTracker.addMovement(event);finalfloat x = event.getX(index);finalfloat y = event.getY(index);
mAccumulatedX += x - mLastX;
mAccumulatedY += y - mLastY;
mLastX = x;
mLastY = y;// Consume any accumulated movement so far.finalint 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);finalfloat vx = mVelocityTracker.getXVelocity(mActivePointerId);finalfloat vy = mVelocityTracker.getYVelocity(mActivePointerId);if(!startFling(time, vx, vy)){finishKeys(time);}}finishTracking(time);}break;}caseMotionEvent.ACTION_CANCEL:{finishKeys(time);finishTracking(time);break;}}}publicvoidcancel(MotionEvent event){if(mCurrentDeviceId == event.getDeviceId()&& mCurrentSource == event.getSource()){finallong time = event.getEventTime();finishKeys(time);finishTracking(time);}}privatevoidfinishKeys(long time){cancelFling();sendKeyUp(time);}privatevoidfinishTracking(long time){if(mActivePointerId >=0){
mActivePointerId =-1;
mVelocityTracker.recycle();
mVelocityTracker =null;}}privatevoidconsumeAccumulatedMovement(long time,int metaState){finalfloat absX =Math.abs(mAccumulatedX);finalfloat 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;}}}privatefloatconsumeAccumulatedMovement(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;}privatevoidsendKeyDownOrRepeat(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(newKeyEvent(mPendingKeyDownTime, time,KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount,
mPendingKeyMetaState, mCurrentDeviceId,KeyEvent.FLAG_FALLBACK, mCurrentSource));}privatevoidsendKeyUp(long time){if(mPendingKeyCode !=KeyEvent.KEYCODE_UNKNOWN){if(LOCAL_DEBUG){Log.d(LOCAL_TAG,"Sending key up: keyCode="+ mPendingKeyCode
+", metaState="+Integer.toHexString(mPendingKeyMetaState));}enqueueInputEvent(newKeyEvent(mPendingKeyDownTime, time,KeyEvent.ACTION_UP, mPendingKeyCode,0, mPendingKeyMetaState,
mCurrentDeviceId,0,KeyEvent.FLAG_FALLBACK,
mCurrentSource));
mPendingKeyCode =KeyEvent.KEYCODE_UNKNOWN;}}privatebooleanstartFling(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){caseKeyEvent.KEYCODE_DPAD_LEFT:if(-vx >= mConfigMinFlingVelocity
&&Math.abs(vy)< mConfigMinFlingVelocity){
mFlingVelocity =-vx;break;}returnfalse;caseKeyEvent.KEYCODE_DPAD_RIGHT:if(vx >= mConfigMinFlingVelocity
&&Math.abs(vy)< mConfigMinFlingVelocity){
mFlingVelocity = vx;break;}returnfalse;caseKeyEvent.KEYCODE_DPAD_UP:if(-vy >= mConfigMinFlingVelocity
&&Math.abs(vx)< mConfigMinFlingVelocity){
mFlingVelocity =-vy;break;}returnfalse;caseKeyEvent.KEYCODE_DPAD_DOWN:if(vy >= mConfigMinFlingVelocity
&&Math.abs(vx)< mConfigMinFlingVelocity){
mFlingVelocity = vy;break;}returnfalse;}// Post the first fling event.
mFlinging =postFling(time);return mFlinging;}privatebooleanpostFling(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);}returntrue;}returnfalse;}privatevoidcancelFling(){if(mFlinging){removeCallbacks(mFlingRunnable);
mFlinging =false;}}privatefinalRunnable mFlingRunnable =newRunnable(){@Overridepublicvoidrun(){finallong time =SystemClock.uptimeMillis();sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState);
mFlingVelocity *=FLING_TICK_DECAY;if(!postFling(time)){
mFlinging =false;finishKeys(time);}}};}finalclassSyntheticKeyboardHandler{publicvoidprocess(KeyEvent event){if((event.getFlags()&KeyEvent.FLAG_FALLBACK)!=0){return;}finalKeyCharacterMap kcm = event.getKeyCharacterMap();finalint keyCode = event.getKeyCode();finalint metaState = event.getMetaState();// Check for fallback actions specified by the key character map.KeyCharacterMap.FallbackAction fallbackAction =
kcm.getFallbackAction(keyCode, metaState);if(fallbackAction !=null){finalint 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.
*/privatestaticbooleanisNavigationKey(KeyEvent keyEvent){switch(keyEvent.getKeyCode()){caseKeyEvent.KEYCODE_DPAD_LEFT:caseKeyEvent.KEYCODE_DPAD_RIGHT:caseKeyEvent.KEYCODE_DPAD_UP:caseKeyEvent.KEYCODE_DPAD_DOWN:caseKeyEvent.KEYCODE_DPAD_CENTER:caseKeyEvent.KEYCODE_PAGE_UP:caseKeyEvent.KEYCODE_PAGE_DOWN:caseKeyEvent.KEYCODE_MOVE_HOME:caseKeyEvent.KEYCODE_MOVE_END:caseKeyEvent.KEYCODE_TAB:caseKeyEvent.KEYCODE_SPACE:caseKeyEvent.KEYCODE_ENTER:returntrue;}returnfalse;}/**
* Returns true if the key is used for typing.
* @param keyEvent The key event.
* @return True if the key is used for typing.
*/privatestaticbooleanisTypingKey(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).
*/privatebooleancheckForLeavingTouchModeAndConsume(KeyEvent event){// Only relevant in touch mode.if(!mAttachInfo.mInTouchMode){returnfalse;}// Only consider leaving touch mode on DOWN or MULTIPLE actions, never on UP.finalint action = event.getAction();if(action !=KeyEvent.ACTION_DOWN&& action !=KeyEvent.ACTION_MULTIPLE){returnfalse;}// Don't leave touch mode if the IME told us not to.if((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE)!=0){returnfalse;}// 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)){returnensureTouchMode(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);returnfalse;}returnfalse;}/* drag/drop */voidsetLocalDragState(Object obj){
mLocalDragState = obj;}privatevoidhandleDragEvent(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){finalint what = event.mAction;// Cache the drag description when the operation starts, then fill it in// on subsequent calls as a convenienceif(what ==DragEvent.ACTION_DRAG_STARTED){
mCurrentDragView =null;// Start the current-recipient tracking
mDragDescription = event.mClipDescription;}else{if(what ==DragEvent.ACTION_DRAG_ENDED){
mDragDescription =null;}
event.mClipDescription = mDragDescription;}if(what ==DragEvent.ACTION_DRAG_EXITED){// A direct EXITED event means that the window manager knows we've just crossed// a window boundary, so the current drag target within this one must have// just been exited. Send the EXITED notification to the current drag view, if any.if(View.sCascadedDragDrop){
mView.dispatchDragEnterExitInPreN(event);}setDragFocus(null, event);}else{// For events with a [screen] location, translate into window coordinatesif((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-dispatchfinalView prevDragView = mCurrentDragView;if(what ==DragEvent.ACTION_DROP&& event.mClipData !=null){
event.mClipData.prepareToEnterProcess();}// Now dispatch the drag/drop eventboolean result = mView.dispatchDragEvent(event);if(what ==DragEvent.ACTION_DRAG_LOCATION&&!event.mEventHandlerWasCalled){// If the LOCATION event wasn't delivered to any handler, no view now has a drag// focus.setDragFocus(null, event);}// If we changed apparent drag target, tell the OS about itif(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 doneif(what ==DragEvent.ACTION_DROP){try{Log.i(mTag,"Reporting drop result: "+ result);
mWindowSession.reportDropResult(mWindow, result);}catch(RemoteException e){Log.e(mTag,"Unable to report drop result");}}// When the drag operation ends, reset drag-related stateif(what ==DragEvent.ACTION_DRAG_ENDED){
mCurrentDragView =null;setLocalDragState(null);
mAttachInfo.mDragToken =null;if(mAttachInfo.mDragSurface !=null){
mAttachInfo.mDragSurface.release();
mAttachInfo.mDragSurface =null;}}}}
event.recycle();}publicvoidhandleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args){if(mSeq != args.seq){// The sequence has changed, so we need to update our value and make// sure to do a traversal afterward so the window manager is given our// most recent data.
mSeq = args.seq;
mAttachInfo.mForceReportNewAttributes =true;scheduleTraversals();}if(mView ==null)return;if(args.localChanges !=0){
mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);}int visibility = args.globalVisibility&View.SYSTEM_UI_CLEARABLE_FLAGS;if(visibility != mAttachInfo.mGlobalSystemUiVisibility){
mAttachInfo.mGlobalSystemUiVisibility = visibility;
mView.dispatchSystemUiVisibilityChanged(visibility);}}/**
* Notify that the window title changed
*/publicvoidonWindowTitleChanged(){
mAttachInfo.mForceReportNewAttributes =true;}publicvoidhandleDispatchWindowShown(){
mAttachInfo.mTreeObserver.dispatchOnWindowShown();}publicvoidhandleRequestKeyboardShortcuts(IResultReceiver receiver,int deviceId){Bundle data =newBundle();ArrayList<KeyboardShortcutGroup> list =newArrayList<>();if(mView !=null){
mView.requestKeyboardShortcuts(list, deviceId);}
data.putParcelableArrayList(WindowManager.PARCEL_KEY_SHORTCUTS_ARRAY, list);try{
receiver.send(0, data);}catch(RemoteException e){}}publicvoidgetLastTouchPoint(Point outLocation){
outLocation.x =(int) mLastTouchPoint.x;
outLocation.y =(int) mLastTouchPoint.y;}publicintgetLastTouchSource(){return mLastTouchSource;}publicvoidsetDragFocus(View newDragTarget,DragEvent event){if(mCurrentDragView != newDragTarget &&!View.sCascadedDragDrop){// Send EXITED and ENTERED notifications to the old and new drag focus views.finalfloat tx = event.mX;finalfloat ty = event.mY;finalint action = event.mAction;finalClipData td = event.mClipData;// Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
event.mX =0;
event.mY =0;
event.mClipData =null;if(mCurrentDragView !=null){
event.mAction =DragEvent.ACTION_DRAG_EXITED;
mCurrentDragView.callDragEventHandler(event);}if(newDragTarget !=null){
event.mAction =DragEvent.ACTION_DRAG_ENTERED;
newDragTarget.callDragEventHandler(event);}
event.mAction = action;
event.mX = tx;
event.mY = ty;
event.mClipData = td;}
mCurrentDragView = newDragTarget;}privateAudioManagergetAudioManager(){if(mView ==null){thrownewIllegalStateException("getAudioManager called when there is no mView");}if(mAudioManager ==null){
mAudioManager =(AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE);}return mAudioManager;}private@NullableAutofillManagergetAutofillManager(){if(mView instanceofViewGroup){ViewGroup decorView =(ViewGroup) mView;if(decorView.getChildCount()>0){// We cannot use decorView's Context for querying AutofillManager: DecorView's// context is based on Application Context, it would allocate a different// AutofillManager instance.return decorView.getChildAt(0).getContext().getSystemService(AutofillManager.class);}}returnnull;}privatebooleanisAutofillUiShowing(){AutofillManager afm =getAutofillManager();if(afm ==null){returnfalse;}return afm.isAutofillUiShowing();}publicAccessibilityInteractionControllergetAccessibilityInteractionController(){if(mView ==null){thrownewIllegalStateException("getAccessibilityInteractionController"+" called when there is no mView");}if(mAccessibilityInteractionController ==null){
mAccessibilityInteractionController =newAccessibilityInteractionController(this);}return mAccessibilityInteractionController;}privateintrelayoutWindow(WindowManager.LayoutParams params,int viewVisibility,boolean insetsPending)throwsRemoteException{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);if(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;}}}long frameNumber =-1;if(mSurface.isValid()){
frameNumber = mSurface.getNextFrameNumber();}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, frameNumber,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurface);
mPendingAlwaysConsumeNavBar =(relayoutResult &WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR)!=0;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}
*/@OverridepublicvoidplaySoundEffect(int effectId){checkThread();try{finalAudioManager audioManager =getAudioManager();switch(effectId){caseSoundEffectConstants.CLICK:
audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);return;caseSoundEffectConstants.NAVIGATION_DOWN:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);return;caseSoundEffectConstants.NAVIGATION_LEFT:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);return;caseSoundEffectConstants.NAVIGATION_RIGHT:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);return;caseSoundEffectConstants.NAVIGATION_UP:
audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);return;default:thrownewIllegalArgumentException("unknown effect id "+ effectId +" not defined in "+SoundEffectConstants.class.getCanonicalName());}}catch(IllegalStateException e){// Exception thrown by getAudioManager() when mView is nullLog.e(mTag,"FATAL EXCEPTION when attempting to play sound effect: "+ e);
e.printStackTrace();}}/**
* {@inheritDoc}
*/@OverridepublicbooleanperformHapticFeedback(int effectId,boolean always){try{return mWindowSession.performHapticFeedback(mWindow, effectId, always);}catch(RemoteException e){returnfalse;}}/**
* {@inheritDoc}
*/@OverridepublicViewfocusSearch(View focused,int direction){checkThread();if(!(mView instanceofViewGroup)){returnnull;}if(JoyarHelper.DBG){Log.d(TAG,"focusSearch findNextFocus root:"+ mView +" focused:"+ focused);}returnFocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);}
AdapterView.java
/**
* Register a callback to be invoked when an item in this AdapterView has
* been clicked.
*
* @param listener The callback that will be invoked.
*/publicvoidsetOnItemClickListener(@NullableOnItemClickListener listener){JoyarHelper.getInstance().AdapterViewSetOnItemClickListener(this, listener);
mOnItemClickListener = listener;}/**
* @return The callback to be invoked with an item in this AdapterView has
* been clicked, or null id no callback has been set.
*/@NullablepublicfinalOnItemClickListenergetOnItemClickListener(){return mOnItemClickListener;}/**
* Call the OnItemClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @param view The view within the AdapterView that was clicked.
* @param position The position of the view in the adapter.
* @param id The row id of the item that was clicked.
* @return True if there was an assigned OnItemClickListener that was
* called, false otherwise is returned.
*/publicbooleanperformItemClick(View view,int position,long id){finalboolean result;if(mOnItemClickListener !=null){playSoundEffect(SoundEffectConstants.CLICK);if(!JoyarHelper.getInstance().AdapterViewOnItemClick(this, view, position, id)){
mOnItemClickListener.onItemClick(this, view, position, id);}
result =true;}elseif(JoyarHelper.getInstance().AdapterViewPerformItemClick(this, view, position, id)){
result =true;}else{
result =false;}if(view !=null){
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);}return result;}/**
* Interface definition for a callback to be invoked when an item in this
* view has been clicked and held.
*/publicinterfaceOnItemLongClickListener{/**
* Callback method to be invoked when an item in this view has been
* clicked and held.
*
* Implementers can call getItemAtPosition(position) if they need to access
* the data associated with the selected item.
*
* @param parent The AbsListView where the click happened
* @param view The view within the AbsListView that was clicked
* @param position The position of the view in the list
* @param id The row id of the item that was clicked
*
* @return true if the callback consumed the long click, false otherwise
*/booleanonItemLongClick(AdapterView<?> parent,View view,int position,long id);}/**
* Register a callback to be invoked when an item in this AdapterView has
* been clicked and held
*
* @param listener The callback that will run
*/publicvoidsetOnItemLongClickListener(OnItemLongClickListener listener){if(!isLongClickable()){setLongClickable(true);}
mOnItemLongClickListener = listener;}/**
* @return The callback to be invoked with an item in this AdapterView has
* been clicked and held, or null id no callback as been set.
*/publicfinalOnItemLongClickListenergetOnItemLongClickListener(){return mOnItemLongClickListener;}/**
* Interface definition for a callback to be invoked when
* an item in this view has been selected.
*/publicinterfaceOnItemSelectedListener{/**
* <p>Callback method to be invoked when an item in this view has been
* selected. This callback is invoked only when the newly selected
* position is different from the previously selected position or if
* there was no selected item.</p>
*
* Implementers can call getItemAtPosition(position) if they need to access the
* data associated with the selected item.
*
* @param parent The AdapterView where the selection happened
* @param view The view within the AdapterView that was clicked
* @param position The position of the view in the adapter
* @param id The row id of the item that is selected
*/voidonItemSelected(AdapterView<?> parent,View view,int position,long id);/**
* Callback method to be invoked when the selection disappears from this
* view. The selection can disappear for instance when touch is activated
* or when the adapter becomes empty.
*
* @param parent The AdapterView that now contains no selected item.
*/voidonNothingSelected(AdapterView<?> parent);}/**
* Register a callback to be invoked when an item in this AdapterView has
* been selected.
*
* @param listener The callback that will run
*/publicvoidsetOnItemSelectedListener(@NullableOnItemSelectedListener listener){
mOnItemSelectedListener = listener;}@NullablepublicfinalOnItemSelectedListenergetOnItemSelectedListener(){return mOnItemSelectedListener;}/**
* Extra menu information provided to the
* {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
* callback when a context menu is brought up for this AdapterView.
*
*/publicstaticclassAdapterContextMenuInfoimplementsContextMenu.ContextMenuInfo{publicAdapterContextMenuInfo(View targetView,int position,long id){this.targetView = targetView;this.position = position;this.id = id;}/**
* The child view for which the context menu is being displayed. This
* will be one of the children of this AdapterView.
*/publicView targetView;/**
* The position in the adapter for which the context menu is being
* displayed.
*/publicint position;/**
* The row id of the item for which the context menu is being displayed.
*/publiclong id;}/**
* Returns the adapter currently associated with this widget.
*
* @return The adapter used to provide this view's content.
*/publicabstractTgetAdapter();/**
* Sets the adapter that provides the data and the views to represent the data
* in this widget.
*
* @param adapter The adapter to use to create this view's content.
*/publicabstractvoidsetAdapter(T adapter);/**
* This method is not supported and throws an UnsupportedOperationException when called.
*
* @param child Ignored.
*
* @throws UnsupportedOperationException Every time this method is invoked.
*/@OverridepublicvoidaddView(View child){thrownewUnsupportedOperationException("addView(View) is not supported in AdapterView");}/**
* This method is not supported and throws an UnsupportedOperationException when called.
*
* @param child Ignored.
* @param index Ignored.
*
* @throws UnsupportedOperationException Every time this method is invoked.
*/@OverridepublicvoidaddView(View child,int index){thrownewUnsupportedOperationException("addView(View, int) is not supported in AdapterView");}/**
* This method is not supported and throws an UnsupportedOperationException when called.
*
* @param child Ignored.
* @param params Ignored.
*
* @throws UnsupportedOperationException Every time this method is invoked.
*/@OverridepublicvoidaddView(View child,LayoutParams params){thrownewUnsupportedOperationException("addView(View, LayoutParams) "+"is not supported in AdapterView");}/**
* This method is not supported and throws an UnsupportedOperationException when called.
*
* @param child Ignored.
* @param index Ignored.
* @param params Ignored.
*
* @throws UnsupportedOperationException Every time this method is invoked.
*/@OverridepublicvoidaddView(View child,int index,LayoutParams params){thrownewUnsupportedOperationException("addView(View, int, LayoutParams) "+"is not supported in AdapterView");}/**
* This method is not supported and throws an UnsupportedOperationException when called.
*
* @param child Ignored.
*
* @throws UnsupportedOperationException Every time this method is invoked.
*/@OverridepublicvoidremoveView(View child){thrownewUnsupportedOperationException("removeView(View) is not supported in AdapterView");}/**
* This method is not supported and throws an UnsupportedOperationException when called.
*
* @param index Ignored.
*
* @throws UnsupportedOperationException Every time this method is invoked.
*/@OverridepublicvoidremoveViewAt(int index){thrownewUnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");}/**
* This method is not supported and throws an UnsupportedOperationException when called.
*
* @throws UnsupportedOperationException Every time this method is invoked.
*/@OverridepublicvoidremoveAllViews(){thrownewUnsupportedOperationException("removeAllViews() is not supported in AdapterView");}@OverrideprotectedvoidonLayout(boolean changed,int left,int top,int right,int bottom){
mLayoutHeight =getHeight();}/**
* Return the position of the currently selected item within the adapter's data set
*
* @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
*/@ViewDebug.CapturedViewPropertypublicintgetSelectedItemPosition(){return mNextSelectedPosition;}/**
* @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
* if nothing is selected.
*/@ViewDebug.CapturedViewPropertypubliclonggetSelectedItemId(){return mNextSelectedRowId;}/**
* @return The view corresponding to the currently selected item, or null
* if nothing is selected
*/publicabstractViewgetSelectedView();/**
* @return The data corresponding to the currently selected item, or
* null if there is nothing selected.
*/publicObjectgetSelectedItem(){T adapter =getAdapter();int selection =getSelectedItemPosition();if(adapter !=null&& adapter.getCount()>0&& selection >=0){return adapter.getItem(selection);}else{returnnull;}}/**
* @return The number of items owned by the Adapter associated with this
* AdapterView. (This is the number of data items, which may be
* larger than the number of visible views.)
*/@ViewDebug.CapturedViewPropertypublicintgetCount(){return mItemCount;}/**
* Returns the position within the adapter's data set for the view, where
* view is a an adapter item or a descendant of an adapter item.
* <p>
* <strong>Note:</strong> The result of this method only reflects the
* position of the data bound to <var>view</var> during the most recent
* layout pass. If the adapter's data set has changed without a subsequent
* layout pass, the position returned by this method may not match the
* current position of the data within the adapter.
*
* @param view an adapter item, or a descendant of an adapter item. This
* must be visible in this AdapterView at the time of the call.
* @return the position within the adapter's data set of the view, or
* {@link #INVALID_POSITION} if the view does not correspond to a
* list item (or it is not currently visible)
*/publicintgetPositionForView(View view){View listItem = view;try{View v;while((v =(View) listItem.getParent())!=null&&!v.equals(this)){
listItem = v;}}catch(ClassCastException e){// We made it up to the window without find this list viewreturnINVALID_POSITION;}if(listItem !=null){// Search the children for the list itemfinalint childCount =getChildCount();for(int i =0; i < childCount; i++){if(getChildAt(i).equals(listItem)){return mFirstPosition + i;}}}// Child not found!returnINVALID_POSITION;}/**
* Returns the position within the adapter's data set for the first item
* displayed on screen.
*
* @return The position within the adapter's data set
*/publicintgetFirstVisiblePosition(){return mFirstPosition;}/**
* Returns the position within the adapter's data set for the last item
* displayed on screen.
*
* @return The position within the adapter's data set
*/publicintgetLastVisiblePosition(){return mFirstPosition +getChildCount()-1;}/**
* Sets the currently selected item. To support accessibility subclasses that
* override this method must invoke the overriden super method first.
*
* @param position Index (starting at 0) of the data item to be selected.
*/publicabstractvoidsetSelection(int position);/**
* Sets the view to show if the adapter is empty
*/@android.view.RemotableViewMethodpublicvoidsetEmptyView(View emptyView){
mEmptyView = emptyView;// If not explicitly specified this view is important for accessibility.if(emptyView !=null&& emptyView.getImportantForAccessibility()==IMPORTANT_FOR_ACCESSIBILITY_AUTO){
emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);}finalT adapter =getAdapter();finalboolean empty =((adapter ==null)|| adapter.isEmpty());updateEmptyStatus(empty);}/**
* When the current adapter is empty, the AdapterView can display a special view
* called the empty view. The empty view is used to provide feedback to the user
* that no data is available in this AdapterView.
*
* @return The view to show if the adapter is empty.
*/publicViewgetEmptyView(){return mEmptyView;}/**
* Indicates whether this view is in filter mode. Filter mode can for instance
* be enabled by a user when typing on the keyboard.
*
* @return True if the view is in filter mode, false otherwise.
*/booleanisInFilterMode(){returnfalse;}@OverridepublicvoidsetFocusable(@Focusableint focusable){finalT adapter =getAdapter();finalboolean empty = adapter ==null|| adapter.getCount()==0;
mDesiredFocusableState = focusable;if((focusable &(FOCUSABLE_AUTO|FOCUSABLE))==0){
mDesiredFocusableInTouchModeState =false;}super.setFocusable((!empty ||isInFilterMode())? focusable :NOT_FOCUSABLE);}@OverridepublicvoidsetFocusableInTouchMode(boolean focusable){finalT adapter =getAdapter();finalboolean empty = adapter ==null|| adapter.getCount()==0;
mDesiredFocusableInTouchModeState = focusable;if(focusable){
mDesiredFocusableState =FOCUSABLE;}super.setFocusableInTouchMode(focusable &&(!empty ||isInFilterMode()));}voidcheckFocus(){finalT adapter =getAdapter();finalboolean empty = adapter ==null|| adapter.getCount()==0;finalboolean focusable =!empty ||isInFilterMode();// The order in which we set focusable in touch mode/focusable may matter// for the client, see View.setFocusableInTouchMode() comments for more// detailssuper.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);super.setFocusable(focusable ? mDesiredFocusableState :NOT_FOCUSABLE);if(mEmptyView !=null){updateEmptyStatus((adapter ==null)|| adapter.isEmpty());}}/**
* Update the status of the list based on the empty parameter. If empty is true and
* we have an empty view, display it. In all the other cases, make sure that the listview
* is VISIBLE and that the empty view is GONE (if it's not null).
*/privatevoidupdateEmptyStatus(boolean empty){if(isInFilterMode()){
empty =false;}if(empty){if(mEmptyView !=null){
mEmptyView.setVisibility(View.VISIBLE);setVisibility(View.GONE);}else{// If the caller just removed our empty view, make sure the list view is visiblesetVisibility(View.VISIBLE);}// We are now GONE, so pending layouts will not be dispatched.// Force one here to make sure that the state of the list matches// the state of the adapter.if(mDataChanged){this.onLayout(false, mLeft, mTop, mRight, mBottom);}}else{if(mEmptyView !=null) mEmptyView.setVisibility(View.GONE);setVisibility(View.VISIBLE);}}/**
* Gets the data associated with the specified position in the list.
*
* @param position Which data to get
* @return The data associated with the specified position in the list
*/publicObjectgetItemAtPosition(int position){T adapter =getAdapter();return(adapter ==null|| position <0)?null: adapter.getItem(position);}publiclonggetItemIdAtPosition(int position){T adapter =getAdapter();return(adapter ==null|| position <0)?INVALID_ROW_ID: adapter.getItemId(position);}@OverridepublicvoidsetOnClickListener(OnClickListener l){thrownewRuntimeException("Don't call setOnClickListener for an AdapterView. "+"You probably want setOnItemClickListener instead");}/**
* Override to prevent freezing of any views created by the adapter.
*/@OverrideprotectedvoiddispatchSaveInstanceState(SparseArray<Parcelable> container){dispatchFreezeSelfOnly(container);}/**
* Override to prevent thawing of any views created by the adapter.
*/@OverrideprotectedvoiddispatchRestoreInstanceState(SparseArray<Parcelable> container){dispatchThawSelfOnly(container);}classAdapterDataSetObserverextendsDataSetObserver{privateParcelable mInstanceState =null;@OverridepublicvoidonChanged(){
mDataChanged =true;
mOldItemCount = mItemCount;
mItemCount =getAdapter().getCount();// Detect the case where a cursor that was previously invalidated has// been repopulated with new data.if(AdapterView.this.getAdapter().hasStableIds()&& mInstanceState !=null&& mOldItemCount ==0&& mItemCount >0){AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState =null;}else{rememberSyncState();}checkFocus();requestLayout();}@OverridepublicvoidonInvalidated(){
mDataChanged =true;if(AdapterView.this.getAdapter().hasStableIds()){// Remember the current state for the case where our hosting activity is being// stopped and later restarted
mInstanceState =AdapterView.this.onSaveInstanceState();}// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount =0;
mSelectedPosition =INVALID_POSITION;
mSelectedRowId =INVALID_ROW_ID;
mNextSelectedPosition =INVALID_POSITION;
mNextSelectedRowId =INVALID_ROW_ID;
mNeedSync =false;checkFocus();requestLayout();}publicvoidclearSavedState(){
mInstanceState =null;}}@OverrideprotectedvoidonDetachedFromWindow(){super.onDetachedFromWindow();removeCallbacks(mSelectionNotifier);}privateclassSelectionNotifierimplementsRunnable{publicvoidrun(){
mPendingSelectionNotifier =null;if(mDataChanged &&getViewRootImpl()!=null&&getViewRootImpl().isLayoutRequested()){// Data has changed between when this SelectionNotifier was// posted and now. Postpone the notification until the next// layout is complete and we run checkSelectionChanged().if(getAdapter()!=null){
mPendingSelectionNotifier =this;}}else{dispatchOnItemSelected();}}}voidselectionChanged(){// We're about to post or run the selection notifier, so we don't need// a pending notifier.
mPendingSelectionNotifier =null;if(mOnItemSelectedListener !=null||AccessibilityManager.getInstance(mContext).isEnabled()){if(mInLayout || mBlockLayoutRequests){// If we are in a layout traversal, defer notification// by posting. This ensures that the view tree is// in a consistent state and is able to accommodate// new layout or invalidate requests.if(mSelectionNotifier ==null){
mSelectionNotifier =newSelectionNotifier();}else{removeCallbacks(mSelectionNotifier);}post(mSelectionNotifier);}else{dispatchOnItemSelected();}}// Always notify AutoFillManager - it will return right away if autofill is disabled.finalAutofillManager afm = mContext.getSystemService(AutofillManager.class);if(afm !=null){
afm.notifyValueChanged(this);}}privatevoiddispatchOnItemSelected(){fireOnSelected();performAccessibilityActionsOnSelected();}privatevoidfireOnSelected(){if(mOnItemSelectedListener ==null){return;}finalint selection =getSelectedItemPosition();if(selection >=0){View v =getSelectedView();
mOnItemSelectedListener.onItemSelected(this, v, selection,getAdapter().getItemId(selection));}else{
mOnItemSelectedListener.onNothingSelected(this);}}privatevoidperformAccessibilityActionsOnSelected(){if(!AccessibilityManager.getInstance(mContext).isEnabled()){return;}finalint position =getSelectedItemPosition();if(position >=0){// we fire selection events here not in ViewsendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);}}/** @hide */@OverridepublicbooleandispatchPopulateAccessibilityEventInternal(AccessibilityEvent event){View selectedView =getSelectedView();if(selectedView !=null&& selectedView.getVisibility()==VISIBLE&& selectedView.dispatchPopulateAccessibilityEvent(event)){returntrue;}returnfalse;}/** @hide */@OverridepublicbooleanonRequestSendAccessibilityEventInternal(View child,AccessibilityEvent event){if(super.onRequestSendAccessibilityEventInternal(child, event)){// Add a record for ourselves as well.AccessibilityEvent record =AccessibilityEvent.obtain();onInitializeAccessibilityEvent(record);// Populate with the text of the requesting child.
child.dispatchPopulateAccessibilityEvent(record);
event.appendRecord(record);returntrue;}returnfalse;}@OverridepublicCharSequencegetAccessibilityClassName(){returnAdapterView.class.getName();}/** @hide */@OverridepublicvoidonInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info){super.onInitializeAccessibilityNodeInfoInternal(info);
info.setScrollable(isScrollableForAccessibility());View selectedView =getSelectedView();if(selectedView !=null){
info.setEnabled(selectedView.isEnabled());}}/** @hide */@OverridepublicvoidonInitializeAccessibilityEventInternal(AccessibilityEvent event){super.onInitializeAccessibilityEventInternal(event);
event.setScrollable(isScrollableForAccessibility());View selectedView =getSelectedView();if(selectedView !=null){
event.setEnabled(selectedView.isEnabled());}
event.setCurrentItemIndex(getSelectedItemPosition());
event.setFromIndex(getFirstVisiblePosition());
event.setToIndex(getLastVisiblePosition());
event.setItemCount(getCount());}privatebooleanisScrollableForAccessibility(){T adapter =getAdapter();if(adapter !=null){finalint itemCount = adapter.getCount();return itemCount >0&&(getFirstVisiblePosition()>0||getLastVisiblePosition()< itemCount -1);}returnfalse;}@OverrideprotectedbooleancanAnimate(){returnsuper.canAnimate()&& mItemCount >0;}voidhandleDataChanged(){finalint count = mItemCount;boolean found =false;if(count >0){int newPos;// Find the row we are supposed to sync toif(mNeedSync){// Update this first, since setNextSelectedPositionInt inspects// it
mNeedSync =false;// See if we can find a position in the new data with the same// id as the old selection
newPos =findSyncPosition();if(newPos >=0){// Verify that new selection is selectableint selectablePos =lookForSelectablePosition(newPos,true);if(selectablePos == newPos){// Same row id is selectedsetNextSelectedPositionInt(newPos);
found =true;}}}if(!found){// Try to use the same position if we can't find matching data
newPos =getSelectedItemPosition();// Pin position to the available rangeif(newPos >= count){
newPos = count -1;}if(newPos <0){
newPos =0;}// Make sure we select something selectable -- first look downint selectablePos =lookForSelectablePosition(newPos,true);if(selectablePos <0){// Looking down didn't work -- try looking up
selectablePos =lookForSelectablePosition(newPos,false);}if(selectablePos >=0){setNextSelectedPositionInt(selectablePos);checkSelectionChanged();
found =true;}}}if(!found){// Nothing is selected
mSelectedPosition =INVALID_POSITION;
mSelectedRowId =INVALID_ROW_ID;
mNextSelectedPosition =INVALID_POSITION;
mNextSelectedRowId =INVALID_ROW_ID;
mNeedSync =false;checkSelectionChanged();}notifySubtreeAccessibilityStateChangedIfNeeded();}/**
* Called after layout to determine whether the selection position needs to
* be updated. Also used to fire any pending selection events.
*/voidcheckSelectionChanged(){if((mSelectedPosition != mOldSelectedPosition)||(mSelectedRowId != mOldSelectedRowId)){selectionChanged();
mOldSelectedPosition = mSelectedPosition;
mOldSelectedRowId = mSelectedRowId;}// If we have a pending selection notification -- and we won't if we// just fired one in selectionChanged() -- run it now.if(mPendingSelectionNotifier !=null){
mPendingSelectionNotifier.run();}}/**
* Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
* and then alternates between moving up and moving down until 1) we find the right position, or
* 2) we run out of time, or 3) we have looked at every position
*
* @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
* be found
*/intfindSyncPosition(){int count = mItemCount;if(count ==0){returnINVALID_POSITION;}long idToMatch = mSyncRowId;int seed = mSyncPosition;// If there isn't a selection don't hunt for itif(idToMatch ==INVALID_ROW_ID){returnINVALID_POSITION;}// Pin seed to reasonable values
seed =Math.max(0, seed);
seed =Math.min(count -1, seed);long endTime =SystemClock.uptimeMillis()+SYNC_MAX_DURATION_MILLIS;long rowId;// first position scanned so farint first = seed;// last position scanned so farint last = seed;// True if we should move down on the next iterationboolean next =false;// True when we have looked at the first item in the databoolean hitFirst;// True when we have looked at the last item in the databoolean hitLast;// Get the item ID locally (instead of getItemIdAtPosition), so// we need the adapterT adapter =getAdapter();if(adapter ==null){returnINVALID_POSITION;}while(SystemClock.uptimeMillis()<= endTime){
rowId = adapter.getItemId(seed);if(rowId == idToMatch){// Found it!return seed;}
hitLast = last == count -1;
hitFirst = first ==0;if(hitLast && hitFirst){// Looked at everythingbreak;}if(hitFirst ||(next &&!hitLast)){// Either we hit the top, or we are trying to move down
last++;
seed = last;// Try going up next time
next =false;}elseif(hitLast ||(!next &&!hitFirst)){// Either we hit the bottom, or we are trying to move up
first--;
seed = first;// Try going down next time
next =true;}}returnINVALID_POSITION;}/**
* Find a position that can be selected (i.e., is not a separator).
*
* @param position The starting position to look at.
* @param lookDown Whether to look down for other positions.
* @return The next selectable position starting at position and then searching either up or
* down. Returns {@link #INVALID_POSITION} if nothing can be found.
*/intlookForSelectablePosition(int position,boolean lookDown){return position;}/**
* Utility to keep mSelectedPosition and mSelectedRowId in sync
* @param position Our current position
*/voidsetSelectedPositionInt(int position){
mSelectedPosition = position;
mSelectedRowId =getItemIdAtPosition(position);JoyarHelper.getInstance().AdapterViewSetSelectedPositionInt(this, position);}/**
* Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
* @param position Intended value for mSelectedPosition the next time we go
* through layout
*/voidsetNextSelectedPositionInt(int position){
mNextSelectedPosition = position;
mNextSelectedRowId =getItemIdAtPosition(position);// If we are trying to sync to the selection, update that tooif(mNeedSync && mSyncMode ==SYNC_SELECTED_POSITION&& position >=0){
mSyncPosition = position;
mSyncRowId = mNextSelectedRowId;}}/**
* Remember enough information to restore the screen state when the data has
* changed.
*
*/voidrememberSyncState(){if(getChildCount()>0){
mNeedSync =true;
mSyncHeight = mLayoutHeight;if(mSelectedPosition >=0){// Sync the selection stateView v =getChildAt(mSelectedPosition - mFirstPosition);
mSyncRowId = mNextSelectedRowId;
mSyncPosition = mNextSelectedPosition;if(v !=null){
mSpecificTop = v.getTop();}
mSyncMode =SYNC_SELECTED_POSITION;}else{// Sync the based on the offset of the first viewView v =getChildAt(0);T adapter =getAdapter();if(mFirstPosition >=0&& mFirstPosition < adapter.getCount()){
mSyncRowId = adapter.getItemId(mFirstPosition);}else{
mSyncRowId =NO_ID;}
mSyncPosition = mFirstPosition;if(v !=null){
mSpecificTop = v.getTop();}
mSyncMode =SYNC_FIRST_POSITION;}}}/** @hide */@OverrideprotectedvoidencodeProperties(@NonNullViewHierarchyEncoder encoder){super.encodeProperties(encoder);
encoder.addProperty("scrolling:firstPosition", mFirstPosition);
encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition);
encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId);
encoder.addProperty("list:selectedPosition", mSelectedPosition);
encoder.addProperty("list:itemCount", mItemCount);}/**
* {@inheritDoc}
*
* <p>It also sets the autofill options in the structure; when overridden, it should set it as
* well, either explicitly by calling {@link ViewStructure#setAutofillOptions(CharSequence[])}
* or implicitly by calling {@code super.onProvideAutofillStructure(structure, flags)}.
*/@OverridepublicvoidonProvideAutofillStructure(ViewStructure structure,int flags){super.onProvideAutofillStructure(structure, flags);finalAdapter adapter =getAdapter();if(adapter ==null)return;finalCharSequence[] options = adapter.getAutofillOptions();if(options !=null){
structure.setAutofillOptions(options);}}}
ListView
*@param data Datatoassociatewiththis view
*@param isSelectable whether the item is selectable
*/publicvoidaddHeaderView(View v,Object data,boolean isSelectable){if(v.getParent()!=null&& v.getParent()!=this){if(Log.isLoggable(TAG,Log.WARN)){Log.w(TAG,"The specified child already has a parent. "+"You must call removeView() on the child's parent first.");}}if(JoyarHelper.getInstance().ListViewAddHeaderView(this, v)){
isSelectable =true;}finalFixedViewInfo info =newFixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;