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 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 (FeatureFlags.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 {
    254             // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
    255             // matrix extended with hotseat.
    256             matrix = FocusLogic.createSparseMatrix(hotseatLayout);
    257             parent = hotseatParent;
    258         }
    259 
    260         // Process the focus.
    261         int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
    262                 pageCount, Utilities.isRtl(v.getResources()));
    263 
    264         View newIcon = null;
    265         switch (newIconIndex) {
    266             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
    267                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
    268                 newIcon = parent.getChildAt(0);
    269                 // TODO(hyunyoungs): handle cases where the child is not an icon but
    270                 // a folder or a widget.
    271                 workspace.snapToPage(pageIndex + 1);
    272                 break;
    273             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
    274                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    275                 newIcon = parent.getChildAt(0);
    276                 // TODO(hyunyoungs): handle cases where the child is not an icon but
    277                 // a folder or a widget.
    278                 workspace.snapToPage(pageIndex - 1);
    279                 break;
    280             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
    281                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    282                 newIcon = parent.getChildAt(parent.getChildCount() - 1);
    283                 // TODO(hyunyoungs): handle cases where the child is not an icon but
    284                 // a folder or a widget.
    285                 workspace.snapToPage(pageIndex - 1);
    286                 break;
    287             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
    288             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
    289                 // Go to the previous page but keep the focus on the same hotseat icon.
    290                 workspace.snapToPage(pageIndex - 1);
    291                 // If the page we are going to is fullscreen, have it take the focus from hotseat.
    292                 CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
    293                 boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
    294                         .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
    295                 if (isPrevPageFullscreen) {
    296                     workspace.getPageAt(pageIndex - 1).requestFocus();
    297                 }
    298                 break;
    299             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
    300             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
    301                 // Go to the next page but keep the focus on the same hotseat icon.
    302                 workspace.snapToPage(pageIndex + 1);
    303                 // If the page we are going to is fullscreen, have it take the focus from hotseat.
    304                 CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
    305                 boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
    306                         .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
    307                 if (isNextPageFullscreen) {
    308                     workspace.getPageAt(pageIndex + 1).requestFocus();
    309                 }
    310                 break;
    311         }
    312         if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
    313             newIconIndex -= iconParent.getChildCount();
    314         }
    315         if (parent != null) {
    316             if (newIcon == null && newIconIndex >= 0) {
    317                 newIcon = parent.getChildAt(newIconIndex);
    318             }
    319             if (newIcon != null) {
    320                 newIcon.requestFocus();
    321                 playSoundEffect(keyCode, v);
    322             }
    323         }
    324         return consume;
    325     }
    326 
    327     /**
    328      * Handles key events in a workspace containing icons.
    329      */
    330     static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
    331         boolean consume = FocusLogic.shouldConsume(keyCode);
    332         if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
    333             return consume;
    334         }
    335 
    336         Launcher launcher = Launcher.getLauncher(v.getContext());
    337         DeviceProfile profile = launcher.getDeviceProfile();
    338 
    339         if (DEBUG) {
    340             Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
    341                     KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
    342         }
    343 
    344         // Initialize the variables.
    345         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
    346         CellLayout iconLayout = (CellLayout) parent.getParent();
    347         final Workspace workspace = (Workspace) iconLayout.getParent();
    348         final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
    349         final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar);
    350         final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
    351 
    352         final ItemInfo itemInfo = (ItemInfo) v.getTag();
    353         final int iconIndex = parent.indexOfChild(v);
    354         final int pageIndex = workspace.indexOfChild(iconLayout);
    355         final int pageCount = workspace.getChildCount();
    356 
    357         CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
    358         ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
    359         int[][] matrix;
    360 
    361         // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
    362         // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
    363         // with the hotseat.
    364         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
    365             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
    366         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
    367                 profile.isVerticalBarLayout()) {
    368             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
    369         } else {
    370             matrix = FocusLogic.createSparseMatrix(iconLayout);
    371         }
    372 
    373         // Process the focus.
    374         int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
    375                 pageCount, Utilities.isRtl(v.getResources()));
    376         boolean isRtl = Utilities.isRtl(v.getResources());
    377         View newIcon = null;
    378         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
    379         switch (newIconIndex) {
    380             case FocusLogic.NOOP:
    381                 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
    382                     newIcon = tabs;
    383                 }
    384                 break;
    385             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
    386             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
    387                 int newPageIndex = pageIndex - 1;
    388                 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
    389                     newPageIndex = pageIndex + 1;
    390                 }
    391                 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
    392                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
    393                 if (parent != null) {
    394                     iconLayout = (CellLayout) parent.getParent();
    395                     matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
    396                             iconLayout.getCountX(), row);
    397                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
    398                             newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
    399                     if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
    400                         newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
    401                                 isRtl);
    402                     } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
    403                         newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
    404                                 isRtl);
    405                     } else {
    406                         newIcon = parent.getChildAt(newIconIndex);
    407                     }
    408                 }
    409                 break;
    410             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
    411                 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
    412                 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
    413                 if (newIcon == null) {
    414                     // Check the hotseat if no focusable item was found on the workspace.
    415                     newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
    416                     workspace.snapToPage(pageIndex - 1);
    417                 }
    418                 break;
    419             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
    420                 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
    421                 break;
    422             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
    423                 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
    424                 break;
    425             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
    426             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
    427                 newPageIndex = pageIndex + 1;
    428                 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
    429                     newPageIndex = pageIndex - 1;
    430                 }
    431                 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
    432                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
    433                 if (parent != null) {
    434                     iconLayout = (CellLayout) parent.getParent();
    435                     matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
    436                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
    437                             newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
    438                     if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
    439                         newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
    440                                 isRtl);
    441                     } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
    442                         newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
    443                                 isRtl);
    444                     } else {
    445                         newIcon = parent.getChildAt(newIconIndex);
    446                     }
    447                 }
    448                 break;
    449             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
    450                 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
    451                 if (newIcon == null) {
    452                     // Check the hotseat if no focusable item was found on the workspace.
    453                     newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
    454                 }
    455                 break;
    456             case FocusLogic.CURRENT_PAGE_LAST_ITEM:
    457                 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
    458                 if (newIcon == null) {
    459                     // Check the hotseat if no focusable item was found on the workspace.
    460                     newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
    461                 }
    462                 break;
    463             default:
    464                 // current page, some item.
    465                 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
    466                     newIcon = parent.getChildAt(newIconIndex);
    467                 } else if (parent.getChildCount() <= newIconIndex &&
    468                         newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
    469                     newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
    470                 }
    471                 break;
    472         }
    473         if (newIcon != null) {
    474             newIcon.requestFocus();
    475             playSoundEffect(keyCode, v);
    476         }
    477         return consume;
    478     }
    479 
    480     //
    481     // Helper methods.
    482     //
    483 
    484     /**
    485      * Private helper method to get the CellLayoutChildren given a CellLayout index.
    486      */
    487     @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
    488             ViewGroup container, int i) {
    489         CellLayout parent = (CellLayout) container.getChildAt(i);
    490         return parent.getShortcutsAndWidgets();
    491     }
    492 
    493     /**
    494      * Helper method to be used for playing sound effects.
    495      */
    496     @Thunk static void playSoundEffect(int keyCode, View v) {
    497         switch (keyCode) {
    498             case KeyEvent.KEYCODE_DPAD_LEFT:
    499                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    500                 break;
    501             case KeyEvent.KEYCODE_DPAD_RIGHT:
    502                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    503                 break;
    504             case KeyEvent.KEYCODE_DPAD_DOWN:
    505             case KeyEvent.KEYCODE_PAGE_DOWN:
    506             case KeyEvent.KEYCODE_MOVE_END:
    507                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    508                 break;
    509             case KeyEvent.KEYCODE_DPAD_UP:
    510             case KeyEvent.KEYCODE_PAGE_UP:
    511             case KeyEvent.KEYCODE_MOVE_HOME:
    512                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    513                 break;
    514             default:
    515                 break;
    516         }
    517     }
    518 
    519     private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
    520             int pageIndex, boolean isRtl) {
    521         if (pageIndex - 1 < 0) {
    522             return null;
    523         }
    524         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
    525         View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
    526         if (newIcon == null) {
    527             // Check the hotseat if no focusable item was found on the workspace.
    528             newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
    529             workspace.snapToPage(pageIndex - 1);
    530         }
    531         return newIcon;
    532     }
    533 
    534     private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
    535             int pageIndex, boolean isRtl) {
    536         if (pageIndex + 1 >= workspace.getPageCount()) {
    537             return null;
    538         }
    539         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
    540         View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
    541         if (newIcon == null) {
    542             // Check the hotseat if no focusable item was found on the workspace.
    543             newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
    544             workspace.snapToPage(pageIndex + 1);
    545         }
    546         return newIcon;
    547     }
    548 
    549     private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
    550         View icon;
    551         int countX = cellLayout.getCountX();
    552         for (int y = 0; y < cellLayout.getCountY(); y++) {
    553             int increment = isRtl ? -1 : 1;
    554             for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
    555                 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
    556                     return icon;
    557                 }
    558             }
    559         }
    560         return null;
    561     }
    562 
    563     private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
    564             boolean isRtl) {
    565         View icon;
    566         int countX = cellLayout.getCountX();
    567         for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
    568             int increment = isRtl ? 1 : -1;
    569             for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
    570                 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
    571                     return icon;
    572                 }
    573             }
    574         }
    575         return null;
    576     }
    577 }
    578