Home | History | Annotate | Download | only in launcher2
      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.launcher2;
     18 
     19 import android.content.res.Configuration;
     20 import android.view.KeyEvent;
     21 import android.view.View;
     22 import android.view.ViewGroup;
     23 import android.view.ViewParent;
     24 import android.widget.TabHost;
     25 import android.widget.TabWidget;
     26 
     27 import com.android.launcher.R;
     28 
     29 import java.util.ArrayList;
     30 import java.util.Collections;
     31 import java.util.Comparator;
     32 
     33 /**
     34  * A keyboard listener we set on all the workspace icons.
     35  */
     36 class IconKeyEventListener implements View.OnKeyListener {
     37     public boolean onKey(View v, int keyCode, KeyEvent event) {
     38         return FocusHelper.handleIconKeyEvent(v, keyCode, event);
     39     }
     40 }
     41 
     42 /**
     43  * A keyboard listener we set on all the workspace icons.
     44  */
     45 class FolderKeyEventListener implements View.OnKeyListener {
     46     public boolean onKey(View v, int keyCode, KeyEvent event) {
     47         return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
     48     }
     49 }
     50 
     51 /**
     52  * A keyboard listener we set on all the hotseat buttons.
     53  */
     54 class HotseatIconKeyEventListener implements View.OnKeyListener {
     55     public boolean onKey(View v, int keyCode, KeyEvent event) {
     56         final Configuration configuration = v.getResources().getConfiguration();
     57         return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
     58     }
     59 }
     60 
     61 /**
     62  * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
     63  * market icon and vice versa.
     64  */
     65 class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
     66     public boolean onKey(View v, int keyCode, KeyEvent event) {
     67         return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
     68     }
     69 }
     70 
     71 public class FocusHelper {
     72     /**
     73      * Private helper to get the parent TabHost in the view hiearchy.
     74      */
     75     private static TabHost findTabHostParent(View v) {
     76         ViewParent p = v.getParent();
     77         while (p != null && !(p instanceof TabHost)) {
     78             p = p.getParent();
     79         }
     80         return (TabHost) p;
     81     }
     82 
     83     /**
     84      * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
     85      */
     86     static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
     87         final TabHost tabHost = findTabHostParent(v);
     88         final ViewGroup contents = tabHost.getTabContentView();
     89         final View shop = tabHost.findViewById(R.id.market_button);
     90 
     91         final int action = e.getAction();
     92         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
     93         boolean wasHandled = false;
     94         switch (keyCode) {
     95             case KeyEvent.KEYCODE_DPAD_RIGHT:
     96                 if (handleKeyEvent) {
     97                     // Select the shop button if we aren't on it
     98                     if (v != shop) {
     99                         shop.requestFocus();
    100                     }
    101                 }
    102                 wasHandled = true;
    103                 break;
    104             case KeyEvent.KEYCODE_DPAD_DOWN:
    105                 if (handleKeyEvent) {
    106                     // Select the content view (down is handled by the tab key handler otherwise)
    107                     if (v == shop) {
    108                         contents.requestFocus();
    109                         wasHandled = true;
    110                     }
    111                 }
    112                 break;
    113             default: break;
    114         }
    115         return wasHandled;
    116     }
    117 
    118     /**
    119      * Returns the Viewgroup containing page contents for the page at the index specified.
    120      */
    121     private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
    122         ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
    123         if (page instanceof PagedViewCellLayout) {
    124             // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
    125             page = (ViewGroup) page.getChildAt(0);
    126         }
    127         return page;
    128     }
    129 
    130     /**
    131      * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
    132      */
    133     static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
    134             KeyEvent e) {
    135 
    136         final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
    137         final PagedView container = (PagedView) parent.getParent();
    138         final TabHost tabHost = findTabHostParent(container);
    139         final TabWidget tabs = tabHost.getTabWidget();
    140         final int widgetIndex = parent.indexOfChild(w);
    141         final int widgetCount = parent.getChildCount();
    142         final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
    143         final int pageCount = container.getChildCount();
    144         final int cellCountX = parent.getCellCountX();
    145         final int cellCountY = parent.getCellCountY();
    146         final int x = widgetIndex % cellCountX;
    147         final int y = widgetIndex / cellCountX;
    148 
    149         final int action = e.getAction();
    150         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    151         ViewGroup newParent = null;
    152         // Now that we load items in the bg asynchronously, we can't just focus
    153         // child siblings willy-nilly
    154         View child = null;
    155         boolean wasHandled = false;
    156         switch (keyCode) {
    157             case KeyEvent.KEYCODE_DPAD_LEFT:
    158                 if (handleKeyEvent) {
    159                     // Select the previous widget or the last widget on the previous page
    160                     if (widgetIndex > 0) {
    161                         parent.getChildAt(widgetIndex - 1).requestFocus();
    162                     } else {
    163                         if (pageIndex > 0) {
    164                             newParent = getAppsCustomizePage(container, pageIndex - 1);
    165                             if (newParent != null) {
    166                                 child = newParent.getChildAt(newParent.getChildCount() - 1);
    167                                 if (child != null) child.requestFocus();
    168                             }
    169                         }
    170                     }
    171                 }
    172                 wasHandled = true;
    173                 break;
    174             case KeyEvent.KEYCODE_DPAD_RIGHT:
    175                 if (handleKeyEvent) {
    176                     // Select the next widget or the first widget on the next page
    177                     if (widgetIndex < (widgetCount - 1)) {
    178                         parent.getChildAt(widgetIndex + 1).requestFocus();
    179                     } else {
    180                         if (pageIndex < (pageCount - 1)) {
    181                             newParent = getAppsCustomizePage(container, pageIndex + 1);
    182                             if (newParent != null) {
    183                                 child = newParent.getChildAt(0);
    184                                 if (child != null) child.requestFocus();
    185                             }
    186                         }
    187                     }
    188                 }
    189                 wasHandled = true;
    190                 break;
    191             case KeyEvent.KEYCODE_DPAD_UP:
    192                 if (handleKeyEvent) {
    193                     // Select the closest icon in the previous row, otherwise select the tab bar
    194                     if (y > 0) {
    195                         int newWidgetIndex = ((y - 1) * cellCountX) + x;
    196                         child = parent.getChildAt(newWidgetIndex);
    197                         if (child != null) child.requestFocus();
    198                     } else {
    199                         tabs.requestFocus();
    200                     }
    201                 }
    202                 wasHandled = true;
    203                 break;
    204             case KeyEvent.KEYCODE_DPAD_DOWN:
    205                 if (handleKeyEvent) {
    206                     // Select the closest icon in the previous row, otherwise do nothing
    207                     if (y < (cellCountY - 1)) {
    208                         int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
    209                         child = parent.getChildAt(newWidgetIndex);
    210                         if (child != null) child.requestFocus();
    211                     }
    212                 }
    213                 wasHandled = true;
    214                 break;
    215             case KeyEvent.KEYCODE_ENTER:
    216             case KeyEvent.KEYCODE_DPAD_CENTER:
    217                 if (handleKeyEvent) {
    218                     // Simulate a click on the widget
    219                     View.OnClickListener clickListener = (View.OnClickListener) container;
    220                     clickListener.onClick(w);
    221                 }
    222                 wasHandled = true;
    223                 break;
    224             case KeyEvent.KEYCODE_PAGE_UP:
    225                 if (handleKeyEvent) {
    226                     // Select the first item on the previous page, or the first item on this page
    227                     // if there is no previous page
    228                     if (pageIndex > 0) {
    229                         newParent = getAppsCustomizePage(container, pageIndex - 1);
    230                         if (newParent != null) {
    231                             child = newParent.getChildAt(0);
    232                         }
    233                     } else {
    234                         child = parent.getChildAt(0);
    235                     }
    236                     if (child != null) child.requestFocus();
    237                 }
    238                 wasHandled = true;
    239                 break;
    240             case KeyEvent.KEYCODE_PAGE_DOWN:
    241                 if (handleKeyEvent) {
    242                     // Select the first item on the next page, or the last item on this page
    243                     // if there is no next page
    244                     if (pageIndex < (pageCount - 1)) {
    245                         newParent = getAppsCustomizePage(container, pageIndex + 1);
    246                         if (newParent != null) {
    247                             child = newParent.getChildAt(0);
    248                         }
    249                     } else {
    250                         child = parent.getChildAt(widgetCount - 1);
    251                     }
    252                     if (child != null) child.requestFocus();
    253                 }
    254                 wasHandled = true;
    255                 break;
    256             case KeyEvent.KEYCODE_MOVE_HOME:
    257                 if (handleKeyEvent) {
    258                     // Select the first item on this page
    259                     child = parent.getChildAt(0);
    260                     if (child != null) child.requestFocus();
    261                 }
    262                 wasHandled = true;
    263                 break;
    264             case KeyEvent.KEYCODE_MOVE_END:
    265                 if (handleKeyEvent) {
    266                     // Select the last item on this page
    267                     parent.getChildAt(widgetCount - 1).requestFocus();
    268                 }
    269                 wasHandled = true;
    270                 break;
    271             default: break;
    272         }
    273         return wasHandled;
    274     }
    275 
    276     /**
    277      * Handles key events in a PageViewCellLayout containing PagedViewIcons.
    278      */
    279     static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
    280         ViewGroup parentLayout;
    281         ViewGroup itemContainer;
    282         int countX;
    283         int countY;
    284         if (v.getParent() instanceof PagedViewCellLayoutChildren) {
    285             itemContainer = (ViewGroup) v.getParent();
    286             parentLayout = (ViewGroup) itemContainer.getParent();
    287             countX = ((PagedViewCellLayout) parentLayout).getCellCountX();
    288             countY = ((PagedViewCellLayout) parentLayout).getCellCountY();
    289         } else {
    290             itemContainer = parentLayout = (ViewGroup) v.getParent();
    291             countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
    292             countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
    293         }
    294 
    295         // Note we have an extra parent because of the
    296         // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
    297         final PagedView container = (PagedView) parentLayout.getParent();
    298         final TabHost tabHost = findTabHostParent(container);
    299         final TabWidget tabs = tabHost.getTabWidget();
    300         final int iconIndex = itemContainer.indexOfChild(v);
    301         final int itemCount = itemContainer.getChildCount();
    302         final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
    303         final int pageCount = container.getChildCount();
    304 
    305         final int x = iconIndex % countX;
    306         final int y = iconIndex / countX;
    307 
    308         final int action = e.getAction();
    309         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    310         ViewGroup newParent = null;
    311         // Side pages do not always load synchronously, so check before focusing child siblings
    312         // willy-nilly
    313         View child = null;
    314         boolean wasHandled = false;
    315         switch (keyCode) {
    316             case KeyEvent.KEYCODE_DPAD_LEFT:
    317                 if (handleKeyEvent) {
    318                     // Select the previous icon or the last icon on the previous page
    319                     if (iconIndex > 0) {
    320                         itemContainer.getChildAt(iconIndex - 1).requestFocus();
    321                     } else {
    322                         if (pageIndex > 0) {
    323                             newParent = getAppsCustomizePage(container, pageIndex - 1);
    324                             if (newParent != null) {
    325                                 container.snapToPage(pageIndex - 1);
    326                                 child = newParent.getChildAt(newParent.getChildCount() - 1);
    327                                 if (child != null) child.requestFocus();
    328                             }
    329                         }
    330                     }
    331                 }
    332                 wasHandled = true;
    333                 break;
    334             case KeyEvent.KEYCODE_DPAD_RIGHT:
    335                 if (handleKeyEvent) {
    336                     // Select the next icon or the first icon on the next page
    337                     if (iconIndex < (itemCount - 1)) {
    338                         itemContainer.getChildAt(iconIndex + 1).requestFocus();
    339                     } else {
    340                         if (pageIndex < (pageCount - 1)) {
    341                             newParent = getAppsCustomizePage(container, pageIndex + 1);
    342                             if (newParent != null) {
    343                                 container.snapToPage(pageIndex + 1);
    344                                 child = newParent.getChildAt(0);
    345                                 if (child != null) child.requestFocus();
    346                             }
    347                         }
    348                     }
    349                 }
    350                 wasHandled = true;
    351                 break;
    352             case KeyEvent.KEYCODE_DPAD_UP:
    353                 if (handleKeyEvent) {
    354                     // Select the closest icon in the previous row, otherwise select the tab bar
    355                     if (y > 0) {
    356                         int newiconIndex = ((y - 1) * countX) + x;
    357                         itemContainer.getChildAt(newiconIndex).requestFocus();
    358                     } else {
    359                         tabs.requestFocus();
    360                     }
    361                 }
    362                 wasHandled = true;
    363                 break;
    364             case KeyEvent.KEYCODE_DPAD_DOWN:
    365                 if (handleKeyEvent) {
    366                     // Select the closest icon in the previous row, otherwise do nothing
    367                     if (y < (countY - 1)) {
    368                         int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
    369                         itemContainer.getChildAt(newiconIndex).requestFocus();
    370                     }
    371                 }
    372                 wasHandled = true;
    373                 break;
    374             case KeyEvent.KEYCODE_ENTER:
    375             case KeyEvent.KEYCODE_DPAD_CENTER:
    376                 if (handleKeyEvent) {
    377                     // Simulate a click on the icon
    378                     View.OnClickListener clickListener = (View.OnClickListener) container;
    379                     clickListener.onClick(v);
    380                 }
    381                 wasHandled = true;
    382                 break;
    383             case KeyEvent.KEYCODE_PAGE_UP:
    384                 if (handleKeyEvent) {
    385                     // Select the first icon on the previous page, or the first icon on this page
    386                     // if there is no previous page
    387                     if (pageIndex > 0) {
    388                         newParent = getAppsCustomizePage(container, pageIndex - 1);
    389                         if (newParent != null) {
    390                             container.snapToPage(pageIndex - 1);
    391                             child = newParent.getChildAt(0);
    392                             if (child != null) child.requestFocus();
    393                         }
    394                     } else {
    395                         itemContainer.getChildAt(0).requestFocus();
    396                     }
    397                 }
    398                 wasHandled = true;
    399                 break;
    400             case KeyEvent.KEYCODE_PAGE_DOWN:
    401                 if (handleKeyEvent) {
    402                     // Select the first icon on the next page, or the last icon on this page
    403                     // if there is no next page
    404                     if (pageIndex < (pageCount - 1)) {
    405                         newParent = getAppsCustomizePage(container, pageIndex + 1);
    406                         if (newParent != null) {
    407                             container.snapToPage(pageIndex + 1);
    408                             child = newParent.getChildAt(0);
    409                             if (child != null) child.requestFocus();
    410                         }
    411                     } else {
    412                         itemContainer.getChildAt(itemCount - 1).requestFocus();
    413                     }
    414                 }
    415                 wasHandled = true;
    416                 break;
    417             case KeyEvent.KEYCODE_MOVE_HOME:
    418                 if (handleKeyEvent) {
    419                     // Select the first icon on this page
    420                     itemContainer.getChildAt(0).requestFocus();
    421                 }
    422                 wasHandled = true;
    423                 break;
    424             case KeyEvent.KEYCODE_MOVE_END:
    425                 if (handleKeyEvent) {
    426                     // Select the last icon on this page
    427                     itemContainer.getChildAt(itemCount - 1).requestFocus();
    428                 }
    429                 wasHandled = true;
    430                 break;
    431             default: break;
    432         }
    433         return wasHandled;
    434     }
    435 
    436     /**
    437      * Handles key events in the tab widget.
    438      */
    439     static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
    440         if (!LauncherApplication.isScreenLarge()) return false;
    441 
    442         final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
    443         final TabHost tabHost = findTabHostParent(parent);
    444         final ViewGroup contents = tabHost.getTabContentView();
    445         final int tabCount = parent.getTabCount();
    446         final int tabIndex = parent.getChildTabIndex(v);
    447 
    448         final int action = e.getAction();
    449         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    450         boolean wasHandled = false;
    451         switch (keyCode) {
    452             case KeyEvent.KEYCODE_DPAD_LEFT:
    453                 if (handleKeyEvent) {
    454                     // Select the previous tab
    455                     if (tabIndex > 0) {
    456                         parent.getChildTabViewAt(tabIndex - 1).requestFocus();
    457                     }
    458                 }
    459                 wasHandled = true;
    460                 break;
    461             case KeyEvent.KEYCODE_DPAD_RIGHT:
    462                 if (handleKeyEvent) {
    463                     // Select the next tab, or if the last tab has a focus right id, select that
    464                     if (tabIndex < (tabCount - 1)) {
    465                         parent.getChildTabViewAt(tabIndex + 1).requestFocus();
    466                     } else {
    467                         if (v.getNextFocusRightId() != View.NO_ID) {
    468                             tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
    469                         }
    470                     }
    471                 }
    472                 wasHandled = true;
    473                 break;
    474             case KeyEvent.KEYCODE_DPAD_UP:
    475                 // Do nothing
    476                 wasHandled = true;
    477                 break;
    478             case KeyEvent.KEYCODE_DPAD_DOWN:
    479                 if (handleKeyEvent) {
    480                     // Select the content view
    481                     contents.requestFocus();
    482                 }
    483                 wasHandled = true;
    484                 break;
    485             default: break;
    486         }
    487         return wasHandled;
    488     }
    489 
    490     /**
    491      * Handles key events in the workspace hotseat (bottom of the screen).
    492      */
    493     static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
    494         final ViewGroup parent = (ViewGroup) v.getParent();
    495         final ViewGroup launcher = (ViewGroup) parent.getParent();
    496         final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
    497         final int buttonIndex = parent.indexOfChild(v);
    498         final int buttonCount = parent.getChildCount();
    499         final int pageIndex = workspace.getCurrentPage();
    500 
    501         // NOTE: currently we don't special case for the phone UI in different
    502         // orientations, even though the hotseat is on the side in landscape mode.  This
    503         // is to ensure that accessibility consistency is maintained across rotations.
    504 
    505         final int action = e.getAction();
    506         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    507         boolean wasHandled = false;
    508         switch (keyCode) {
    509             case KeyEvent.KEYCODE_DPAD_LEFT:
    510                 if (handleKeyEvent) {
    511                     // Select the previous button, otherwise snap to the previous page
    512                     if (buttonIndex > 0) {
    513                         parent.getChildAt(buttonIndex - 1).requestFocus();
    514                     } else {
    515                         workspace.snapToPage(pageIndex - 1);
    516                     }
    517                 }
    518                 wasHandled = true;
    519                 break;
    520             case KeyEvent.KEYCODE_DPAD_RIGHT:
    521                 if (handleKeyEvent) {
    522                     // Select the next button, otherwise snap to the next page
    523                     if (buttonIndex < (buttonCount - 1)) {
    524                         parent.getChildAt(buttonIndex + 1).requestFocus();
    525                     } else {
    526                         workspace.snapToPage(pageIndex + 1);
    527                     }
    528                 }
    529                 wasHandled = true;
    530                 break;
    531             case KeyEvent.KEYCODE_DPAD_UP:
    532                 if (handleKeyEvent) {
    533                     // Select the first bubble text view in the current page of the workspace
    534                     final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
    535                     final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
    536                     final View newIcon = getIconInDirection(layout, children, -1, 1);
    537                     if (newIcon != null) {
    538                         newIcon.requestFocus();
    539                     } else {
    540                         workspace.requestFocus();
    541                     }
    542                 }
    543                 wasHandled = true;
    544                 break;
    545             case KeyEvent.KEYCODE_DPAD_DOWN:
    546                 // Do nothing
    547                 wasHandled = true;
    548                 break;
    549             default: break;
    550         }
    551         return wasHandled;
    552     }
    553 
    554     /**
    555      * Private helper method to get the CellLayoutChildren given a CellLayout index.
    556      */
    557     private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
    558             ViewGroup container, int i) {
    559         ViewGroup parent = (ViewGroup) container.getChildAt(i);
    560         return (ShortcutAndWidgetContainer) parent.getChildAt(0);
    561     }
    562 
    563     /**
    564      * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
    565      * from top left to bottom right.
    566      */
    567     private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
    568             ViewGroup parent) {
    569         // First we order each the CellLayout children by their x,y coordinates
    570         final int cellCountX = layout.getCountX();
    571         final int count = parent.getChildCount();
    572         ArrayList<View> views = new ArrayList<View>();
    573         for (int j = 0; j < count; ++j) {
    574             views.add(parent.getChildAt(j));
    575         }
    576         Collections.sort(views, new Comparator<View>() {
    577             @Override
    578             public int compare(View lhs, View rhs) {
    579                 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
    580                 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
    581                 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
    582                 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
    583                 return lvIndex - rvIndex;
    584             }
    585         });
    586         return views;
    587     }
    588     /**
    589      * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
    590      * direction delta.
    591      *
    592      * @param delta either -1 or 1 depending on the direction we want to search
    593      */
    594     private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
    595         // Then we find the next BubbleTextView offset by delta from i
    596         final int count = views.size();
    597         int newI = i + delta;
    598         while (0 <= newI && newI < count) {
    599             View newV = views.get(newI);
    600             if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
    601                 return newV;
    602             }
    603             newI += delta;
    604         }
    605         return null;
    606     }
    607     private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
    608             int delta) {
    609         final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    610         return findIndexOfIcon(views, i, delta);
    611     }
    612     private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
    613             int delta) {
    614         final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    615         return findIndexOfIcon(views, views.indexOf(v), delta);
    616     }
    617     /**
    618      * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
    619      * delta on the next line.
    620      *
    621      * @param delta either -1 or 1 depending on the line and direction we want to search
    622      */
    623     private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
    624             int lineDelta) {
    625         final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
    626         final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
    627         final int cellCountY = layout.getCountY();
    628         final int row = lp.cellY;
    629         final int newRow = row + lineDelta;
    630         if (0 <= newRow && newRow < cellCountY) {
    631             float closestDistance = Float.MAX_VALUE;
    632             int closestIndex = -1;
    633             int index = views.indexOf(v);
    634             int endIndex = (lineDelta < 0) ? -1 : views.size();
    635             while (index != endIndex) {
    636                 View newV = views.get(index);
    637                 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
    638                 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
    639                 if (satisfiesRow &&
    640                         (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
    641                     float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
    642                             Math.pow(tmpLp.cellY - lp.cellY, 2));
    643                     if (tmpDistance < closestDistance) {
    644                         closestIndex = index;
    645                         closestDistance = tmpDistance;
    646                     }
    647                 }
    648                 if (index <= endIndex) {
    649                     ++index;
    650                 } else {
    651                     --index;
    652                 }
    653             }
    654             if (closestIndex > -1) {
    655                 return views.get(closestIndex);
    656             }
    657         }
    658         return null;
    659     }
    660 
    661     /**
    662      * Handles key events in a Workspace containing.
    663      */
    664     static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
    665         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
    666         final CellLayout layout = (CellLayout) parent.getParent();
    667         final Workspace workspace = (Workspace) layout.getParent();
    668         final ViewGroup launcher = (ViewGroup) workspace.getParent();
    669         final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
    670         final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
    671         int pageIndex = workspace.indexOfChild(layout);
    672         int pageCount = workspace.getChildCount();
    673 
    674         final int action = e.getAction();
    675         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    676         boolean wasHandled = false;
    677         switch (keyCode) {
    678             case KeyEvent.KEYCODE_DPAD_LEFT:
    679                 if (handleKeyEvent) {
    680                     // Select the previous icon or the last icon on the previous page if possible
    681                     View newIcon = getIconInDirection(layout, parent, v, -1);
    682                     if (newIcon != null) {
    683                         newIcon.requestFocus();
    684                     } else {
    685                         if (pageIndex > 0) {
    686                             parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    687                             newIcon = getIconInDirection(layout, parent,
    688                                     parent.getChildCount(), -1);
    689                             if (newIcon != null) {
    690                                 newIcon.requestFocus();
    691                             } else {
    692                                 // Snap to the previous page
    693                                 workspace.snapToPage(pageIndex - 1);
    694                             }
    695                         }
    696                     }
    697                 }
    698                 wasHandled = true;
    699                 break;
    700             case KeyEvent.KEYCODE_DPAD_RIGHT:
    701                 if (handleKeyEvent) {
    702                     // Select the next icon or the first icon on the next page if possible
    703                     View newIcon = getIconInDirection(layout, parent, v, 1);
    704                     if (newIcon != null) {
    705                         newIcon.requestFocus();
    706                     } else {
    707                         if (pageIndex < (pageCount - 1)) {
    708                             parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
    709                             newIcon = getIconInDirection(layout, parent, -1, 1);
    710                             if (newIcon != null) {
    711                                 newIcon.requestFocus();
    712                             } else {
    713                                 // Snap to the next page
    714                                 workspace.snapToPage(pageIndex + 1);
    715                             }
    716                         }
    717                     }
    718                 }
    719                 wasHandled = true;
    720                 break;
    721             case KeyEvent.KEYCODE_DPAD_UP:
    722                 if (handleKeyEvent) {
    723                     // Select the closest icon in the previous line, otherwise select the tab bar
    724                     View newIcon = getClosestIconOnLine(layout, parent, v, -1);
    725                     if (newIcon != null) {
    726                         newIcon.requestFocus();
    727                         wasHandled = true;
    728                     } else {
    729                         tabs.requestFocus();
    730                     }
    731                 }
    732                 break;
    733             case KeyEvent.KEYCODE_DPAD_DOWN:
    734                 if (handleKeyEvent) {
    735                     // Select the closest icon in the next line, otherwise select the button bar
    736                     View newIcon = getClosestIconOnLine(layout, parent, v, 1);
    737                     if (newIcon != null) {
    738                         newIcon.requestFocus();
    739                         wasHandled = true;
    740                     } else if (hotseat != null) {
    741                         hotseat.requestFocus();
    742                     }
    743                 }
    744                 break;
    745             case KeyEvent.KEYCODE_PAGE_UP:
    746                 if (handleKeyEvent) {
    747                     // Select the first icon on the previous page or the first icon on this page
    748                     // if there is no previous page
    749                     if (pageIndex > 0) {
    750                         parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
    751                         View newIcon = getIconInDirection(layout, parent, -1, 1);
    752                         if (newIcon != null) {
    753                             newIcon.requestFocus();
    754                         } else {
    755                             // Snap to the previous page
    756                             workspace.snapToPage(pageIndex - 1);
    757                         }
    758                     } else {
    759                         View newIcon = getIconInDirection(layout, parent, -1, 1);
    760                         if (newIcon != null) {
    761                             newIcon.requestFocus();
    762                         }
    763                     }
    764                 }
    765                 wasHandled = true;
    766                 break;
    767             case KeyEvent.KEYCODE_PAGE_DOWN:
    768                 if (handleKeyEvent) {
    769                     // Select the first icon on the next page or the last icon on this page
    770                     // if there is no previous page
    771                     if (pageIndex < (pageCount - 1)) {
    772                         parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
    773                         View newIcon = getIconInDirection(layout, parent, -1, 1);
    774                         if (newIcon != null) {
    775                             newIcon.requestFocus();
    776                         } else {
    777                             // Snap to the next page
    778                             workspace.snapToPage(pageIndex + 1);
    779                         }
    780                     } else {
    781                         View newIcon = getIconInDirection(layout, parent,
    782                                 parent.getChildCount(), -1);
    783                         if (newIcon != null) {
    784                             newIcon.requestFocus();
    785                         }
    786                     }
    787                 }
    788                 wasHandled = true;
    789                 break;
    790             case KeyEvent.KEYCODE_MOVE_HOME:
    791                 if (handleKeyEvent) {
    792                     // Select the first icon on this page
    793                     View newIcon = getIconInDirection(layout, parent, -1, 1);
    794                     if (newIcon != null) {
    795                         newIcon.requestFocus();
    796                     }
    797                 }
    798                 wasHandled = true;
    799                 break;
    800             case KeyEvent.KEYCODE_MOVE_END:
    801                 if (handleKeyEvent) {
    802                     // Select the last icon on this page
    803                     View newIcon = getIconInDirection(layout, parent,
    804                             parent.getChildCount(), -1);
    805                     if (newIcon != null) {
    806                         newIcon.requestFocus();
    807                     }
    808                 }
    809                 wasHandled = true;
    810                 break;
    811             default: break;
    812         }
    813         return wasHandled;
    814     }
    815 
    816     /**
    817      * Handles key events for items in a Folder.
    818      */
    819     static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
    820         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
    821         final CellLayout layout = (CellLayout) parent.getParent();
    822         final Folder folder = (Folder) layout.getParent();
    823         View title = folder.mFolderName;
    824 
    825         final int action = e.getAction();
    826         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
    827         boolean wasHandled = false;
    828         switch (keyCode) {
    829             case KeyEvent.KEYCODE_DPAD_LEFT:
    830                 if (handleKeyEvent) {
    831                     // Select the previous icon
    832                     View newIcon = getIconInDirection(layout, parent, v, -1);
    833                     if (newIcon != null) {
    834                         newIcon.requestFocus();
    835                     }
    836                 }
    837                 wasHandled = true;
    838                 break;
    839             case KeyEvent.KEYCODE_DPAD_RIGHT:
    840                 if (handleKeyEvent) {
    841                     // Select the next icon
    842                     View newIcon = getIconInDirection(layout, parent, v, 1);
    843                     if (newIcon != null) {
    844                         newIcon.requestFocus();
    845                     } else {
    846                         title.requestFocus();
    847                     }
    848                 }
    849                 wasHandled = true;
    850                 break;
    851             case KeyEvent.KEYCODE_DPAD_UP:
    852                 if (handleKeyEvent) {
    853                     // Select the closest icon in the previous line
    854                     View newIcon = getClosestIconOnLine(layout, parent, v, -1);
    855                     if (newIcon != null) {
    856                         newIcon.requestFocus();
    857                     }
    858                 }
    859                 wasHandled = true;
    860                 break;
    861             case KeyEvent.KEYCODE_DPAD_DOWN:
    862                 if (handleKeyEvent) {
    863                     // Select the closest icon in the next line
    864                     View newIcon = getClosestIconOnLine(layout, parent, v, 1);
    865                     if (newIcon != null) {
    866                         newIcon.requestFocus();
    867                     } else {
    868                         title.requestFocus();
    869                     }
    870                 }
    871                 wasHandled = true;
    872                 break;
    873             case KeyEvent.KEYCODE_MOVE_HOME:
    874                 if (handleKeyEvent) {
    875                     // Select the first icon on this page
    876                     View newIcon = getIconInDirection(layout, parent, -1, 1);
    877                     if (newIcon != null) {
    878                         newIcon.requestFocus();
    879                     }
    880                 }
    881                 wasHandled = true;
    882                 break;
    883             case KeyEvent.KEYCODE_MOVE_END:
    884                 if (handleKeyEvent) {
    885                     // Select the last icon on this page
    886                     View newIcon = getIconInDirection(layout, parent,
    887                             parent.getChildCount(), -1);
    888                     if (newIcon != null) {
    889                         newIcon.requestFocus();
    890                     }
    891                 }
    892                 wasHandled = true;
    893                 break;
    894             default: break;
    895         }
    896         return wasHandled;
    897     }
    898 }
    899