michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.List; michael@0: michael@0: import org.mozilla.gecko.animation.PropertyAnimator; michael@0: import org.mozilla.gecko.animation.PropertyAnimator.Property; michael@0: import org.mozilla.gecko.animation.ViewHelper; michael@0: import org.mozilla.gecko.widget.TwoWayView; michael@0: import org.mozilla.gecko.widget.TabThumbnailWrapper; michael@0: michael@0: import android.content.Context; michael@0: import android.content.res.TypedArray; michael@0: import android.graphics.Rect; michael@0: import android.graphics.drawable.Drawable; michael@0: import android.util.AttributeSet; michael@0: import android.view.LayoutInflater; michael@0: import android.view.MotionEvent; michael@0: import android.view.VelocityTracker; michael@0: import android.view.View; michael@0: import android.view.ViewConfiguration; michael@0: import android.view.ViewGroup; michael@0: import android.widget.BaseAdapter; michael@0: import android.widget.Button; michael@0: import android.widget.ImageButton; michael@0: import android.widget.ImageView; michael@0: import android.widget.TextView; michael@0: michael@0: public class TabsTray extends TwoWayView michael@0: implements TabsPanel.PanelView { michael@0: private static final String LOGTAG = "GeckoTabsTray"; michael@0: michael@0: private Context mContext; michael@0: private TabsPanel mTabsPanel; michael@0: michael@0: private TabsAdapter mTabsAdapter; michael@0: michael@0: private List mPendingClosedTabs; michael@0: private int mCloseAnimationCount; michael@0: michael@0: private TabSwipeGestureListener mSwipeListener; michael@0: michael@0: // Time to animate non-flinged tabs of screen, in milliseconds michael@0: private static final int ANIMATION_DURATION = 250; michael@0: michael@0: private static final String ABOUT_HOME = "about:home"; michael@0: private int mOriginalSize = 0; michael@0: michael@0: public TabsTray(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: mContext = context; michael@0: michael@0: mCloseAnimationCount = 0; michael@0: mPendingClosedTabs = new ArrayList(); michael@0: michael@0: setItemsCanFocus(true); michael@0: michael@0: TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray); michael@0: boolean isPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1); michael@0: a.recycle(); michael@0: michael@0: mTabsAdapter = new TabsAdapter(mContext, isPrivate); michael@0: setAdapter(mTabsAdapter); michael@0: michael@0: mSwipeListener = new TabSwipeGestureListener(); michael@0: setOnTouchListener(mSwipeListener); michael@0: setOnScrollListener(mSwipeListener.makeScrollListener()); michael@0: michael@0: setRecyclerListener(new RecyclerListener() { michael@0: @Override michael@0: public void onMovedToScrapHeap(View view) { michael@0: TabRow row = (TabRow) view.getTag(); michael@0: row.thumbnail.setImageDrawable(null); michael@0: row.close.setVisibility(View.VISIBLE); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @Override michael@0: public ViewGroup getLayout() { michael@0: return this; michael@0: } michael@0: michael@0: @Override michael@0: public void setTabsPanel(TabsPanel panel) { michael@0: mTabsPanel = panel; michael@0: } michael@0: michael@0: @Override michael@0: public void show() { michael@0: setVisibility(View.VISIBLE); michael@0: Tabs.getInstance().refreshThumbnails(); michael@0: Tabs.registerOnTabsChangedListener(mTabsAdapter); michael@0: mTabsAdapter.refreshTabsData(); michael@0: } michael@0: michael@0: @Override michael@0: public void hide() { michael@0: setVisibility(View.GONE); michael@0: Tabs.unregisterOnTabsChangedListener(mTabsAdapter); michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Screenshot:Cancel","")); michael@0: mTabsAdapter.clear(); michael@0: } michael@0: michael@0: @Override michael@0: public boolean shouldExpand() { michael@0: return isVertical(); michael@0: } michael@0: michael@0: private void autoHidePanel() { michael@0: mTabsPanel.autoHidePanel(); michael@0: } michael@0: michael@0: // ViewHolder for a row in the list michael@0: private class TabRow { michael@0: int id; michael@0: TextView title; michael@0: ImageView thumbnail; michael@0: ImageButton close; michael@0: ViewGroup info; michael@0: TabThumbnailWrapper thumbnailWrapper; michael@0: michael@0: public TabRow(View view) { michael@0: info = (ViewGroup) view; michael@0: title = (TextView) view.findViewById(R.id.title); michael@0: thumbnail = (ImageView) view.findViewById(R.id.thumbnail); michael@0: close = (ImageButton) view.findViewById(R.id.close); michael@0: thumbnailWrapper = (TabThumbnailWrapper) view.findViewById(R.id.wrapper); michael@0: } michael@0: } michael@0: michael@0: // Adapter to bind tabs into a list michael@0: private class TabsAdapter extends BaseAdapter implements Tabs.OnTabsChangedListener { michael@0: private Context mContext; michael@0: private boolean mIsPrivate; michael@0: private ArrayList mTabs; michael@0: private LayoutInflater mInflater; michael@0: private Button.OnClickListener mOnCloseClickListener; michael@0: michael@0: public TabsAdapter(Context context, boolean isPrivate) { michael@0: mContext = context; michael@0: mInflater = LayoutInflater.from(mContext); michael@0: mIsPrivate = isPrivate; michael@0: michael@0: mOnCloseClickListener = new Button.OnClickListener() { michael@0: @Override michael@0: public void onClick(View v) { michael@0: TabRow tab = (TabRow) v.getTag(); michael@0: final int pos = (isVertical() ? tab.info.getWidth() : tab.info.getHeight()); michael@0: animateClose(tab.info, pos); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: @Override michael@0: public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { michael@0: switch (msg) { michael@0: case ADDED: michael@0: // Refresh the list to make sure the new tab is added in the right position. michael@0: refreshTabsData(); michael@0: break; michael@0: michael@0: case CLOSED: michael@0: removeTab(tab); michael@0: break; michael@0: michael@0: case SELECTED: michael@0: // Update the selected position, then fall through... michael@0: updateSelectedPosition(); michael@0: case UNSELECTED: michael@0: // We just need to update the style for the unselected tab... michael@0: case THUMBNAIL: michael@0: case TITLE: michael@0: case RECORDING_CHANGE: michael@0: View view = TabsTray.this.getChildAt(getPositionForTab(tab) - TabsTray.this.getFirstVisiblePosition()); michael@0: if (view == null) michael@0: return; michael@0: michael@0: TabRow row = (TabRow) view.getTag(); michael@0: assignValues(row, tab); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: private void refreshTabsData() { michael@0: // Store a different copy of the tabs, so that we don't have to worry about michael@0: // accidentally updating it on the wrong thread. michael@0: mTabs = new ArrayList(); michael@0: michael@0: Iterable tabs = Tabs.getInstance().getTabsInOrder(); michael@0: for (Tab tab : tabs) { michael@0: if (tab.isPrivate() == mIsPrivate) michael@0: mTabs.add(tab); michael@0: } michael@0: michael@0: notifyDataSetChanged(); // Be sure to call this whenever mTabs changes. michael@0: updateSelectedPosition(); michael@0: } michael@0: michael@0: // Updates the selected position in the list so that it will be scrolled to the right place. michael@0: private void updateSelectedPosition() { michael@0: int selected = getPositionForTab(Tabs.getInstance().getSelectedTab()); michael@0: updateSelectedStyle(selected); michael@0: michael@0: if (selected != -1) { michael@0: TabsTray.this.setSelection(selected); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Updates the selected/unselected style for the tabs. michael@0: * michael@0: * @param selected position of the selected tab michael@0: */ michael@0: private void updateSelectedStyle(int selected) { michael@0: for (int i = 0; i < getCount(); i++) { michael@0: TabsTray.this.setItemChecked(i, (i == selected)); michael@0: } michael@0: } michael@0: michael@0: public void clear() { michael@0: mTabs = null; michael@0: notifyDataSetChanged(); // Be sure to call this whenever mTabs changes. michael@0: } michael@0: michael@0: @Override michael@0: public int getCount() { michael@0: return (mTabs == null ? 0 : mTabs.size()); michael@0: } michael@0: michael@0: @Override michael@0: public Tab getItem(int position) { michael@0: return mTabs.get(position); michael@0: } michael@0: michael@0: @Override michael@0: public long getItemId(int position) { michael@0: return position; michael@0: } michael@0: michael@0: private int getPositionForTab(Tab tab) { michael@0: if (mTabs == null || tab == null) michael@0: return -1; michael@0: michael@0: return mTabs.indexOf(tab); michael@0: } michael@0: michael@0: private void removeTab(Tab tab) { michael@0: if (tab.isPrivate() == mIsPrivate && mTabs != null) { michael@0: mTabs.remove(tab); michael@0: notifyDataSetChanged(); // Be sure to call this whenever mTabs changes. michael@0: michael@0: int selected = getPositionForTab(Tabs.getInstance().getSelectedTab()); michael@0: updateSelectedStyle(selected); michael@0: } michael@0: } michael@0: michael@0: private void assignValues(TabRow row, Tab tab) { michael@0: if (row == null || tab == null) michael@0: return; michael@0: michael@0: row.id = tab.getId(); michael@0: michael@0: Drawable thumbnailImage = tab.getThumbnail(); michael@0: if (thumbnailImage != null) { michael@0: row.thumbnail.setImageDrawable(thumbnailImage); michael@0: } else if (AboutPages.isAboutHome(tab.getURL())) { michael@0: row.thumbnail.setImageResource(R.drawable.abouthome_thumbnail); michael@0: } else { michael@0: row.thumbnail.setImageResource(R.drawable.tab_thumbnail_default); michael@0: } michael@0: if (row.thumbnailWrapper != null) { michael@0: row.thumbnailWrapper.setRecording(tab.isRecording()); michael@0: } michael@0: row.title.setText(tab.getDisplayTitle()); michael@0: row.close.setTag(row); michael@0: } michael@0: michael@0: private void resetTransforms(View view) { michael@0: ViewHelper.setAlpha(view, 1); michael@0: if (mOriginalSize == 0) michael@0: return; michael@0: michael@0: if (isVertical()) { michael@0: ViewHelper.setHeight(view, mOriginalSize); michael@0: ViewHelper.setTranslationX(view, 0); michael@0: } else { michael@0: ViewHelper.setWidth(view, mOriginalSize); michael@0: ViewHelper.setTranslationY(view, 0); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public View getView(int position, View convertView, ViewGroup parent) { michael@0: TabRow row; michael@0: michael@0: if (convertView == null) { michael@0: convertView = mInflater.inflate(R.layout.tabs_row, null); michael@0: row = new TabRow(convertView); michael@0: row.close.setOnClickListener(mOnCloseClickListener); michael@0: convertView.setTag(row); michael@0: } else { michael@0: row = (TabRow) convertView.getTag(); michael@0: // If we're recycling this view, there's a chance it was transformed during michael@0: // the close animation. Remove any of those properties. michael@0: resetTransforms(convertView); michael@0: } michael@0: michael@0: Tab tab = mTabs.get(position); michael@0: assignValues(row, tab); michael@0: michael@0: return convertView; michael@0: } michael@0: } michael@0: michael@0: private boolean isVertical() { michael@0: return (getOrientation().compareTo(TwoWayView.Orientation.VERTICAL) == 0); michael@0: } michael@0: michael@0: private void animateClose(final View view, int pos) { michael@0: PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION); michael@0: animator.attach(view, Property.ALPHA, 0); michael@0: michael@0: if (isVertical()) michael@0: animator.attach(view, Property.TRANSLATION_X, pos); michael@0: else michael@0: animator.attach(view, Property.TRANSLATION_Y, pos); michael@0: michael@0: mCloseAnimationCount++; michael@0: mPendingClosedTabs.add(view); michael@0: michael@0: animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { michael@0: @Override michael@0: public void onPropertyAnimationStart() { } michael@0: @Override michael@0: public void onPropertyAnimationEnd() { michael@0: mCloseAnimationCount--; michael@0: if (mCloseAnimationCount > 0) michael@0: return; michael@0: michael@0: for (View pendingView : mPendingClosedTabs) { michael@0: animateFinishClose(pendingView); michael@0: } michael@0: michael@0: mPendingClosedTabs.clear(); michael@0: } michael@0: }); michael@0: michael@0: if (mTabsAdapter.getCount() == 1) michael@0: autoHidePanel(); michael@0: michael@0: animator.start(); michael@0: } michael@0: michael@0: private void animateFinishClose(final View view) { michael@0: PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION); michael@0: michael@0: final boolean isVertical = isVertical(); michael@0: if (isVertical) michael@0: animator.attach(view, Property.HEIGHT, 1); michael@0: else michael@0: animator.attach(view, Property.WIDTH, 1); michael@0: michael@0: TabRow tab = (TabRow)view.getTag(); michael@0: final int tabId = tab.id; michael@0: // Caching this assumes that all rows are the same height michael@0: if (mOriginalSize == 0) michael@0: mOriginalSize = (isVertical ? view.getHeight() : view.getWidth()); michael@0: michael@0: animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { michael@0: @Override michael@0: public void onPropertyAnimationStart() { } michael@0: @Override michael@0: public void onPropertyAnimationEnd() { michael@0: Tabs tabs = Tabs.getInstance(); michael@0: Tab tab = tabs.getTab(tabId); michael@0: tabs.closeTab(tab); michael@0: } michael@0: }); michael@0: michael@0: animator.start(); michael@0: } michael@0: michael@0: private void animateCancel(final View view) { michael@0: PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION); michael@0: animator.attach(view, Property.ALPHA, 1); michael@0: michael@0: if (isVertical()) michael@0: animator.attach(view, Property.TRANSLATION_X, 0); michael@0: else michael@0: animator.attach(view, Property.TRANSLATION_Y, 0); michael@0: michael@0: michael@0: animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { michael@0: @Override michael@0: public void onPropertyAnimationStart() { } michael@0: @Override michael@0: public void onPropertyAnimationEnd() { michael@0: TabRow tab = (TabRow) view.getTag(); michael@0: tab.close.setVisibility(View.VISIBLE); michael@0: } michael@0: }); michael@0: michael@0: animator.start(); michael@0: } michael@0: michael@0: private class TabSwipeGestureListener implements View.OnTouchListener { michael@0: // same value the stock browser uses for after drag animation velocity in pixels/sec michael@0: // http://androidxref.com/4.0.4/xref/packages/apps/Browser/src/com/android/browser/NavTabScroller.java#61 michael@0: private static final float MIN_VELOCITY = 750; michael@0: michael@0: private int mSwipeThreshold; michael@0: private int mMinFlingVelocity; michael@0: michael@0: private int mMaxFlingVelocity; michael@0: private VelocityTracker mVelocityTracker; michael@0: michael@0: private int mListWidth = 1; michael@0: private int mListHeight = 1; michael@0: michael@0: private View mSwipeView; michael@0: private int mSwipeViewPosition; michael@0: private Runnable mPendingCheckForTap; michael@0: michael@0: private float mSwipeStartX; michael@0: private float mSwipeStartY; michael@0: private boolean mSwiping; michael@0: private boolean mEnabled; michael@0: michael@0: public TabSwipeGestureListener() { michael@0: mSwipeView = null; michael@0: mSwipeViewPosition = TwoWayView.INVALID_POSITION; michael@0: mSwiping = false; michael@0: mEnabled = true; michael@0: michael@0: ViewConfiguration vc = ViewConfiguration.get(TabsTray.this.getContext()); michael@0: mSwipeThreshold = vc.getScaledTouchSlop(); michael@0: mMinFlingVelocity = (int) (getContext().getResources().getDisplayMetrics().density * MIN_VELOCITY); michael@0: mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); michael@0: } michael@0: michael@0: public void setEnabled(boolean enabled) { michael@0: mEnabled = enabled; michael@0: } michael@0: michael@0: public TwoWayView.OnScrollListener makeScrollListener() { michael@0: return new TwoWayView.OnScrollListener() { michael@0: @Override michael@0: public void onScrollStateChanged(TwoWayView twoWayView, int scrollState) { michael@0: setEnabled(scrollState != TwoWayView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); michael@0: } michael@0: michael@0: @Override michael@0: public void onScroll(TwoWayView twoWayView, int i, int i1, int i2) { michael@0: } michael@0: }; michael@0: } michael@0: michael@0: @Override michael@0: public boolean onTouch(View view, MotionEvent e) { michael@0: if (!mEnabled) michael@0: return false; michael@0: michael@0: if (mListWidth < 2 || mListHeight < 2) { michael@0: mListWidth = TabsTray.this.getWidth(); michael@0: mListHeight = TabsTray.this.getHeight(); michael@0: } michael@0: michael@0: switch (e.getActionMasked()) { michael@0: case MotionEvent.ACTION_DOWN: { michael@0: // Check if we should set pressed state on the michael@0: // touched view after a standard delay. michael@0: triggerCheckForTap(); michael@0: michael@0: final float x = e.getRawX(); michael@0: final float y = e.getRawY(); michael@0: michael@0: // Find out which view is being touched michael@0: mSwipeView = findViewAt(x, y); michael@0: michael@0: if (mSwipeView != null) { michael@0: mSwipeStartX = e.getRawX(); michael@0: mSwipeStartY = e.getRawY(); michael@0: mSwipeViewPosition = TabsTray.this.getPositionForView(mSwipeView); michael@0: michael@0: mVelocityTracker = VelocityTracker.obtain(); michael@0: mVelocityTracker.addMovement(e); michael@0: } michael@0: michael@0: view.onTouchEvent(e); michael@0: return true; michael@0: } michael@0: michael@0: case MotionEvent.ACTION_UP: { michael@0: if (mSwipeView == null) michael@0: break; michael@0: michael@0: cancelCheckForTap(); michael@0: mSwipeView.setPressed(false); michael@0: michael@0: if (!mSwiping) { michael@0: TabRow tab = (TabRow) mSwipeView.getTag(); michael@0: Tabs.getInstance().selectTab(tab.id); michael@0: autoHidePanel(); michael@0: break; michael@0: } michael@0: michael@0: mVelocityTracker.addMovement(e); michael@0: mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); michael@0: michael@0: float velocityX = Math.abs(mVelocityTracker.getXVelocity()); michael@0: float velocityY = Math.abs(mVelocityTracker.getYVelocity()); michael@0: michael@0: boolean dismiss = false; michael@0: boolean dismissDirection = false; michael@0: int dismissTranslation = 0; michael@0: michael@0: if (isVertical()) { michael@0: float deltaX = ViewHelper.getTranslationX(mSwipeView); michael@0: michael@0: if (Math.abs(deltaX) > mListWidth / 2) { michael@0: dismiss = true; michael@0: dismissDirection = (deltaX > 0); michael@0: } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity michael@0: && velocityY < velocityX) { michael@0: dismiss = mSwiping && (deltaX * mVelocityTracker.getXVelocity() > 0); michael@0: dismissDirection = (mVelocityTracker.getXVelocity() > 0); michael@0: } michael@0: michael@0: dismissTranslation = (dismissDirection ? mListWidth : -mListWidth); michael@0: } else { michael@0: float deltaY = ViewHelper.getTranslationY(mSwipeView); michael@0: michael@0: if (Math.abs(deltaY) > mListHeight / 2) { michael@0: dismiss = true; michael@0: dismissDirection = (deltaY > 0); michael@0: } else if (mMinFlingVelocity <= velocityY && velocityY <= mMaxFlingVelocity michael@0: && velocityX < velocityY) { michael@0: dismiss = mSwiping && (deltaY * mVelocityTracker.getYVelocity() > 0); michael@0: dismissDirection = (mVelocityTracker.getYVelocity() > 0); michael@0: } michael@0: michael@0: dismissTranslation = (dismissDirection ? mListHeight : -mListHeight); michael@0: } michael@0: michael@0: if (dismiss) michael@0: animateClose(mSwipeView, dismissTranslation); michael@0: else michael@0: animateCancel(mSwipeView); michael@0: michael@0: mVelocityTracker = null; michael@0: mSwipeView = null; michael@0: mSwipeViewPosition = TwoWayView.INVALID_POSITION; michael@0: michael@0: mSwipeStartX = 0; michael@0: mSwipeStartY = 0; michael@0: mSwiping = false; michael@0: michael@0: break; michael@0: } michael@0: michael@0: case MotionEvent.ACTION_MOVE: { michael@0: if (mSwipeView == null) michael@0: break; michael@0: michael@0: mVelocityTracker.addMovement(e); michael@0: michael@0: final boolean isVertical = isVertical(); michael@0: michael@0: float deltaX = e.getRawX() - mSwipeStartX; michael@0: float deltaY = e.getRawY() - mSwipeStartY; michael@0: float delta = (isVertical ? deltaX : deltaY); michael@0: michael@0: boolean isScrollingX = Math.abs(deltaX) > mSwipeThreshold; michael@0: boolean isScrollingY = Math.abs(deltaY) > mSwipeThreshold; michael@0: boolean isSwipingToClose = (isVertical ? isScrollingX : isScrollingY); michael@0: michael@0: // If we're actually swiping, make sure we don't michael@0: // set pressed state on the swiped view. michael@0: if (isScrollingX || isScrollingY) michael@0: cancelCheckForTap(); michael@0: michael@0: if (isSwipingToClose) { michael@0: mSwiping = true; michael@0: TabsTray.this.requestDisallowInterceptTouchEvent(true); michael@0: michael@0: TabRow tab = (TabRow) mSwipeView.getTag(); michael@0: tab.close.setVisibility(View.INVISIBLE); michael@0: michael@0: // Stops listview from highlighting the touched item michael@0: // in the list when swiping. michael@0: MotionEvent cancelEvent = MotionEvent.obtain(e); michael@0: cancelEvent.setAction(MotionEvent.ACTION_CANCEL | michael@0: (e.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); michael@0: TabsTray.this.onTouchEvent(cancelEvent); michael@0: } michael@0: michael@0: if (mSwiping) { michael@0: if (isVertical) michael@0: ViewHelper.setTranslationX(mSwipeView, delta); michael@0: else michael@0: ViewHelper.setTranslationY(mSwipeView, delta); michael@0: michael@0: ViewHelper.setAlpha(mSwipeView, Math.max(0.1f, Math.min(1f, michael@0: 1f - 2f * Math.abs(delta) / (isVertical ? mListWidth : mListHeight)))); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: private View findViewAt(float rawX, float rawY) { michael@0: Rect rect = new Rect(); michael@0: michael@0: int[] listViewCoords = new int[2]; michael@0: TabsTray.this.getLocationOnScreen(listViewCoords); michael@0: michael@0: int x = (int) rawX - listViewCoords[0]; michael@0: int y = (int) rawY - listViewCoords[1]; michael@0: michael@0: for (int i = 0; i < TabsTray.this.getChildCount(); i++) { michael@0: View child = TabsTray.this.getChildAt(i); michael@0: child.getHitRect(rect); michael@0: michael@0: if (rect.contains(x, y)) michael@0: return child; michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: michael@0: private void triggerCheckForTap() { michael@0: if (mPendingCheckForTap == null) michael@0: mPendingCheckForTap = new CheckForTap(); michael@0: michael@0: TabsTray.this.postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); michael@0: } michael@0: michael@0: private void cancelCheckForTap() { michael@0: if (mPendingCheckForTap == null) michael@0: return; michael@0: michael@0: TabsTray.this.removeCallbacks(mPendingCheckForTap); michael@0: } michael@0: michael@0: private class CheckForTap implements Runnable { michael@0: @Override michael@0: public void run() { michael@0: if (!mSwiping && mSwipeView != null && mEnabled) michael@0: mSwipeView.setPressed(true); michael@0: } michael@0: } michael@0: } michael@0: }