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
17package com.android.launcher2;
18
19import android.content.res.Configuration;
20import android.view.KeyEvent;
21import android.view.View;
22import android.view.ViewGroup;
23import android.view.ViewParent;
24import android.widget.TabHost;
25import android.widget.TabWidget;
26
27import com.android.launcher.R;
28
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32
33/**
34 * A keyboard listener we set on all the workspace icons.
35 */
36class 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 */
45class 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 */
54class 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 */
65class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
66    public boolean onKey(View v, int keyCode, KeyEvent event) {
67        return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
68    }
69}
70
71public 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