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