• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.util;
18 
19 import android.util.Log;
20 import android.view.KeyEvent;
21 import android.view.View;
22 import android.view.ViewGroup;
23 
24 import com.android.launcher3.CellLayout;
25 import com.android.launcher3.DeviceProfile;
26 import com.android.launcher3.InvariantDeviceProfile;
27 import com.android.launcher3.ShortcutAndWidgetContainer;
28 import com.android.launcher3.config.FeatureFlags;
29 
30 import java.util.Arrays;
31 
32 /**
33  * Calculates the next item that a {@link KeyEvent} should change the focus to.
34  *<p>
35  * Note, this utility class calculates everything regards to icon index and its (x,y) coordinates.
36  * Currently supports:
37  * <ul>
38  *  <li> full matrix of cells that are 1x1
39  *  <li> sparse matrix of cells that are 1x1
40  *     [ 1][  ][ 2][  ]
41  *     [  ][  ][ 3][  ]
42  *     [  ][ 4][  ][  ]
43  *     [  ][ 5][ 6][ 7]
44  * </ul>
45  * *<p>
46  * For testing, one can use a BT keyboard, or use following adb command.
47  * ex. $ adb shell input keyevent 20 // KEYCODE_DPAD_LEFT
48  */
49 public class FocusLogic {
50 
51     private static final String TAG = "FocusLogic";
52     private static final boolean DEBUG = false;
53 
54     /** Item and page index related constant used by {@link #handleKeyEvent}. */
55     public static final int NOOP = -1;
56 
57     public static final int PREVIOUS_PAGE_RIGHT_COLUMN  = -2;
58     public static final int PREVIOUS_PAGE_FIRST_ITEM    = -3;
59     public static final int PREVIOUS_PAGE_LAST_ITEM     = -4;
60     public static final int PREVIOUS_PAGE_LEFT_COLUMN   = -5;
61 
62     public static final int CURRENT_PAGE_FIRST_ITEM     = -6;
63     public static final int CURRENT_PAGE_LAST_ITEM      = -7;
64 
65     public static final int NEXT_PAGE_FIRST_ITEM        = -8;
66     public static final int NEXT_PAGE_LEFT_COLUMN       = -9;
67     public static final int NEXT_PAGE_RIGHT_COLUMN      = -10;
68 
69     public static final int ALL_APPS_COLUMN = -11;
70 
71     // Matrix related constant.
72     public static final int EMPTY = -1;
73     public static final int PIVOT = 100;
74 
75     /**
76      * Returns true only if this utility class handles the key code.
77      */
shouldConsume(int keyCode)78     public static boolean shouldConsume(int keyCode) {
79         return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
80                 keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
81                 keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END ||
82                 keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN ||
83                 keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL);
84     }
85 
handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex, int pageCount, boolean isRtl)86     public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
87             int pageCount, boolean isRtl) {
88 
89         int cntX = map == null ? -1 : map.length;
90         int cntY = map == null ? -1 : map[0].length;
91 
92         if (DEBUG) {
93             Log.v(TAG, String.format(
94                     "handleKeyEvent START: cntX=%d, cntY=%d, iconIdx=%d, pageIdx=%d, pageCnt=%d",
95                     cntX, cntY, iconIdx, pageIndex, pageCount));
96         }
97 
98         int newIndex = NOOP;
99         switch (keyCode) {
100             case KeyEvent.KEYCODE_DPAD_LEFT:
101                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/, isRtl);
102                 if (!isRtl && newIndex == NOOP && pageIndex > 0) {
103                     newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
104                 } else if (isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
105                     newIndex = NEXT_PAGE_RIGHT_COLUMN;
106                 }
107                 break;
108             case KeyEvent.KEYCODE_DPAD_RIGHT:
109                 newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/, isRtl);
110                 if (!isRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
111                     newIndex = NEXT_PAGE_LEFT_COLUMN;
112                 } else if (isRtl && newIndex == NOOP && pageIndex > 0) {
113                     newIndex = PREVIOUS_PAGE_LEFT_COLUMN;
114                 }
115                 break;
116             case KeyEvent.KEYCODE_DPAD_DOWN:
117                 newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, 1  /*increment*/);
118                 break;
119             case KeyEvent.KEYCODE_DPAD_UP:
120                 newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, -1  /*increment*/);
121                 break;
122             case KeyEvent.KEYCODE_MOVE_HOME:
123                 newIndex = handleMoveHome();
124                 break;
125             case KeyEvent.KEYCODE_MOVE_END:
126                 newIndex = handleMoveEnd();
127                 break;
128             case KeyEvent.KEYCODE_PAGE_DOWN:
129                 newIndex = handlePageDown(pageIndex, pageCount);
130                 break;
131             case KeyEvent.KEYCODE_PAGE_UP:
132                 newIndex = handlePageUp(pageIndex);
133                 break;
134             default:
135                 break;
136         }
137 
138         if (DEBUG) {
139             Log.v(TAG, String.format("handleKeyEvent FINISH: index [%d -> %s]",
140                     iconIdx, getStringIndex(newIndex)));
141         }
142         return newIndex;
143     }
144 
145     /**
146      * Returns a matrix of size (m x n) that has been initialized with {@link #EMPTY}.
147      *
148      * @param m                 number of columns in the matrix
149      * @param n                 number of rows in the matrix
150      */
151     // TODO: get rid of dynamic matrix creation.
createFullMatrix(int m, int n)152     private static int[][] createFullMatrix(int m, int n) {
153         int[][] matrix = new int [m][n];
154 
155         for (int i=0; i < m;i++) {
156             Arrays.fill(matrix[i], EMPTY);
157         }
158         return matrix;
159     }
160 
161     /**
162      * Returns a matrix of size same as the {@link CellLayout} dimension that is initialized with the
163      * index of the child view.
164      */
165     // TODO: get rid of the dynamic matrix creation
createSparseMatrix(CellLayout layout)166     public static int[][] createSparseMatrix(CellLayout layout) {
167         ShortcutAndWidgetContainer parent = layout.getShortcutsAndWidgets();
168         final int m = layout.getCountX();
169         final int n = layout.getCountY();
170         final boolean invert = parent.invertLayoutHorizontally();
171 
172         int[][] matrix = createFullMatrix(m, n);
173 
174         // Iterate thru the children.
175         for (int i = 0; i < parent.getChildCount(); i++ ) {
176             View cell = parent.getChildAt(i);
177             if (!cell.isFocusable()) {
178                 continue;
179             }
180             int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
181             int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
182             matrix[invert ? (m - cx - 1) : cx][cy] = i;
183         }
184         if (DEBUG) {
185             printMatrix(matrix);
186         }
187         return matrix;
188     }
189 
190     /**
191      * Creates a sparse matrix that merges the icon and hotseat view group using the cell layout.
192      * The size of the returning matrix is [icon column count x (icon + hotseat row count)]
193      * in portrait orientation. In landscape, [(icon + hotseat) column count x (icon row count)]
194      */
195     // TODO: get rid of the dynamic matrix creation
createSparseMatrixWithHotseat( CellLayout iconLayout, CellLayout hotseatLayout, DeviceProfile dp)196     public static int[][] createSparseMatrixWithHotseat(
197             CellLayout iconLayout, CellLayout hotseatLayout, DeviceProfile dp) {
198 
199         ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
200         ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets();
201 
202         boolean isHotseatHorizontal = !dp.isVerticalBarLayout();
203         boolean moreIconsInHotseatThanWorkspace = !FeatureFlags.NO_ALL_APPS_ICON &&
204                 (isHotseatHorizontal
205                         ? hotseatLayout.getCountX() > iconLayout.getCountX()
206                         : hotseatLayout.getCountY() > iconLayout.getCountY());
207 
208         int m, n;
209         if (isHotseatHorizontal) {
210             m = hotseatLayout.getCountX();
211             n = iconLayout.getCountY() + hotseatLayout.getCountY();
212         } else {
213             m = iconLayout.getCountX() + hotseatLayout.getCountX();
214             n = hotseatLayout.getCountY();
215         }
216         int[][] matrix = createFullMatrix(m, n);
217         if (moreIconsInHotseatThanWorkspace) {
218             int allappsiconRank = dp.inv.getAllAppsButtonRank();
219             if (isHotseatHorizontal) {
220                 for (int j = 0; j < n; j++) {
221                     matrix[allappsiconRank][j] = ALL_APPS_COLUMN;
222                 }
223             } else {
224                 for (int j = 0; j < m; j++) {
225                     matrix[j][allappsiconRank] = ALL_APPS_COLUMN;
226                 }
227             }
228         }
229         // Iterate thru the children of the workspace.
230         for (int i = 0; i < iconParent.getChildCount(); i++) {
231             View cell = iconParent.getChildAt(i);
232             if (!cell.isFocusable()) {
233                 continue;
234             }
235             int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
236             int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
237             if (moreIconsInHotseatThanWorkspace) {
238                 int allappsiconRank = dp.inv.getAllAppsButtonRank();
239                 if (isHotseatHorizontal && cx >= allappsiconRank) {
240                     // Add 1 to account for the All Apps button.
241                     cx++;
242                 }
243                 if (!isHotseatHorizontal && cy >= allappsiconRank) {
244                     // Add 1 to account for the All Apps button.
245                     cy++;
246                 }
247             }
248             matrix[cx][cy] = i;
249         }
250 
251         // Iterate thru the children of the hotseat.
252         for (int i = hotseatParent.getChildCount() - 1; i >= 0; i--) {
253             if (isHotseatHorizontal) {
254                 int cx = ((CellLayout.LayoutParams)
255                         hotseatParent.getChildAt(i).getLayoutParams()).cellX;
256                 matrix[cx][iconLayout.getCountY()] = iconParent.getChildCount() + i;
257             } else {
258                 int cy = ((CellLayout.LayoutParams)
259                         hotseatParent.getChildAt(i).getLayoutParams()).cellY;
260                 matrix[iconLayout.getCountX()][cy] = iconParent.getChildCount() + i;
261             }
262         }
263         if (DEBUG) {
264             printMatrix(matrix);
265         }
266         return matrix;
267     }
268 
269     /**
270      * Creates a sparse matrix that merges the icon of previous/next page and last column of
271      * current page. When left key is triggered on the leftmost column, sparse matrix is created
272      * that combines previous page matrix and an extra column on the right. Likewise, when right
273      * key is triggered on the rightmost column, sparse matrix is created that combines this column
274      * on the 0th column and the next page matrix.
275      *
276      * @param pivotX    x coordinate of the focused item in the current page
277      * @param pivotY    y coordinate of the focused item in the current page
278      */
279     // TODO: get rid of the dynamic matrix creation
createSparseMatrixWithPivotColumn(CellLayout iconLayout, int pivotX, int pivotY)280     public static int[][] createSparseMatrixWithPivotColumn(CellLayout iconLayout,
281             int pivotX, int pivotY) {
282 
283         ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
284 
285         int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY());
286 
287         // Iterate thru the children of the top parent.
288         for (int i = 0; i < iconParent.getChildCount(); i++) {
289             View cell = iconParent.getChildAt(i);
290             if (!cell.isFocusable()) {
291                 continue;
292             }
293             int cx = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellX;
294             int cy = ((CellLayout.LayoutParams) cell.getLayoutParams()).cellY;
295             if (pivotX < 0) {
296                 matrix[cx - pivotX][cy] = i;
297             } else {
298                 matrix[cx][cy] = i;
299             }
300         }
301 
302         if (pivotX < 0) {
303             matrix[0][pivotY] = PIVOT;
304         } else {
305             matrix[pivotX][pivotY] = PIVOT;
306         }
307         if (DEBUG) {
308             printMatrix(matrix);
309         }
310         return matrix;
311     }
312 
313     //
314     // key event handling methods.
315     //
316 
317     /**
318      * Calculates icon that has is closest to the horizontal axis in reference to the cur icon.
319      *
320      * Example of the check order for KEYCODE_DPAD_RIGHT:
321      * [  ][  ][13][14][15]
322      * [  ][ 6][ 8][10][12]
323      * [ X][ 1][ 2][ 3][ 4]
324      * [  ][ 5][ 7][ 9][11]
325      */
326     // TODO: add unit tests to verify all permutation.
handleDpadHorizontal(int iconIdx, int cntX, int cntY, int[][] matrix, int increment, boolean isRtl)327     private static int handleDpadHorizontal(int iconIdx, int cntX, int cntY,
328             int[][] matrix, int increment, boolean isRtl) {
329         if(matrix == null) {
330             throw new IllegalStateException("Dpad navigation requires a matrix.");
331         }
332         int newIconIndex = NOOP;
333 
334         int xPos = -1;
335         int yPos = -1;
336         // Figure out the location of the icon.
337         for (int i = 0; i < cntX; i++) {
338             for (int j = 0; j < cntY; j++) {
339                 if (matrix[i][j] == iconIdx) {
340                     xPos = i;
341                     yPos = j;
342                 }
343             }
344         }
345         if (DEBUG) {
346             Log.v(TAG, String.format("\thandleDpadHorizontal: \t[x, y]=[%d, %d] iconIndex=%d",
347                     xPos, yPos, iconIdx));
348         }
349 
350         // Rule1: check first in the horizontal direction
351         for (int x = xPos + increment; 0 <= x && x < cntX; x += increment) {
352             if ((newIconIndex = inspectMatrix(x, yPos, cntX, cntY, matrix)) != NOOP
353                     && newIconIndex != ALL_APPS_COLUMN) {
354                 return newIconIndex;
355             }
356         }
357 
358         // Rule2: check (x1-n, yPos + increment),   (x1-n, yPos - increment)
359         //              (x2-n, yPos + 2*increment), (x2-n, yPos - 2*increment)
360         int nextYPos1;
361         int nextYPos2;
362         boolean haveCrossedAllAppsColumn1 = false;
363         boolean haveCrossedAllAppsColumn2 = false;
364         int x = -1;
365         for (int coeff = 1; coeff < cntY; coeff++) {
366             nextYPos1 = yPos + coeff * increment;
367             nextYPos2 = yPos - coeff * increment;
368             x = xPos + increment * coeff;
369             if (inspectMatrix(x, nextYPos1, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
370                 haveCrossedAllAppsColumn1 = true;
371             }
372             if (inspectMatrix(x, nextYPos2, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
373                 haveCrossedAllAppsColumn2 = true;
374             }
375             for (; 0 <= x && x < cntX; x += increment) {
376                 int offset1 = haveCrossedAllAppsColumn1 && x < cntX - 1 ? increment : 0;
377                 newIconIndex = inspectMatrix(x, nextYPos1 + offset1, cntX, cntY, matrix);
378                 if (newIconIndex != NOOP) {
379                     return newIconIndex;
380                 }
381                 int offset2 = haveCrossedAllAppsColumn2 && x < cntX - 1 ? -increment : 0;
382                 newIconIndex = inspectMatrix(x, nextYPos2 + offset2, cntX, cntY, matrix);
383                 if (newIconIndex != NOOP) {
384                     return newIconIndex;
385                 }
386             }
387         }
388 
389         // Rule3: if switching between pages, do a brute-force search to find an item that was
390         //        missed by rules 1 and 2 (such as when going from a bottom right icon to top left)
391         if (iconIdx == PIVOT) {
392             if (isRtl) {
393                 return increment < 0 ? NEXT_PAGE_FIRST_ITEM : PREVIOUS_PAGE_LAST_ITEM;
394             }
395             return increment < 0 ? PREVIOUS_PAGE_LAST_ITEM : NEXT_PAGE_FIRST_ITEM;
396         }
397         return newIconIndex;
398     }
399 
400     /**
401      * Calculates icon that is closest to the vertical axis in reference to the current icon.
402      *
403      * Example of the check order for KEYCODE_DPAD_DOWN:
404      * [  ][  ][  ][ X][  ][  ][  ]
405      * [  ][  ][ 5][ 1][ 4][  ][  ]
406      * [  ][10][ 7][ 2][ 6][ 9][  ]
407      * [14][12][ 9][ 3][ 8][11][13]
408      */
409     // TODO: add unit tests to verify all permutation.
410     private static int handleDpadVertical(int iconIndex, int cntX, int cntY,
411             int [][] matrix, int increment) {
412         int newIconIndex = NOOP;
413         if(matrix == null) {
414             throw new IllegalStateException("Dpad navigation requires a matrix.");
415         }
416 
417         int xPos = -1;
418         int yPos = -1;
419         // Figure out the location of the icon.
420         for (int i = 0; i< cntX; i++) {
421             for (int j = 0; j < cntY; j++) {
422                 if (matrix[i][j] == iconIndex) {
423                     xPos = i;
424                     yPos = j;
425                 }
426             }
427         }
428 
429         if (DEBUG) {
430             Log.v(TAG, String.format("\thandleDpadVertical: \t[x, y]=[%d, %d] iconIndex=%d",
431                     xPos, yPos, iconIndex));
432         }
433 
434         // Rule1: check first in the dpad direction
435         for (int y = yPos + increment; 0 <= y && y <cntY && 0 <= y; y += increment) {
436             if ((newIconIndex = inspectMatrix(xPos, y, cntX, cntY, matrix)) != NOOP
437                     && newIconIndex != ALL_APPS_COLUMN) {
438                 return newIconIndex;
439             }
440         }
441 
442         // Rule2: check (xPos + increment, y_(1-n)),   (xPos - increment, y_(1-n))
443         //              (xPos + 2*increment, y_(2-n))), (xPos - 2*increment, y_(2-n))
444         int nextXPos1;
445         int nextXPos2;
446         boolean haveCrossedAllAppsColumn1 = false;
447         boolean haveCrossedAllAppsColumn2 = false;
448         int y = -1;
449         for (int coeff = 1; coeff < cntX; coeff++) {
450             nextXPos1 = xPos + coeff * increment;
451             nextXPos2 = xPos - coeff * increment;
452             y = yPos + increment * coeff;
453             if (inspectMatrix(nextXPos1, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
454                 haveCrossedAllAppsColumn1 = true;
455             }
456             if (inspectMatrix(nextXPos2, y, cntX, cntY, matrix) == ALL_APPS_COLUMN) {
457                 haveCrossedAllAppsColumn2 = true;
458             }
459             for (; 0 <= y && y < cntY; y = y + increment) {
460                 int offset1 = haveCrossedAllAppsColumn1 && y < cntY - 1 ? increment : 0;
461                 newIconIndex = inspectMatrix(nextXPos1 + offset1, y, cntX, cntY, matrix);
462                 if (newIconIndex != NOOP) {
463                     return newIconIndex;
464                 }
465                 int offset2 = haveCrossedAllAppsColumn2 && y < cntY - 1 ? -increment : 0;
466                 newIconIndex = inspectMatrix(nextXPos2 + offset2, y, cntX, cntY, matrix);
467                 if (newIconIndex != NOOP) {
468                     return newIconIndex;
469                 }
470             }
471         }
472         return newIconIndex;
473     }
474 
475     private static int handleMoveHome() {
476         return CURRENT_PAGE_FIRST_ITEM;
477     }
478 
479     private static int handleMoveEnd() {
480         return CURRENT_PAGE_LAST_ITEM;
481     }
482 
483     private static int handlePageDown(int pageIndex, int pageCount) {
484         if (pageIndex < pageCount -1) {
485             return NEXT_PAGE_FIRST_ITEM;
486         }
487         return CURRENT_PAGE_LAST_ITEM;
488     }
489 
490     private static int handlePageUp(int pageIndex) {
491         if (pageIndex > 0) {
492             return PREVIOUS_PAGE_FIRST_ITEM;
493         } else {
494             return CURRENT_PAGE_FIRST_ITEM;
495         }
496     }
497 
498     //
499     // Helper methods.
500     //
501 
502     private static boolean isValid(int xPos, int yPos, int countX, int countY) {
503         return (0 <= xPos && xPos < countX && 0 <= yPos && yPos < countY);
504     }
505 
506     private static int inspectMatrix(int x, int y, int cntX, int cntY, int[][] matrix) {
507         int newIconIndex = NOOP;
508         if (isValid(x, y, cntX, cntY)) {
509             if (matrix[x][y] != -1) {
510                 newIconIndex = matrix[x][y];
511                 if (DEBUG) {
512                     Log.v(TAG, String.format("\t\tinspect: \t[x, y]=[%d, %d] %d",
513                             x, y, matrix[x][y]));
514                 }
515                 return newIconIndex;
516             }
517         }
518         return newIconIndex;
519     }
520 
521     /**
522      * Only used for debugging.
523      */
524     private static String getStringIndex(int index) {
525         switch(index) {
526             case NOOP: return "NOOP";
527             case PREVIOUS_PAGE_FIRST_ITEM:  return "PREVIOUS_PAGE_FIRST";
528             case PREVIOUS_PAGE_LAST_ITEM:   return "PREVIOUS_PAGE_LAST";
529             case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN";
530             case CURRENT_PAGE_FIRST_ITEM:   return "CURRENT_PAGE_FIRST";
531             case CURRENT_PAGE_LAST_ITEM:    return "CURRENT_PAGE_LAST";
532             case NEXT_PAGE_FIRST_ITEM:      return "NEXT_PAGE_FIRST";
533             case NEXT_PAGE_LEFT_COLUMN:     return "NEXT_PAGE_LEFT_COLUMN";
534             case ALL_APPS_COLUMN:           return "ALL_APPS_COLUMN";
535             default:
536                 return Integer.toString(index);
537         }
538     }
539 
540     /**
541      * Only used for debugging.
542      */
543     private static void printMatrix(int[][] matrix) {
544         Log.v(TAG, "\tprintMap:");
545         int m = matrix.length;
546         int n = matrix[0].length;
547 
548         for (int j=0; j < n; j++) {
549             String colY = "\t\t";
550             for (int i=0; i < m; i++) {
551                 colY +=  String.format("%3d",matrix[i][j]);
552             }
553             Log.v(TAG, colY);
554         }
555     }
556 
557     /**
558      * @param edgeColumn the column of the new icon. either {@link #NEXT_PAGE_LEFT_COLUMN} or
559      * {@link #NEXT_PAGE_RIGHT_COLUMN}
560      * @return the view adjacent to {@param oldView} in the {@param nextPage} of the folder.
561      */
562     public static View getAdjacentChildInNextFolderPage(
563             ShortcutAndWidgetContainer nextPage, View oldView, int edgeColumn) {
564         final int newRow = ((CellLayout.LayoutParams) oldView.getLayoutParams()).cellY;
565 
566         int column = (edgeColumn == NEXT_PAGE_LEFT_COLUMN) ^ nextPage.invertLayoutHorizontally()
567                 ? 0 : (((CellLayout) nextPage.getParent()).getCountX() - 1);
568 
569         for (; column >= 0; column--) {
570             for (int row = newRow; row >= 0; row--) {
571                 View newView = nextPage.getChildAt(column, row);
572                 if (newView != null) {
573                     return newView;
574                 }
575             }
576         }
577         return null;
578     }
579 }
580