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