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