Merge "Fix NotificationCompat.MediaStyle crash on ICS" into lmp-mr1-ub-dev
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 8ccb357..949ce5f 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -273,7 +273,7 @@
if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
// We're set to scroll so add the child's height
- range += childHeight;
+ range += childHeight + lp.topMargin + lp.bottomMargin;
if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
// For a collapsing scroll, we to take the collapsed height into account.
@@ -322,6 +322,8 @@
final int flags = lp.mScrollFlags;
if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) {
+ // First take the margin into account
+ range += lp.topMargin + lp.bottomMargin;
// The view has the quick return flag combination...
if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) {
// If they're set to enter collapsed, use the minimum height
@@ -352,9 +354,10 @@
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int childHeight = ViewCompat.isLaidOut(child)
+ int childHeight = ViewCompat.isLaidOut(child)
? child.getHeight()
: child.getMeasuredHeight();
+ childHeight += lp.topMargin + lp.bottomMargin;
final int flags = lp.mScrollFlags;
@@ -868,10 +871,13 @@
int childScrollableHeight = 0;
final int flags = childLp.getScrollFlags();
if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
- // We're set to scroll so add the child's height
- childScrollableHeight += child.getHeight();
+ // We're set to scroll so add the child's height plus margin
+ childScrollableHeight += child.getHeight() + childLp.topMargin
+ + childLp.bottomMargin;
+
if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
- // For a collapsing scroll, we to take the collapsed height into account.
+ // For a collapsing scroll, we to take the collapsed height
+ // into account.
childScrollableHeight -= ViewCompat.getMinimumHeight(child);
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 6ca37b8..d65233c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -21,6 +21,9 @@
import android.os.Parcelable;
import android.support.v4.util.CircularIntArray;
import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Recycler;
@@ -203,16 +206,10 @@
targetView.requestFocus();
mInSelection = false;
}
- if (needsDispatchChildSelectedOnStop()) {
- dispatchChildSelected();
- }
+ dispatchChildSelected();
super.onStop();
}
- boolean needsDispatchChildSelectedOnStop() {
- return true;
- }
-
@Override
protected void onTargetFound(View targetView,
RecyclerView.State state, Action action) {
@@ -254,18 +251,12 @@
void increasePendingMoves() {
if (mPendingMoves < MAX_PENDING_MOVES) {
mPendingMoves++;
- if (mPendingMoves == 0) {
- dispatchChildSelected();
- }
}
}
void decreasePendingMoves() {
if (mPendingMoves > -MAX_PENDING_MOVES) {
mPendingMoves--;
- if (mPendingMoves == 0) {
- dispatchChildSelected();
- }
}
}
@@ -313,43 +304,7 @@
void consumePendingMovesAfterLayout() {
if (mStaggeredGrid && mPendingMoves != 0) {
// consume pending moves, focus to item on the same row.
- final int focusedRow = mGrid != null && mFocusPosition != NO_POSITION ?
- mGrid.getLocation(mFocusPosition).row : NO_POSITION;
- View newSelected = null;
- for (int i = 0, count = getChildCount(); i < count && mPendingMoves != 0; i++) {
- int index = mPendingMoves > 0 ? i : count - 1 - i;
- final View child = getChildAt(index);
- if (!canScrollTo(child)) {
- continue;
- }
- int position = getPositionByIndex(index);
- Grid.Location loc = mGrid.getLocation(position);
- if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
- if (mFocusPosition == NO_POSITION) {
- mFocusPosition = position;
- mSubFocusPosition = 0;
- newSelected = child;
- } else if ((mPendingMoves > 0 && position > mFocusPosition)
- || (mPendingMoves < 0 && position < mFocusPosition)) {
- mFocusPosition = position;
- mSubFocusPosition = 0;
- if (mPendingMoves > 0) {
- mPendingMoves--;
- } else {
- mPendingMoves++;
- }
- newSelected = child;
- }
- }
- }
- if (newSelected != null && hasFocus()) {
- mInSelection = true;
- newSelected.requestFocus();
- mInSelection = false;
- }
- if (mPendingMoves == 0) {
- dispatchChildSelected();
- }
+ mPendingMoves = processSelectionMoves(true, mPendingMoves);
}
if (mPendingMoves == 0 || (mPendingMoves > 0 && hasCreatedLastItem())
|| (mPendingMoves < 0 && hasCreatedFirstItem())) {
@@ -381,11 +336,6 @@
}
@Override
- boolean needsDispatchChildSelectedOnStop() {
- return mPendingMoves != 0;
- }
-
- @Override
protected void onStop() {
super.onStop();
// if we hit wall, need clear the remaining pending moves.
@@ -1422,8 +1372,7 @@
// avoid lots of childSelected events during a long smooth scrolling and
// increase performance.
if (index == mFocusPosition && subindex == mSubFocusPosition
- && (mPendingMoveSmoothScroller == null
- || mPendingMoveSmoothScroller.mPendingMoves == 0)) {
+ && mPendingMoveSmoothScroller == null) {
dispatchChildSelected();
}
} else if (!mInFastRelayout) {
@@ -3010,4 +2959,140 @@
requestLayout();
if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
}
+
+ @Override
+ public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mOrientation == HORIZONTAL && mGrid != null) {
+ return mGrid.getNumRows();
+ }
+ return super.getRowCountForAccessibility(recycler, state);
+ }
+
+ @Override
+ public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mOrientation == VERTICAL && mGrid != null) {
+ return mGrid.getNumRows();
+ }
+ return super.getColumnCountForAccessibility(recycler, state);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
+ RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
+ ViewGroup.LayoutParams lp = host.getLayoutParams();
+ if (mGrid == null || !(lp instanceof LayoutParams)) {
+ super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
+ return;
+ }
+ LayoutParams glp = (LayoutParams) lp;
+ int position = glp.getViewLayoutPosition();
+ int rowIndex = mGrid.getRowIndex(position);
+ int guessSpanIndex = position / mGrid.getNumRows();
+ if (mOrientation == HORIZONTAL) {
+ info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+ rowIndex, 1, guessSpanIndex, 1, false, false));
+ } else {
+ info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+ guessSpanIndex, 1, rowIndex, 1, false, false));
+ }
+ }
+
+ /*
+ * Leanback widget is different than the default implementation because the "scroll" is driven
+ * by selection change.
+ */
+ @Override
+ public boolean performAccessibilityAction(Recycler recycler, State state, int action,
+ Bundle args) {
+ saveContext(recycler, state);
+ switch (action) {
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
+ // try to focus all the way to the last visible item on the same row.
+ processSelectionMoves(false, -mState.getItemCount());
+ break;
+ case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
+ processSelectionMoves(false, mState.getItemCount());
+ break;
+ }
+ leaveContext();
+ return true;
+ }
+
+ /*
+ * Move mFocusPosition multiple steps on the same row in main direction.
+ * Stops when moves are all consumed or reach first/last visible item.
+ * Returning remaining moves.
+ */
+ private int processSelectionMoves(boolean preventScroll, int moves) {
+ if (mGrid == null) {
+ return moves;
+ }
+ int focusPosition = mFocusPosition;
+ int focusedRow = focusPosition != NO_POSITION ?
+ mGrid.getRowIndex(focusPosition) : NO_POSITION;
+ View newSelected = null;
+ for (int i = 0, count = getChildCount(); i < count && moves != 0; i++) {
+ int index = moves > 0 ? i : count - 1 - i;
+ final View child = getChildAt(index);
+ if (!canScrollTo(child)) {
+ continue;
+ }
+ int position = getPositionByIndex(index);
+ int rowIndex = mGrid.getRowIndex(position);
+ if (focusedRow == NO_POSITION) {
+ focusPosition = position;
+ newSelected = child;
+ focusedRow = rowIndex;
+ } else if (rowIndex == focusedRow) {
+ if ((moves > 0 && position > focusPosition)
+ || (moves < 0 && position < focusPosition)) {
+ focusPosition = position;
+ newSelected = child;
+ if (moves > 0) {
+ moves--;
+ } else {
+ moves++;
+ }
+ }
+ }
+ }
+ if (newSelected != null) {
+ if (preventScroll) {
+ if (hasFocus()) {
+ mInSelection = true;
+ newSelected.requestFocus();
+ mInSelection = false;
+ }
+ mFocusPosition = focusPosition;
+ mSubFocusPosition = 0;
+ } else {
+ scrollToView(newSelected, true);
+ }
+ }
+ return moves;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
+ AccessibilityNodeInfoCompat info) {
+ saveContext(recycler, state);
+ if (mScrollEnabled && !hasCreatedFirstItem()) {
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+ info.setScrollable(true);
+ }
+ if (mScrollEnabled && !hasCreatedLastItem()) {
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+ info.setScrollable(true);
+ }
+ final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo
+ = AccessibilityNodeInfoCompat.CollectionInfoCompat
+ .obtain(getRowCountForAccessibility(recycler, state),
+ getColumnCountForAccessibility(recycler, state),
+ isLayoutHierarchical(recycler, state),
+ getSelectionModeForAccessibility(recycler, state));
+ info.setCollectionInfo(collectionInfo);
+ leaveContext();
+ }
}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
index c4369a0..026ed71 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -25,6 +25,10 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
+
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Parcelable;
@@ -1679,4 +1683,58 @@
assertTrue(v.getTop() < windowSize);
assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin());
}
+
+ public void testAccessibility() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ assertTrue(0 == mGridView.getSelectedPosition());
+
+ final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+ .getCompatAccessibilityDelegate();
+ final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+ }
+ });
+ assertTrue("test sanity", info.isScrollable());
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ delegateCompat.performAccessibilityAction(mGridView,
+ AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
+ }
+ });
+ waitForScrollIdle(mVerifyLayout);
+ int selectedPosition1 = mGridView.getSelectedPosition();
+ assertTrue(0 < selectedPosition1);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+ }
+ });
+ assertTrue("test sanity", info.isScrollable());
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ delegateCompat.performAccessibilityAction(mGridView,
+ AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
+ }
+ });
+ waitForScrollIdle(mVerifyLayout);
+ int selectedPosition2 = mGridView.getSelectedPosition();
+ assertTrue(selectedPosition2 < selectedPosition1);
+ }
+
}