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