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