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