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.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      */
     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 
     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.
    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
    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
    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
    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.
    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