Home | History | Annotate | Download | only in launcher3
      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;
     18 
     19 import android.util.Log;
     20 import android.view.KeyEvent;
     21 import android.view.SoundEffectConstants;
     22 import android.view.View;
     23 import android.view.ViewGroup;
     24 
     25 import com.android.launcher3.config.ProviderConfig;
     26 import com.android.launcher3.folder.Folder;
     27 import com.android.launcher3.folder.FolderPagedView;
     28 import com.android.launcher3.util.FocusLogic;
     29 import com.android.launcher3.util.Thunk;
     30 
     31 /**
     32  * A keyboard listener we set on all the workspace icons.
     33  */
     34 class IconKeyEventListener implements View.OnKeyListener {
     35     @Override
     36     public boolean onKey(View v, int keyCode, KeyEvent event) {
     37         return FocusHelper.handleIconKeyEvent(v, keyCode, event);
     38     }
     39 }
     40 
     41 /**
     42  * A keyboard listener we set on all the hotseat buttons.
     43  */
     44 class HotseatIconKeyEventListener implements View.OnKeyListener {
     45     @Override
     46     public boolean onKey(View v, int keyCode, KeyEvent event) {
     47         return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
     48     }
     49 }
     50 
     51 /**
     52  * A keyboard listener we set on full screen pages (e.g. custom content).
     53  */
     54 class FullscreenKeyEventListener implements View.OnKeyListener {
     55     @Override
     56     public boolean onKey(View v, int keyCode, KeyEvent event) {
     57         if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
     58                 || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
     59             // Handle the key event just like a workspace icon would in these cases. In this case,
     60             // it will basically act as if there is a single icon in the top left (so you could
     61             // think of the fullscreen page as a focusable fullscreen widget).
     62             return FocusHelper.handleIconKeyEvent(v, keyCode, event);
     63         }
     64         return false;
     65     }
     66 }
     67 
     68 public class FocusHelper {
     69 
     70     private static final String TAG = "FocusHelper";
     71     private static final boolean DEBUG = false;
     72 
     73     /**
     74      * Handles key events in paged folder.
     75      */
     76     public static class PagedFolderKeyEventListener implements View.OnKeyListener {
     77 
     78         private final Folder mFolder;
     79 
     80         public PagedFolderKeyEventListener(Folder folder) {
     81             mFolder = folder;
     82         }
     83 
     84         @Override
     85         public boolean onKey(View v, int keyCode, KeyEvent e) {
     86             boolean consume = FocusLogic.shouldConsume(keyCode);
     87             if (e.getAction() == KeyEvent.ACTION_UP) {
     88                 return consume;
     89             }
     90             if (DEBUG) {
     91                 Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
     92                         KeyEvent.keyCodeToString(keyCode)));
     93             }
     94 
     95             if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
     96                 if (ProviderConfig.IS_DOGFOOD_BUILD) {
     97                     throw new IllegalStateException("Parent of the focused item is not supported.");
     98                 } else {
     99                     return false;
    100                 }
    101             }
    102 
    103             // Initialize variables.
    104             final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
    105             final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
    106 
    107             final int iconIndex = itemContainer.indexOfChild(v);
    108             final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
    109 
    110             final int pageIndex = pagedView.indexOfChild(cellLayout);
    111             final int pageCount = pagedView.getPageCount();
    112             final boolean isLayoutRtl = Utilities.isRtl(v.getResources());
    113 
    114             int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
    115             // Process focus.
    116             int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
    117                     pageCount, isLayoutRtl);
    118             if (newIconIndex == FocusLogic.NOOP) {
    119                 handleNoopKey(keyCode, v);
    120                 return consume;
    121             }
    122             ShortcutAndWidgetContainer newParent = null;
    123             View child = null;
    124 
    125             switch (newIconIndex) {
    126                 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
    127                 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
    128                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
    129                     if (newParent != null) {
    130                         int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
    131                         pagedView.snapToPage(pageIndex - 1);
    132                         child = newParent.getChildAt(
    133                                 ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
    134                                     ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
    135                                 row);
    136                     }
    137                     break;
    138                 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
    139                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
    140                     if (newParent != null) {
    141                         pagedView.snapToPage(pageIndex - 1);
    142                         child = newParent.getChildAt(0, 0);
    143                     }
    144                     break;
    145                 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
    146                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
    147                     if (newParent != null) {
    148                         pagedView.snapToPage(pageIndex - 1);
    149                         child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1);
    150                     }
    151                     break;
    152                 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
    153                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
    154                     if (newParent != null) {
    155                         pagedView.snapToPage(pageIndex + 1);
    156                         child = newParent.getChildAt(0, 0);
    157                     }
    158                     break;
    159                 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
    160                 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
    161                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
    162                     if (newParent != null) {
    163                         pagedView.snapToPage(pageIndex + 1);
    164                         child = FocusLogic.getAdjacentChildInNextFolderPage(
    165                                 newParent, v, newIconIndex);
    166                     }
    167                     break;
    168                 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
    169                     child = cellLayout.getChildAt(0, 0);
    170                     break;
    171                 case FocusLogic.CURRENT_PAGE_LAST_ITEM:
    172                     child = pagedView.getLastItem();
    173                     break;
    174                 default: // Go to some item on the current page.
    175                     child = itemContainer.getChildAt(newIconIndex);
    176                     break;
    177             }
    178             if (child != null) {
    179                 child.requestFocus();
    180                 playSoundEffect(keyCode, v);
    181             } else {
    182                 handleNoopKey(keyCode, v);
    183             }
    184             return consume;
    185         }
    186 
    187         public void handleNoopKey(int keyCode, View v) {
    188             if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
    189                 mFolder.mFolderName.requestFocus();
    190                 playSoundEffect(keyCode, v);
    191             }
    192         }
    193     }
    194 
    195     /**
    196      * Handles key events in the workspace hotseat (bottom of the screen).
    197      * <p>Currently we don't special case for the phone UI in different orientations, even though
    198      * the hotseat is on the side in landscape mode. This is to ensure that accessibility
    199      * consistency is maintained across rotations.
    200      */
    201     static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
    202         boolean consume = FocusLogic.shouldConsume(keyCode);
    203         if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
    204             return consume;
    205         }
    206 
    207         final Launcher launcher = Launcher.getLauncher(v.getContext());
    208         final DeviceProfile profile = launcher.getDeviceProfile();
    209 
    210         if (DEBUG) {
    211             Log.v(TAG, String.format(
    212                     "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
    213                     KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
    214         }
    215 
    216         // Initialize the variables.
    217         final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
    218         final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
    219         final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
    220 
    221         final ItemInfo itemInfo = (ItemInfo) v.getTag();
    222         int pageIndex = workspace.getNextPage();
    223         int pageCount = workspace.getChildCount();
    224         int iconIndex = hotseatParent.indexOfChild(v);
    225         int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
    226                 .getChildAt(iconIndex).getLayoutParams()).cellX;
    227 
    228         final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
    229         if (iconLayout == null) {
    230             // This check is to guard against cases where key strokes rushes in when workspace
    231             // child creation/deletion is still in flux. (e.g., during drop or fling
    232             // animation.)
    233             return consume;
    234         }
    235         final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
    236 
    237         ViewGroup parent = null;
    238         int[][] matrix = null;
    239 
    240         if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
    241                 !profile.isVerticalBarLayout()) {
    242             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
    243             iconIndex += iconParent.getChildCount();
    244             parent = iconParent;
    245         } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
    246                 profile.isVerticalBarLayout()) {
    247             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
    248             iconIndex += iconParent.getChildCount();
    249             parent = iconParent;
    250         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
    251                 profile.isVerticalBarLayout()) {
    252             keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
    253         } else if (isUninstallKeyChord(e)) {
    254             matrix = FocusLogic.createSparseMatrix(iconLayout);
    255             if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
    256                 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
    257             }
    258         } else if (isDeleteKeyChord(e)) {
    259             matrix = FocusLogic.createSparseMatrix(iconLayout);
    260             launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
    261         } else {
    262             // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
    263             // matrix extended with hotseat.
    264             matrix = FocusLogic.createSparseMatrix(hotseatLayout);
    265             parent = hotseatParent;
    266         }
    267 
    268         // Process the focus.
    269         int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
    270                 pageCount, Utilities.isRtl(v.getResources()));
    271 
    272         View newIcon = null;
    273         switch (newIconIndex) {
    274             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
    275                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
    276                 newIcon = parent.getChildAt(0);
    277                 // TODO(hyunyoungs): handle cases where the child is not an icon but
    278                 // a folder or a widget.
    279                 workspace.snapToPage(pageIndex + 1);
    280                 break;
    281             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
    282                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    283                 newIcon = parent.getChildAt(0);
    284                 // TODO(hyunyoungs): handle cases where the child is not an icon but
    285                 // a folder or a widget.
    286                 workspace.snapToPage(pageIndex - 1);
    287                 break;
    288             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
    289                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    290                 newIcon = parent.getChildAt(parent.getChildCount() - 1);
    291                 // TODO(hyunyoungs): handle cases where the child is not an icon but
    292                 // a folder or a widget.
    293                 workspace.snapToPage(pageIndex - 1);
    294                 break;
    295             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
    296             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
    297                 // Go to the previous page but keep the focus on the same hotseat icon.
    298                 workspace.snapToPage(pageIndex - 1);
    299                 // If the page we are going to is fullscreen, have it take the focus from hotseat.
    300                 CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
    301                 boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
    302                         .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
    303                 if (isPrevPageFullscreen) {
    304                     workspace.getPageAt(pageIndex - 1).requestFocus();
    305                 }
    306                 break;
    307             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
    308             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
    309                 // Go to the next page but keep the focus on the same hotseat icon.
    310                 workspace.snapToPage(pageIndex + 1);
    311                 // If the page we are going to is fullscreen, have it take the focus from hotseat.
    312                 CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
    313                 boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
    314                         .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
    315                 if (isNextPageFullscreen) {
    316                     workspace.getPageAt(pageIndex + 1).requestFocus();
    317                 }
    318                 break;
    319         }
    320         if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
    321             newIconIndex -= iconParent.getChildCount();
    322         }
    323         if (parent != null) {
    324             if (newIcon == null && newIconIndex >= 0) {
    325                 newIcon = parent.getChildAt(newIconIndex);
    326             }
    327             if (newIcon != null) {
    328                 newIcon.requestFocus();
    329                 playSoundEffect(keyCode, v);
    330             }
    331         }
    332         return consume;
    333     }
    334 
    335     /**
    336      * Handles key events in a workspace containing icons.
    337      */
    338     static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
    339         boolean consume = FocusLogic.shouldConsume(keyCode);
    340         if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
    341             return consume;
    342         }
    343 
    344         Launcher launcher = Launcher.getLauncher(v.getContext());
    345         DeviceProfile profile = launcher.getDeviceProfile();
    346 
    347         if (DEBUG) {
    348             Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
    349                     KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
    350         }
    351 
    352         // Initialize the variables.
    353         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
    354         CellLayout iconLayout = (CellLayout) parent.getParent();
    355         final Workspace workspace = (Workspace) iconLayout.getParent();
    356         final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
    357         final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar);
    358         final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
    359 
    360         final ItemInfo itemInfo = (ItemInfo) v.getTag();
    361         final int iconIndex = parent.indexOfChild(v);
    362         final int pageIndex = workspace.indexOfChild(iconLayout);
    363         final int pageCount = workspace.getChildCount();
    364 
    365         CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
    366         ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
    367         int[][] matrix;
    368 
    369         // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
    370         // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
    371         // with the hotseat.
    372         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
    373             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
    374         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
    375                 profile.isVerticalBarLayout()) {
    376             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
    377         } else if (isUninstallKeyChord(e)) {
    378             matrix = FocusLogic.createSparseMatrix(iconLayout);
    379             if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
    380                 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
    381             }
    382         } else if (isDeleteKeyChord(e)) {
    383             matrix = FocusLogic.createSparseMatrix(iconLayout);
    384             launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
    385         } else {
    386             matrix = FocusLogic.createSparseMatrix(iconLayout);
    387         }
    388 
    389         // Process the focus.
    390         int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
    391                 pageCount, Utilities.isRtl(v.getResources()));
    392         boolean isRtl = Utilities.isRtl(v.getResources());
    393         View newIcon = null;
    394         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
    395         switch (newIconIndex) {
    396             case FocusLogic.NOOP:
    397                 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
    398                     newIcon = tabs;
    399                 }
    400                 break;
    401             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
    402             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
    403                 int newPageIndex = pageIndex - 1;
    404                 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
    405                     newPageIndex = pageIndex + 1;
    406                 }
    407                 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
    408                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
    409                 if (parent != null) {
    410                     iconLayout = (CellLayout) parent.getParent();
    411                     matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
    412                             iconLayout.getCountX(), row);
    413                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
    414                             newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
    415                     if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
    416                         newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
    417                                 isRtl);
    418                     } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
    419                         newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
    420                                 isRtl);
    421                     } else {
    422                         newIcon = parent.getChildAt(newIconIndex);
    423                     }
    424                 }
    425                 break;
    426             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
    427                 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
    428                 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
    429                 if (newIcon == null) {
    430                     // Check the hotseat if no focusable item was found on the workspace.
    431                     newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
    432                     workspace.snapToPage(pageIndex - 1);
    433                 }
    434                 break;
    435             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
    436                 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
    437                 break;
    438             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
    439                 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
    440                 break;
    441             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
    442             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
    443                 newPageIndex = pageIndex + 1;
    444                 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
    445                     newPageIndex = pageIndex - 1;
    446                 }
    447                 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
    448                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
    449                 if (parent != null) {
    450                     iconLayout = (CellLayout) parent.getParent();
    451                     matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
    452                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
    453                             newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
    454                     if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
    455                         newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
    456                                 isRtl);
    457                     } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
    458                         newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
    459                                 isRtl);
    460                     } else {
    461                         newIcon = parent.getChildAt(newIconIndex);
    462                     }
    463                 }
    464                 break;
    465             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
    466                 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
    467                 if (newIcon == null) {
    468                     // Check the hotseat if no focusable item was found on the workspace.
    469                     newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
    470                 }
    471                 break;
    472             case FocusLogic.CURRENT_PAGE_LAST_ITEM:
    473                 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
    474                 if (newIcon == null) {
    475                     // Check the hotseat if no focusable item was found on the workspace.
    476                     newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
    477                 }
    478                 break;
    479             default:
    480                 // current page, some item.
    481                 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
    482                     newIcon = parent.getChildAt(newIconIndex);
    483                 } else if (parent.getChildCount() <= newIconIndex &&
    484                         newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
    485                     newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
    486                 }
    487                 break;
    488         }
    489         if (newIcon != null) {
    490             newIcon.requestFocus();
    491             playSoundEffect(keyCode, v);
    492         }
    493         return consume;
    494     }
    495 
    496     //
    497     // Helper methods.
    498     //
    499 
    500     /**
    501      * Private helper method to get the CellLayoutChildren given a CellLayout index.
    502      */
    503     @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
    504             ViewGroup container, int i) {
    505         CellLayout parent = (CellLayout) container.getChildAt(i);
    506         return parent.getShortcutsAndWidgets();
    507     }
    508 
    509     /**
    510      * Helper method to be used for playing sound effects.
    511      */
    512     @Thunk static void playSoundEffect(int keyCode, View v) {
    513         switch (keyCode) {
    514             case KeyEvent.KEYCODE_DPAD_LEFT:
    515                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    516                 break;
    517             case KeyEvent.KEYCODE_DPAD_RIGHT:
    518                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    519                 break;
    520             case KeyEvent.KEYCODE_DPAD_DOWN:
    521             case KeyEvent.KEYCODE_PAGE_DOWN:
    522             case KeyEvent.KEYCODE_MOVE_END:
    523                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    524                 break;
    525             case KeyEvent.KEYCODE_DPAD_UP:
    526             case KeyEvent.KEYCODE_PAGE_UP:
    527             case KeyEvent.KEYCODE_MOVE_HOME:
    528                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    529                 break;
    530             default:
    531                 break;
    532         }
    533     }
    534 
    535     /**
    536      * Returns whether the key event represents a valid uninstall key chord.
    537      */
    538     private static boolean isUninstallKeyChord(KeyEvent event) {
    539         int keyCode = event.getKeyCode();
    540         return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
    541                 event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
    542     }
    543 
    544     /**
    545      * Returns whether the key event represents a valid delete key chord.
    546      */
    547     private static boolean isDeleteKeyChord(KeyEvent event) {
    548         int keyCode = event.getKeyCode();
    549         return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
    550                 event.hasModifiers(KeyEvent.META_CTRL_ON);
    551     }
    552 
    553     private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
    554             int pageIndex, boolean isRtl) {
    555         if (pageIndex - 1 < 0) {
    556             return null;
    557         }
    558         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
    559         View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
    560         if (newIcon == null) {
    561             // Check the hotseat if no focusable item was found on the workspace.
    562             newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
    563             workspace.snapToPage(pageIndex - 1);
    564         }
    565         return newIcon;
    566     }
    567 
    568     private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
    569             int pageIndex, boolean isRtl) {
    570         if (pageIndex + 1 >= workspace.getPageCount()) {
    571             return null;
    572         }
    573         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
    574         View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
    575         if (newIcon == null) {
    576             // Check the hotseat if no focusable item was found on the workspace.
    577             newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
    578             workspace.snapToPage(pageIndex + 1);
    579         }
    580         return newIcon;
    581     }
    582 
    583     private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
    584         View icon;
    585         int countX = cellLayout.getCountX();
    586         for (int y = 0; y < cellLayout.getCountY(); y++) {
    587             int increment = isRtl ? -1 : 1;
    588             for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
    589                 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
    590                     return icon;
    591                 }
    592             }
    593         }
    594         return null;
    595     }
    596 
    597     private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
    598             boolean isRtl) {
    599         View icon;
    600         int countX = cellLayout.getCountX();
    601         for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
    602             int increment = isRtl ? 1 : -1;
    603             for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
    604                 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
    605                     return icon;
    606                 }
    607             }
    608         }
    609         return null;
    610     }
    611 }
    612