Home | History | Annotate | Download | only in util
      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      */
     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 
     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.
    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
    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
    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
    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.
    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