From 38531719636eba7c924e3e71c583ebce2c89a1d0 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Tue, 3 Mar 2015 19:25:16 -0800 Subject: [key event focus] DPAD navigates to the nearest item on next/previous page b/19381790 b/16351792 TL;DR;; Previously, when RIGHT is handled on the right most column of the current page or when LEFT is handled on the left most column, the next icon of focus is next page 'first' icon or the previous page 'last icon'. With this change, the row information is preserved when trying to locate an icon to give focus in the next/previous page. Next CL: long awaited unit tests that capture corner cases for different orientation/ device configuration. Change-Id: I5278bed45275b3e4cb39fb698df35f90bb45a415 --- src/com/android/launcher3/FocusHelper.java | 46 ++++++++++- src/com/android/launcher3/util/FocusLogic.java | 94 +++++++++++++++++++--- .../com/android/launcher3/util/FocusLogicTest.java | 20 ++++- 3 files changed, 148 insertions(+), 12 deletions(-) diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index bdfd7b287..ebb319b0b 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -89,6 +89,7 @@ public class FocusHelper { countX = ((CellLayout) parentLayout).getCountX(); countY = ((CellLayout) parentLayout).getCountY(); } else if (v.getParent() instanceof ViewGroup) { + //TODO(hyunyoungs): figure out when this needs to be called. itemContainer = parentLayout = (ViewGroup) v.getParent(); countX = ((PagedViewGridLayout) parentLayout).getCellCountX(); countY = ((PagedViewGridLayout) parentLayout).getCellCountY(); @@ -102,6 +103,7 @@ public class FocusHelper { final int pageCount = container.getChildCount(); ViewGroup newParent = null; View child = null; + // TODO(hyunyoungs): this matrix is not applicable on the last page. int[][] matrix = FocusLogic.createFullMatrix(countX, countY, true); // Process focus. @@ -111,12 +113,22 @@ public class FocusHelper { return consume; } switch (newIconIndex) { + case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: + newParent = getAppsCustomizePage(container, pageIndex -1); + if (newParent != null) { + int row = FocusLogic.findRow(matrix, iconIndex); + container.snapToPage(pageIndex - 1); + // no need to create a new matrix. + child = newParent.getChildAt(matrix[countX-1][row]); + } + break; case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM: newParent = getAppsCustomizePage(container, pageIndex - 1); if (newParent != null) { container.snapToPage(pageIndex - 1); child = newParent.getChildAt(0); } + break; case FocusLogic.PREVIOUS_PAGE_LAST_ITEM: newParent = getAppsCustomizePage(container, pageIndex - 1); if (newParent != null) { @@ -131,6 +143,14 @@ public class FocusHelper { child = newParent.getChildAt(0); } break; + case FocusLogic.NEXT_PAGE_LEFT_COLUMN: + newParent = getAppsCustomizePage(container, pageIndex + 1); + if (newParent != null) { + container.snapToPage(pageIndex + 1); + int row = FocusLogic.findRow(matrix, iconIndex); + child = newParent.getChildAt(matrix[0][row]); + } + break; case FocusLogic.CURRENT_PAGE_FIRST_ITEM: child = container.getChildAt(0); break; @@ -256,7 +276,7 @@ public class FocusHelper { // Initialize the variables. ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); - final CellLayout iconLayout = (CellLayout) parent.getParent(); + CellLayout iconLayout = (CellLayout) parent.getParent(); final Workspace workspace = (Workspace) iconLayout.getParent(); final ViewGroup launcher = (ViewGroup) workspace.getParent(); final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar); @@ -301,10 +321,23 @@ public class FocusHelper { newIcon = tabs; } break; + case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: + int row = FocusLogic.findRow(matrix, iconIndex); + parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); + if (parent != null) { + iconLayout = (CellLayout) parent.getParent(); + matrix = FocusLogic.createSparseMatrix(iconLayout, orientation, + iconLayout.getCountX(), row); + newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix, + FocusLogic.PIVOT, pageIndex - 1, pageCount); + newIcon = parent.getChildAt(newIconIndex); + } + break; case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM: parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); newIcon = parent.getChildAt(0); workspace.snapToPage(pageIndex - 1); + break; case FocusLogic.PREVIOUS_PAGE_LAST_ITEM: parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); newIcon = parent.getChildAt(parent.getChildCount() - 1); @@ -315,6 +348,17 @@ public class FocusHelper { newIcon = parent.getChildAt(0); workspace.snapToPage(pageIndex + 1); break; + case FocusLogic.NEXT_PAGE_LEFT_COLUMN: + row = FocusLogic.findRow(matrix, iconIndex); + parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); + if (parent != null) { + iconLayout = (CellLayout) parent.getParent(); + matrix = FocusLogic.createSparseMatrix(iconLayout, orientation, -1, row); + newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix, + FocusLogic.PIVOT, pageIndex, pageCount); + newIcon = parent.getChildAt(newIconIndex); + } + break; case FocusLogic.CURRENT_PAGE_FIRST_ITEM: newIcon = parent.getChildAt(0); break; diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java index c0730d93d..0c6bfbf35 100644 --- a/src/com/android/launcher3/util/FocusLogic.java +++ b/src/com/android/launcher3/util/FocusLogic.java @@ -48,16 +48,19 @@ public class FocusLogic { // Item and page index related constant used by {@link #handleKeyEvent}. public static final int NOOP = -1; - public static final int PREVIOUS_PAGE_FIRST_ITEM = -2; - public static final int PREVIOUS_PAGE_LAST_ITEM = -3; + public static final int PREVIOUS_PAGE_RIGHT_COLUMN = -2; + public static final int PREVIOUS_PAGE_FIRST_ITEM = -3; + public static final int PREVIOUS_PAGE_LAST_ITEM = -4; - public static final int CURRENT_PAGE_FIRST_ITEM = -4; - public static final int CURRENT_PAGE_LAST_ITEM = -5; + public static final int CURRENT_PAGE_FIRST_ITEM = -5; + public static final int CURRENT_PAGE_LAST_ITEM = -6; - public static final int NEXT_PAGE_FIRST_ITEM = -6; + public static final int NEXT_PAGE_FIRST_ITEM = -7; + public static final int NEXT_PAGE_LEFT_COLUMN = -8; // Matrix related constant. public static final int EMPTY = -1; + public static final int PIVOT = 100; /** * Returns true only if this utility class handles the key code. @@ -87,13 +90,13 @@ public class FocusLogic { case KeyEvent.KEYCODE_DPAD_LEFT: newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/); if (newIndex == NOOP && pageIndex > 0) { - return PREVIOUS_PAGE_LAST_ITEM; + newIndex = PREVIOUS_PAGE_RIGHT_COLUMN; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/); if (newIndex == NOOP && pageIndex < pageCount - 1) { - return NEXT_PAGE_FIRST_ITEM; + newIndex = NEXT_PAGE_LEFT_COLUMN; } break; case KeyEvent.KEYCODE_DPAD_DOWN: @@ -169,7 +172,7 @@ public class FocusLogic { matrix[cx][cy] = i; } if (DEBUG) { - printMatrix(matrix, m, n); + printMatrix(matrix); } return matrix; } @@ -231,7 +234,47 @@ public class FocusLogic { } } if (DEBUG) { - printMatrix(matrix, m, n); + printMatrix(matrix); + } + return matrix; + } + + /** + * Creates a sparse matrix that merges the icon of previous/next page and last column of + * current page. When left key is triggered on the leftmost column, sparse matrix is created + * that combines previous page matrix and an extra column on the right. Likewise, when right + * key is triggered on the rightmost column, sparse matrix is created that combines this column + * on the 0th column and the next page matrix. + * + * @param pivotX x coordinate of the focused item in the current page + * @param pivotY y coordinate of the focused item in the current page + */ + // TODO: get rid of the dynamic matrix creation + public static int[][] createSparseMatrix(CellLayout iconLayout, int pivotX, int pivotY) { + + ViewGroup iconParent = iconLayout.getShortcutsAndWidgets(); + + int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY(), + false /* set all cell to empty */); + + // Iterate thru the children of the top parent. + for (int i = 0; i < iconParent.getChildCount(); i++) { + int cx = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellX; + int cy = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellY; + if (pivotX < 0) { + matrix[cx - pivotX][cy] = i; + } else { + matrix[cx][cy] = i; + } + } + + if (pivotX < 0) { + matrix[0][pivotY] = PIVOT; + } else { + matrix[pivotX][pivotY] = PIVOT; + } + if (DEBUG) { + printMatrix(matrix); } return matrix; } @@ -407,21 +450,32 @@ public class FocusLogic { return newIconIndex; } + /** + * Only used for debugging. + */ private static String getStringIndex(int index) { switch(index) { case NOOP: return "NOOP"; case PREVIOUS_PAGE_FIRST_ITEM: return "PREVIOUS_PAGE_FIRST"; case PREVIOUS_PAGE_LAST_ITEM: return "PREVIOUS_PAGE_LAST"; + case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN"; case CURRENT_PAGE_FIRST_ITEM: return "CURRENT_PAGE_FIRST"; case CURRENT_PAGE_LAST_ITEM: return "CURRENT_PAGE_LAST"; case NEXT_PAGE_FIRST_ITEM: return "NEXT_PAGE_FIRST"; + case NEXT_PAGE_LEFT_COLUMN: return "NEXT_PAGE_LEFT_COLUMN"; default: return Integer.toString(index); } } - private static void printMatrix(int[][] matrix, int m, int n) { + /** + * Only used for debugging. + */ + private static void printMatrix(int[][] matrix) { Log.v(TAG, "\tprintMap:"); + int m = matrix.length; + int n = matrix[0].length; + for (int j=0; j < n; j++) { String colY = "\t\t"; for (int i=0; i < m; i++) { @@ -430,4 +484,24 @@ public class FocusLogic { Log.v(TAG, colY); } } + + /** + * Figure out the location of the icon. + * + */ + //TODO(hyunyoungs): this helper method should move to CellLayout class while removing the + // dynamic matrix creation all together. + public static int findRow(int[][] matrix, int iconIndex) { + int cntX = matrix.length; + int cntY = matrix[0].length; + + for (int i = 0; i < cntX; i++) { + for (int j = 0; j < cntY; j++) { + if (matrix[i][j] == iconIndex) { + return j; + } + } + } + return -1; + } } diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java index fd2e2a823..ea87014e9 100644 --- a/tests/src/com/android/launcher3/util/FocusLogicTest.java +++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java @@ -18,6 +18,9 @@ package com.android.launcher3; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import android.view.KeyEvent; + +import com.android.launcher3.util.FocusLogic; /** * Tests the {@link FocusLogic} class that handles key event based focus handling. @@ -37,6 +40,21 @@ public final class FocusLogicTest extends AndroidTestCase { } public void testShouldConsume() { - // write tests. + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_UP)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_DOWN)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_HOME)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL)); + assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL)); + } + + public void testCreateSparseMatrix() { + // Either, 1) create a helper method to generate/instantiate all possible cell layout that + // may get created in real world to test this method. OR 2) Move all the matrix + // management routine to celllayout and write tests for them. } } -- cgit v1.2.3