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