Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2011 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.content.res.Configuration;
     20 import android.view.KeyEvent;
     21 import android.view.SoundEffectConstants;
     22 import android.view.View;
     23 import android.view.ViewGroup;
     24 import android.widget.ScrollView;
     25 
     26 import java.util.ArrayList;
     27 import java.util.Collections;
     28 import java.util.Comparator;
     29 
     30 /**
     31  * A keyboard listener we set on all the workspace icons.
     32  */
     33 class IconKeyEventListener implements View.OnKeyListener {
     34     public boolean onKey(View v, int keyCode, KeyEvent event) {
     35         return FocusHelper.handleIconKeyEvent(v, keyCode, event);
     36     }
     37 }
     38 
     39 /**
     40  * A keyboard listener we set on all the workspace icons.
     41  */
     42 class FolderKeyEventListener implements View.OnKeyListener {
     43     public boolean onKey(View v, int keyCode, KeyEvent event) {
     44         return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
     45     }
     46 }
     47 
     48 /**
     49  * A keyboard listener we set on all the hotseat buttons.
     50  */
     51 class HotseatIconKeyEventListener implements View.OnKeyListener {
     52     public boolean onKey(View v, int keyCode, KeyEvent event) {
     53         final Configuration configuration = v.getResources().getConfiguration();
     54         return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
     55     }
     56 }
     57 
     58 public class FocusHelper {
     59 
     60     /**
     61      * Returns the Viewgroup containing page contents for the page at the index specified.
     62      */
     63     private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
     64         ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
     65         if (page instanceof CellLayout) {
     66             // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
     67             page = ((CellLayout) page).getShortcutsAndWidgets();
     68         }
     69         return page;
     70     }
     71 
     72     /**
     73      * Handles key events in a PageViewCellLayout containing PagedViewIcons.
     74      */
     75     static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
     76         ViewGroup parentLayout;
     77         ViewGroup itemContainer;
     78         int countX;
     79         int countY;
     80         if (v.getParent() instanceof ShortcutAndWidgetContainer) {
     81             itemContainer = (ViewGroup) v.getParent();
     82             parentLayout = (ViewGroup) itemContainer.getParent();
     83             countX = ((CellLayout) parentLayout).getCountX();
     84             countY = ((CellLayout) parentLayout).getCountY();
     85         } else {
     86             itemContainer = parentLayout = (ViewGroup) v.getParent();
     87             countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
     88             countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
     89         }
     90 
     91         // Note we have an extra parent because of the
     92         // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
     93         final PagedView container = (PagedView) parentLayout.getParent();
     94         final int iconIndex = itemContainer.indexOfChild(v);
     95         final int itemCount = itemContainer.getChildCount();
     96         final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
     97         final int pageCount = container.getChildCount();
     98 
     99         final int x = iconIndex % countX;
    100         final int y = iconIndex / countX;
    101 
    102         final int action = e.getAction();
    103         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    104         ViewGroup newParent = null;
    105         // Side pages do not always load synchronously, so check before focusing child siblings
    106         // willy-nilly
    107         View child = null;
    108         boolean wasHandled = false;
    109         switch (keyCode) {
    110             case KeyEvent.KEYCODE_DPAD_LEFT:
    111                 if (handleKeyEvent) {
    112                     // Select the previous icon or the last icon on the previous page
    113                     if (iconIndex > 0) {
    114                         itemContainer.getChildAt(iconIndex - 1).requestFocus();
    115                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    116                     } else {
    117                         if (pageIndex > 0) {
    118                             newParent = getAppsCustomizePage(container, pageIndex - 1);
    119                             if (newParent != null) {
    120                                 container.snapToPage(pageIndex - 1);
    121                                 child = newParent.getChildAt(newParent.getChildCount() - 1);
    122                                 if (child != null) {
    123                                     child.requestFocus();
    124                                     v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    125                                 }
    126                             }
    127                         }
    128                     }
    129                 }
    130                 wasHandled = true;
    131                 break;
    132             case KeyEvent.KEYCODE_DPAD_RIGHT:
    133                 if (handleKeyEvent) {
    134                     // Select the next icon or the first icon on the next page
    135                     if (iconIndex < (itemCount - 1)) {
    136                         itemContainer.getChildAt(iconIndex + 1).requestFocus();
    137                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    138                     } else {
    139                         if (pageIndex < (pageCount - 1)) {
    140                             newParent = getAppsCustomizePage(container, pageIndex + 1);
    141                             if (newParent != null) {
    142                                 container.snapToPage(pageIndex + 1);
    143                                 child = newParent.getChildAt(0);
    144                                 if (child != null) {
    145                                     child.requestFocus();
    146                                     v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    147                                 }
    148                             }
    149                         }
    150                     }
    151                 }
    152                 wasHandled = true;
    153                 break;
    154             case KeyEvent.KEYCODE_DPAD_UP:
    155                 if (handleKeyEvent) {
    156                     // Select the closest icon in the previous row, otherwise select the tab bar
    157                     if (y > 0) {
    158                         int newiconIndex = ((y - 1) * countX) + x;
    159                         itemContainer.getChildAt(newiconIndex).requestFocus();
    160                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    161                     }
    162                 }
    163                 wasHandled = true;
    164                 break;
    165             case KeyEvent.KEYCODE_DPAD_DOWN:
    166                 if (handleKeyEvent) {
    167                     // Select the closest icon in the next row, otherwise do nothing
    168                     if (y < (countY - 1)) {
    169                         int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
    170                         int newIconY = newiconIndex / countX;
    171                         if (newIconY != y) {
    172                             itemContainer.getChildAt(newiconIndex).requestFocus();
    173                             v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    174                         }
    175                     }
    176                 }
    177                 wasHandled = true;
    178                 break;
    179             case KeyEvent.KEYCODE_PAGE_UP:
    180                 if (handleKeyEvent) {
    181                     // Select the first icon on the previous page, or the first icon on this page
    182                     // if there is no previous page
    183                     if (pageIndex > 0) {
    184                         newParent = getAppsCustomizePage(container, pageIndex - 1);
    185                         if (newParent != null) {
    186                             container.snapToPage(pageIndex - 1);
    187                             child = newParent.getChildAt(0);
    188                             if (child != null) {
    189                                 child.requestFocus();
    190                                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    191                             }
    192                         }
    193                     } else {
    194                         itemContainer.getChildAt(0).requestFocus();
    195                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    196                     }
    197                 }
    198                 wasHandled = true;
    199                 break;
    200             case KeyEvent.KEYCODE_PAGE_DOWN:
    201                 if (handleKeyEvent) {
    202                     // Select the first icon on the next page, or the last icon on this page
    203                     // if there is no next page
    204                     if (pageIndex < (pageCount - 1)) {
    205                         newParent = getAppsCustomizePage(container, pageIndex + 1);
    206                         if (newParent != null) {
    207                             container.snapToPage(pageIndex + 1);
    208                             child = newParent.getChildAt(0);
    209                             if (child != null) {
    210                                 child.requestFocus();
    211                                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    212                             }
    213                         }
    214                     } else {
    215                         itemContainer.getChildAt(itemCount - 1).requestFocus();
    216                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    217                     }
    218                 }
    219                 wasHandled = true;
    220                 break;
    221             case KeyEvent.KEYCODE_MOVE_HOME:
    222                 if (handleKeyEvent) {
    223                     // Select the first icon on this page
    224                     itemContainer.getChildAt(0).requestFocus();
    225                     v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    226                 }
    227                 wasHandled = true;
    228                 break;
    229             case KeyEvent.KEYCODE_MOVE_END:
    230                 if (handleKeyEvent) {
    231                     // Select the last icon on this page
    232                     itemContainer.getChildAt(itemCount - 1).requestFocus();
    233                     v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    234                 }
    235                 wasHandled = true;
    236                 break;
    237             default: break;
    238         }
    239         return wasHandled;
    240     }
    241 
    242     /**
    243      * Handles key events in the workspace hotseat (bottom of the screen).
    244      */
    245     static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
    246         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
    247         final CellLayout layout = (CellLayout) parent.getParent();
    248 
    249         // NOTE: currently we don't special case for the phone UI in different
    250         // orientations, even though the hotseat is on the side in landscape mode. This
    251         // is to ensure that accessibility consistency is maintained across rotations.
    252         final int action = e.getAction();
    253         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    254         boolean wasHandled = false;
    255         switch (keyCode) {
    256             case KeyEvent.KEYCODE_DPAD_LEFT:
    257                 if (handleKeyEvent) {
    258                     ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    259                     int myIndex = views.indexOf(v);
    260                     // Select the previous button, otherwise do nothing
    261                     if (myIndex > 0) {
    262                         views.get(myIndex - 1).requestFocus();
    263                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    264                     }
    265                 }
    266                 wasHandled = true;
    267                 break;
    268             case KeyEvent.KEYCODE_DPAD_RIGHT:
    269                 if (handleKeyEvent) {
    270                     ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    271                     int myIndex = views.indexOf(v);
    272                     // Select the next button, otherwise do nothing
    273                     if (myIndex < views.size() - 1) {
    274                         views.get(myIndex + 1).requestFocus();
    275                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    276                     }
    277                 }
    278                 wasHandled = true;
    279                 break;
    280             case KeyEvent.KEYCODE_DPAD_UP:
    281                 if (handleKeyEvent) {
    282                     final Workspace workspace = (Workspace)
    283                             v.getRootView().findViewById(R.id.workspace);
    284                     if (workspace != null) {
    285                         int pageIndex = workspace.getCurrentPage();
    286                         CellLayout topLayout = (CellLayout) workspace.getChildAt(pageIndex);
    287                         ShortcutAndWidgetContainer children = topLayout.getShortcutsAndWidgets();
    288                         final View newIcon = getIconInDirection(layout, children, -1, 1);
    289                         // Select the first bubble text view in the current page of the workspace
    290                         if (newIcon != null) {
    291                             newIcon.requestFocus();
    292                             v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    293                         } else {
    294                             workspace.requestFocus();
    295                         }
    296                     }
    297                 }
    298                 wasHandled = true;
    299                 break;
    300             case KeyEvent.KEYCODE_DPAD_DOWN:
    301                 // Do nothing
    302                 wasHandled = true;
    303                 break;
    304             default: break;
    305         }
    306         return wasHandled;
    307     }
    308 
    309     /**
    310      * Private helper method to get the CellLayoutChildren given a CellLayout index.
    311      */
    312     private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
    313             ViewGroup container, int i) {
    314         CellLayout parent = (CellLayout) container.getChildAt(i);
    315         return parent.getShortcutsAndWidgets();
    316     }
    317 
    318     /**
    319      * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
    320      * from top left to bottom right.
    321      */
    322     private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
    323             ViewGroup parent) {
    324         // First we order each the CellLayout children by their x,y coordinates
    325         final int cellCountX = layout.getCountX();
    326         final int count = parent.getChildCount();
    327         ArrayList<View> views = new ArrayList<View>();
    328         for (int j = 0; j < count; ++j) {
    329             views.add(parent.getChildAt(j));
    330         }
    331         Collections.sort(views, new Comparator<View>() {
    332             @Override
    333             public int compare(View lhs, View rhs) {
    334                 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
    335                 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
    336                 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
    337                 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
    338                 return lvIndex - rvIndex;
    339             }
    340         });
    341         return views;
    342     }
    343     /**
    344      * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
    345      * direction delta.
    346      *
    347      * @param delta either -1 or 1 depending on the direction we want to search
    348      */
    349     private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
    350         // Then we find the next BubbleTextView offset by delta from i
    351         final int count = views.size();
    352         int newI = i + delta;
    353         while (0 <= newI && newI < count) {
    354             View newV = views.get(newI);
    355             if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
    356                 return newV;
    357             }
    358             newI += delta;
    359         }
    360         return null;
    361     }
    362     private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
    363             int delta) {
    364         final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    365         return findIndexOfIcon(views, i, delta);
    366     }
    367     private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
    368             int delta) {
    369         final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    370         return findIndexOfIcon(views, views.indexOf(v), delta);
    371     }
    372     /**
    373      * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
    374      * delta on the next line.
    375      *
    376      * @param delta either -1 or 1 depending on the line and direction we want to search
    377      */
    378     private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
    379             int lineDelta) {
    380         final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    381         final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
    382         final int cellCountY = layout.getCountY();
    383         final int row = lp.cellY;
    384         final int newRow = row + lineDelta;
    385         if (0 <= newRow && newRow < cellCountY) {
    386             float closestDistance = Float.MAX_VALUE;
    387             int closestIndex = -1;
    388             int index = views.indexOf(v);
    389             int endIndex = (lineDelta < 0) ? -1 : views.size();
    390             while (index != endIndex) {
    391                 View newV = views.get(index);
    392                 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
    393                 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
    394                 if (satisfiesRow &&
    395                         (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
    396                     float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
    397                             Math.pow(tmpLp.cellY - lp.cellY, 2));
    398                     if (tmpDistance < closestDistance) {
    399                         closestIndex = index;
    400                         closestDistance = tmpDistance;
    401                     }
    402                 }
    403                 if (index <= endIndex) {
    404                     ++index;
    405                 } else {
    406                     --index;
    407                 }
    408             }
    409             if (closestIndex > -1) {
    410                 return views.get(closestIndex);
    411             }
    412         }
    413         return null;
    414     }
    415 
    416     /**
    417      * Handles key events in a Workspace containing.
    418      */
    419     static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
    420         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
    421         final CellLayout layout = (CellLayout) parent.getParent();
    422         final Workspace workspace = (Workspace) layout.getParent();
    423         final ViewGroup launcher = (ViewGroup) workspace.getParent();
    424         final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
    425         final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
    426         int pageIndex = workspace.indexOfChild(layout);
    427         int pageCount = workspace.getChildCount();
    428 
    429         final int action = e.getAction();
    430         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    431         boolean wasHandled = false;
    432         switch (keyCode) {
    433             case KeyEvent.KEYCODE_DPAD_LEFT:
    434                 if (handleKeyEvent) {
    435                     // Select the previous icon or the last icon on the previous page if possible
    436                     View newIcon = getIconInDirection(layout, parent, v, -1);
    437                     if (newIcon != null) {
    438                         newIcon.requestFocus();
    439                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    440                     } else {
    441                         if (pageIndex > 0) {
    442                             parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    443                             newIcon = getIconInDirection(layout, parent,
    444                                     parent.getChildCount(), -1);
    445                             if (newIcon != null) {
    446                                 newIcon.requestFocus();
    447                             } else {
    448                                 // Snap to the previous page
    449                                 workspace.snapToPage(pageIndex - 1);
    450                             }
    451                             v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    452                         }
    453                     }
    454                 }
    455                 wasHandled = true;
    456                 break;
    457             case KeyEvent.KEYCODE_DPAD_RIGHT:
    458                 if (handleKeyEvent) {
    459                     // Select the next icon or the first icon on the next page if possible
    460                     View newIcon = getIconInDirection(layout, parent, v, 1);
    461                     if (newIcon != null) {
    462                         newIcon.requestFocus();
    463                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    464                     } else {
    465                         if (pageIndex < (pageCount - 1)) {
    466                             parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
    467                             newIcon = getIconInDirection(layout, parent, -1, 1);
    468                             if (newIcon != null) {
    469                                 newIcon.requestFocus();
    470                             } else {
    471                                 // Snap to the next page
    472                                 workspace.snapToPage(pageIndex + 1);
    473                             }
    474                             v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    475                         }
    476                     }
    477                 }
    478                 wasHandled = true;
    479                 break;
    480             case KeyEvent.KEYCODE_DPAD_UP:
    481                 if (handleKeyEvent) {
    482                     // Select the closest icon in the previous line, otherwise select the tab bar
    483                     View newIcon = getClosestIconOnLine(layout, parent, v, -1);
    484                     if (newIcon != null) {
    485                         newIcon.requestFocus();
    486                         wasHandled = true;
    487                     } else {
    488                         tabs.requestFocus();
    489                     }
    490                     v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    491                 }
    492                 break;
    493             case KeyEvent.KEYCODE_DPAD_DOWN:
    494                 if (handleKeyEvent) {
    495                     // Select the closest icon in the next line, otherwise select the button bar
    496                     View newIcon = getClosestIconOnLine(layout, parent, v, 1);
    497                     if (newIcon != null) {
    498                         newIcon.requestFocus();
    499                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    500                         wasHandled = true;
    501                     } else if (hotseat != null) {
    502                         hotseat.requestFocus();
    503                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    504                     }
    505                 }
    506                 break;
    507             case KeyEvent.KEYCODE_PAGE_UP:
    508                 if (handleKeyEvent) {
    509                     // Select the first icon on the previous page or the first icon on this page
    510                     // if there is no previous page
    511                     if (pageIndex > 0) {
    512                         parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    513                         View newIcon = getIconInDirection(layout, parent, -1, 1);
    514                         if (newIcon != null) {
    515                             newIcon.requestFocus();
    516                         } else {
    517                             // Snap to the previous page
    518                             workspace.snapToPage(pageIndex - 1);
    519                         }
    520                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    521                     } else {
    522                         View newIcon = getIconInDirection(layout, parent, -1, 1);
    523                         if (newIcon != null) {
    524                             newIcon.requestFocus();
    525                             v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    526                         }
    527                     }
    528                 }
    529                 wasHandled = true;
    530                 break;
    531             case KeyEvent.KEYCODE_PAGE_DOWN:
    532                 if (handleKeyEvent) {
    533                     // Select the first icon on the next page or the last icon on this page
    534                     // if there is no previous page
    535                     if (pageIndex < (pageCount - 1)) {
    536                         parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
    537                         View newIcon = getIconInDirection(layout, parent, -1, 1);
    538                         if (newIcon != null) {
    539                             newIcon.requestFocus();
    540                         } else {
    541                             // Snap to the next page
    542                             workspace.snapToPage(pageIndex + 1);
    543                         }
    544                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    545                     } else {
    546                         View newIcon = getIconInDirection(layout, parent,
    547                                 parent.getChildCount(), -1);
    548                         if (newIcon != null) {
    549                             newIcon.requestFocus();
    550                             v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    551                         }
    552                     }
    553                 }
    554                 wasHandled = true;
    555                 break;
    556             case KeyEvent.KEYCODE_MOVE_HOME:
    557                 if (handleKeyEvent) {
    558                     // Select the first icon on this page
    559                     View newIcon = getIconInDirection(layout, parent, -1, 1);
    560                     if (newIcon != null) {
    561                         newIcon.requestFocus();
    562                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    563                     }
    564                 }
    565                 wasHandled = true;
    566                 break;
    567             case KeyEvent.KEYCODE_MOVE_END:
    568                 if (handleKeyEvent) {
    569                     // Select the last icon on this page
    570                     View newIcon = getIconInDirection(layout, parent,
    571                             parent.getChildCount(), -1);
    572                     if (newIcon != null) {
    573                         newIcon.requestFocus();
    574                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    575                     }
    576                 }
    577                 wasHandled = true;
    578                 break;
    579             default: break;
    580         }
    581         return wasHandled;
    582     }
    583 
    584     /**
    585      * Handles key events for items in a Folder.
    586      */
    587     static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
    588         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
    589         final CellLayout layout = (CellLayout) parent.getParent();
    590         final ScrollView scrollView = (ScrollView) layout.getParent();
    591         final Folder folder = (Folder) scrollView.getParent();
    592         View title = folder.mFolderName;
    593 
    594         final int action = e.getAction();
    595         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    596         boolean wasHandled = false;
    597         switch (keyCode) {
    598             case KeyEvent.KEYCODE_DPAD_LEFT:
    599                 if (handleKeyEvent) {
    600                     // Select the previous icon
    601                     View newIcon = getIconInDirection(layout, parent, v, -1);
    602                     if (newIcon != null) {
    603                         newIcon.requestFocus();
    604                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
    605                     }
    606                 }
    607                 wasHandled = true;
    608                 break;
    609             case KeyEvent.KEYCODE_DPAD_RIGHT:
    610                 if (handleKeyEvent) {
    611                     // Select the next icon
    612                     View newIcon = getIconInDirection(layout, parent, v, 1);
    613                     if (newIcon != null) {
    614                         newIcon.requestFocus();
    615                     } else {
    616                         title.requestFocus();
    617                     }
    618                     v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
    619                 }
    620                 wasHandled = true;
    621                 break;
    622             case KeyEvent.KEYCODE_DPAD_UP:
    623                 if (handleKeyEvent) {
    624                     // Select the closest icon in the previous line
    625                     View newIcon = getClosestIconOnLine(layout, parent, v, -1);
    626                     if (newIcon != null) {
    627                         newIcon.requestFocus();
    628                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    629                     }
    630                 }
    631                 wasHandled = true;
    632                 break;
    633             case KeyEvent.KEYCODE_DPAD_DOWN:
    634                 if (handleKeyEvent) {
    635                     // Select the closest icon in the next line
    636                     View newIcon = getClosestIconOnLine(layout, parent, v, 1);
    637                     if (newIcon != null) {
    638                         newIcon.requestFocus();
    639                     } else {
    640                         title.requestFocus();
    641                     }
    642                     v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    643                 }
    644                 wasHandled = true;
    645                 break;
    646             case KeyEvent.KEYCODE_MOVE_HOME:
    647                 if (handleKeyEvent) {
    648                     // Select the first icon on this page
    649                     View newIcon = getIconInDirection(layout, parent, -1, 1);
    650                     if (newIcon != null) {
    651                         newIcon.requestFocus();
    652                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
    653                     }
    654                 }
    655                 wasHandled = true;
    656                 break;
    657             case KeyEvent.KEYCODE_MOVE_END:
    658                 if (handleKeyEvent) {
    659                     // Select the last icon on this page
    660                     View newIcon = getIconInDirection(layout, parent,
    661                             parent.getChildCount(), -1);
    662                     if (newIcon != null) {
    663                         newIcon.requestFocus();
    664                         v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
    665                     }
    666                 }
    667                 wasHandled = true;
    668                 break;
    669             default: break;
    670         }
    671         return wasHandled;
    672     }
    673 }
    674