blob: f97492bd69e1ba7292c2bc0f44fafef690f13913 [file] [log] [blame]
Winson Chung97d85d22011-04-13 11:27:36 -07001/*
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
Winson Chung4e6a9762011-05-09 11:56:34 -070019import android.content.res.Configuration;
Winson Chung97d85d22011-04-13 11:27:36 -070020import android.view.KeyEvent;
21import android.view.View;
22import android.view.ViewGroup;
23import android.view.ViewParent;
24import android.widget.TabHost;
25import android.widget.TabWidget;
26
27import com.android.launcher.R;
28
Winson Chungfaa13252011-06-13 18:15:54 -070029import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32
Winson Chung97d85d22011-04-13 11:27:36 -070033/**
Winson Chung4d279d92011-07-21 11:46:32 -070034 * A keyboard listener we set on all the workspace icons.
35 */
36class BubbleTextViewKeyEventListener implements View.OnKeyListener {
37 @Override
38 public boolean onKey(View v, int keyCode, KeyEvent event) {
39 return FocusHelper.handleBubbleTextViewKeyEvent((BubbleTextView) v, keyCode, event);
40 }
41}
42
43/**
Winson Chung3d503fb2011-07-13 17:25:49 -070044 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070045 */
Winson Chung4d279d92011-07-21 11:46:32 -070046class HotseatBubbleTextViewKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070047 @Override
48 public boolean onKey(View v, int keyCode, KeyEvent event) {
49 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070050 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070051 }
52}
53
54/**
Winson Chungfaa13252011-06-13 18:15:54 -070055 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070056 * market icon and vice versa.
57 */
Winson Chungfaa13252011-06-13 18:15:54 -070058class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070059 @Override
60 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070061 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070062 }
63}
64
65public class FocusHelper {
66 /**
67 * Private helper to get the parent TabHost in the view hiearchy.
68 */
69 private static TabHost findTabHostParent(View v) {
70 ViewParent p = v.getParent();
71 while (p != null && !(p instanceof TabHost)) {
72 p = p.getParent();
73 }
74 return (TabHost) p;
75 }
76
77 /**
Winson Chungfaa13252011-06-13 18:15:54 -070078 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070079 */
Winson Chungfaa13252011-06-13 18:15:54 -070080 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070081 final TabHost tabHost = findTabHostParent(v);
82 final ViewGroup contents = (ViewGroup)
83 tabHost.findViewById(com.android.internal.R.id.tabcontent);
84 final View shop = tabHost.findViewById(R.id.market_button);
85
86 final int action = e.getAction();
87 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
88 boolean wasHandled = false;
89 switch (keyCode) {
90 case KeyEvent.KEYCODE_DPAD_RIGHT:
91 if (handleKeyEvent) {
92 // Select the shop button if we aren't on it
93 if (v != shop) {
94 shop.requestFocus();
95 }
96 }
97 wasHandled = true;
98 break;
99 case KeyEvent.KEYCODE_DPAD_DOWN:
100 if (handleKeyEvent) {
101 // Select the content view (down is handled by the tab key handler otherwise)
102 if (v == shop) {
103 contents.requestFocus();
104 wasHandled = true;
105 }
106 }
107 break;
108 default: break;
109 }
110 return wasHandled;
111 }
112
113 /**
114 * Private helper to determine whether a view is visible.
115 */
116 private static boolean isVisible(View v) {
117 return v.getVisibility() == View.VISIBLE;
118 }
119
120 /**
121 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700122 */
123 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
124 KeyEvent e) {
125
126 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
127 final ViewGroup container = (ViewGroup) parent.getParent();
128 final TabHost tabHost = findTabHostParent(container);
129 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
130 final int widgetIndex = parent.indexOfChild(w);
131 final int widgetCount = parent.getChildCount();
132 final int pageIndex = container.indexOfChild(parent);
133 final int pageCount = container.getChildCount();
134 final int cellCountX = parent.getCellCountX();
135 final int cellCountY = parent.getCellCountY();
136 final int x = widgetIndex % cellCountX;
137 final int y = widgetIndex / cellCountX;
138
139 final int action = e.getAction();
140 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
141 PagedViewGridLayout newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700142 // Now that we load items in the bg asynchronously, we can't just focus
143 // child siblings willy-nilly
144 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700145 boolean wasHandled = false;
146 switch (keyCode) {
147 case KeyEvent.KEYCODE_DPAD_LEFT:
148 if (handleKeyEvent) {
149 // Select the previous widget or the last widget on the previous page
150 if (widgetIndex > 0) {
151 parent.getChildAt(widgetIndex - 1).requestFocus();
152 } else {
153 if (pageIndex > 0) {
154 newParent = (PagedViewGridLayout)
155 container.getChildAt(pageIndex - 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700156 child = newParent.getChildAt(newParent.getChildCount() - 1);
157 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700158 }
159 }
160 }
161 wasHandled = true;
162 break;
163 case KeyEvent.KEYCODE_DPAD_RIGHT:
164 if (handleKeyEvent) {
165 // Select the next widget or the first widget on the next page
166 if (widgetIndex < (widgetCount - 1)) {
167 parent.getChildAt(widgetIndex + 1).requestFocus();
168 } else {
169 if (pageIndex < (pageCount - 1)) {
170 newParent = (PagedViewGridLayout)
171 container.getChildAt(pageIndex + 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700172 child = newParent.getChildAt(0);
173 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700174 }
175 }
176 }
177 wasHandled = true;
178 break;
179 case KeyEvent.KEYCODE_DPAD_UP:
180 if (handleKeyEvent) {
181 // Select the closest icon in the previous row, otherwise select the tab bar
182 if (y > 0) {
183 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700184 child = parent.getChildAt(newWidgetIndex);
185 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700186 } else {
187 tabs.requestFocus();
188 }
189 }
190 wasHandled = true;
191 break;
192 case KeyEvent.KEYCODE_DPAD_DOWN:
193 if (handleKeyEvent) {
194 // Select the closest icon in the previous row, otherwise do nothing
195 if (y < (cellCountY - 1)) {
196 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700197 child = parent.getChildAt(newWidgetIndex);
198 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700199 }
200 }
201 wasHandled = true;
202 break;
203 case KeyEvent.KEYCODE_ENTER:
204 case KeyEvent.KEYCODE_DPAD_CENTER:
205 if (handleKeyEvent) {
206 // Simulate a click on the widget
207 View.OnClickListener clickListener = (View.OnClickListener) container;
208 clickListener.onClick(w);
209 }
210 wasHandled = true;
211 break;
212 case KeyEvent.KEYCODE_PAGE_UP:
213 if (handleKeyEvent) {
214 // Select the first item on the previous page, or the first item on this page
215 // if there is no previous page
216 if (pageIndex > 0) {
217 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700218 child = newParent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700219 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700220 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700221 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700222 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700223 }
224 wasHandled = true;
225 break;
226 case KeyEvent.KEYCODE_PAGE_DOWN:
227 if (handleKeyEvent) {
228 // Select the first item on the next page, or the last item on this page
229 // if there is no next page
230 if (pageIndex < (pageCount - 1)) {
231 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700232 child = newParent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700233 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700234 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700235 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700236 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700237 }
238 wasHandled = true;
239 break;
240 case KeyEvent.KEYCODE_MOVE_HOME:
241 if (handleKeyEvent) {
242 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700243 child = parent.getChildAt(0);
244 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700245 }
246 wasHandled = true;
247 break;
248 case KeyEvent.KEYCODE_MOVE_END:
249 if (handleKeyEvent) {
250 // Select the last item on this page
251 parent.getChildAt(widgetCount - 1).requestFocus();
252 }
253 wasHandled = true;
254 break;
255 default: break;
256 }
257 return wasHandled;
258 }
259
260 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700261 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
262 * index.
263 */
264 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex(
265 ViewGroup container, int i) {
266 ViewGroup parent = (ViewGroup) container.getChildAt(i);
267 return (PagedViewCellLayoutChildren) parent.getChildAt(0);
268 }
269
270 /**
271 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
272 */
273 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700274 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
275 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
276 // Note we have an extra parent because of the
277 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
278 final ViewGroup container = (ViewGroup) parentLayout.getParent();
279 final TabHost tabHost = findTabHostParent(container);
280 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
281 final int widgetIndex = parent.indexOfChild(v);
282 final int widgetCount = parent.getChildCount();
283 final int pageIndex = container.indexOfChild(parentLayout);
284 final int pageCount = container.getChildCount();
285 final int cellCountX = parentLayout.getCellCountX();
286 final int cellCountY = parentLayout.getCellCountY();
287 final int x = widgetIndex % cellCountX;
288 final int y = widgetIndex / cellCountX;
289
290 final int action = e.getAction();
291 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
292 PagedViewCellLayoutChildren newParent = null;
293 boolean wasHandled = false;
294 switch (keyCode) {
295 case KeyEvent.KEYCODE_DPAD_LEFT:
296 if (handleKeyEvent) {
297 // Select the previous icon or the last icon on the previous page
298 if (widgetIndex > 0) {
299 parent.getChildAt(widgetIndex - 1).requestFocus();
300 } else {
301 if (pageIndex > 0) {
302 newParent = getPagedViewCellLayoutChildrenForIndex(container,
303 pageIndex - 1);
304 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
305 }
306 }
307 }
308 wasHandled = true;
309 break;
310 case KeyEvent.KEYCODE_DPAD_RIGHT:
311 if (handleKeyEvent) {
312 // Select the next icon or the first icon on the next page
313 if (widgetIndex < (widgetCount - 1)) {
314 parent.getChildAt(widgetIndex + 1).requestFocus();
315 } else {
316 if (pageIndex < (pageCount - 1)) {
317 newParent = getPagedViewCellLayoutChildrenForIndex(container,
318 pageIndex + 1);
319 newParent.getChildAt(0).requestFocus();
320 }
321 }
322 }
323 wasHandled = true;
324 break;
325 case KeyEvent.KEYCODE_DPAD_UP:
326 if (handleKeyEvent) {
327 // Select the closest icon in the previous row, otherwise select the tab bar
328 if (y > 0) {
329 int newWidgetIndex = ((y - 1) * cellCountX) + x;
330 parent.getChildAt(newWidgetIndex).requestFocus();
331 } else {
332 tabs.requestFocus();
333 }
334 }
335 wasHandled = true;
336 break;
337 case KeyEvent.KEYCODE_DPAD_DOWN:
338 if (handleKeyEvent) {
339 // Select the closest icon in the previous row, otherwise do nothing
340 if (y < (cellCountY - 1)) {
341 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
342 parent.getChildAt(newWidgetIndex).requestFocus();
343 }
344 }
345 wasHandled = true;
346 break;
347 case KeyEvent.KEYCODE_ENTER:
348 case KeyEvent.KEYCODE_DPAD_CENTER:
349 if (handleKeyEvent) {
350 // Simulate a click on the icon
351 View.OnClickListener clickListener = (View.OnClickListener) container;
352 clickListener.onClick(v);
353 }
354 wasHandled = true;
355 break;
356 case KeyEvent.KEYCODE_PAGE_UP:
357 if (handleKeyEvent) {
358 // Select the first icon on the previous page, or the first icon on this page
359 // if there is no previous page
360 if (pageIndex > 0) {
361 newParent = getPagedViewCellLayoutChildrenForIndex(container,
362 pageIndex - 1);
363 newParent.getChildAt(0).requestFocus();
364 } else {
365 parent.getChildAt(0).requestFocus();
366 }
367 }
368 wasHandled = true;
369 break;
370 case KeyEvent.KEYCODE_PAGE_DOWN:
371 if (handleKeyEvent) {
372 // Select the first icon on the next page, or the last icon on this page
373 // if there is no next page
374 if (pageIndex < (pageCount - 1)) {
375 newParent = getPagedViewCellLayoutChildrenForIndex(container,
376 pageIndex + 1);
377 newParent.getChildAt(0).requestFocus();
378 } else {
379 parent.getChildAt(widgetCount - 1).requestFocus();
380 }
381 }
382 wasHandled = true;
383 break;
384 case KeyEvent.KEYCODE_MOVE_HOME:
385 if (handleKeyEvent) {
386 // Select the first icon on this page
387 parent.getChildAt(0).requestFocus();
388 }
389 wasHandled = true;
390 break;
391 case KeyEvent.KEYCODE_MOVE_END:
392 if (handleKeyEvent) {
393 // Select the last icon on this page
394 parent.getChildAt(widgetCount - 1).requestFocus();
395 }
396 wasHandled = true;
397 break;
398 default: break;
399 }
400 return wasHandled;
401 }
402
403 /**
404 * Handles key events in the tab widget.
405 */
406 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700407 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700408
409 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
410 final TabHost tabHost = findTabHostParent(parent);
411 final ViewGroup contents = (ViewGroup)
412 tabHost.findViewById(com.android.internal.R.id.tabcontent);
413 final int tabCount = parent.getTabCount();
414 final int tabIndex = parent.getChildTabIndex(v);
415
416 final int action = e.getAction();
417 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
418 boolean wasHandled = false;
419 switch (keyCode) {
420 case KeyEvent.KEYCODE_DPAD_LEFT:
421 if (handleKeyEvent) {
422 // Select the previous tab
423 if (tabIndex > 0) {
424 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
425 }
426 }
427 wasHandled = true;
428 break;
429 case KeyEvent.KEYCODE_DPAD_RIGHT:
430 if (handleKeyEvent) {
431 // Select the next tab, or if the last tab has a focus right id, select that
432 if (tabIndex < (tabCount - 1)) {
433 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
434 } else {
435 if (v.getNextFocusRightId() != View.NO_ID) {
436 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
437 }
438 }
439 }
440 wasHandled = true;
441 break;
442 case KeyEvent.KEYCODE_DPAD_UP:
443 // Do nothing
444 wasHandled = true;
445 break;
446 case KeyEvent.KEYCODE_DPAD_DOWN:
447 if (handleKeyEvent) {
448 // Select the content view
449 contents.requestFocus();
450 }
451 wasHandled = true;
452 break;
453 default: break;
454 }
455 return wasHandled;
456 }
457
458 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700459 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700460 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700461 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700462 final ViewGroup parent = (ViewGroup) v.getParent();
463 final ViewGroup launcher = (ViewGroup) parent.getParent();
464 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
465 final int buttonIndex = parent.indexOfChild(v);
466 final int buttonCount = parent.getChildCount();
467 final int pageIndex = workspace.getCurrentPage();
468 final int pageCount = workspace.getChildCount();
Winson Chung4e6a9762011-05-09 11:56:34 -0700469
470 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700471 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700472 // is to ensure that accessibility consistency is maintained across rotations.
473
474 final int action = e.getAction();
475 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
476 boolean wasHandled = false;
477 switch (keyCode) {
478 case KeyEvent.KEYCODE_DPAD_LEFT:
479 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700480 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700481 if (buttonIndex > 0) {
482 parent.getChildAt(buttonIndex - 1).requestFocus();
483 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700484 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700485 }
486 }
487 wasHandled = true;
488 break;
489 case KeyEvent.KEYCODE_DPAD_RIGHT:
490 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700491 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700492 if (buttonIndex < (buttonCount - 1)) {
493 parent.getChildAt(buttonIndex + 1).requestFocus();
494 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700495 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700496 }
497 }
498 wasHandled = true;
499 break;
500 case KeyEvent.KEYCODE_DPAD_UP:
501 if (handleKeyEvent) {
502 // Select the first bubble text view in the current page of the workspace
503 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
504 final CellLayoutChildren children = layout.getChildrenLayout();
505 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
506 if (newIcon != null) {
507 newIcon.requestFocus();
508 } else {
509 workspace.requestFocus();
510 }
511 }
512 wasHandled = true;
513 break;
514 case KeyEvent.KEYCODE_DPAD_DOWN:
515 // Do nothing
516 wasHandled = true;
517 break;
518 default: break;
519 }
520 return wasHandled;
521 }
522
523 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700524 * Private helper method to get the CellLayoutChildren given a CellLayout index.
525 */
526 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
527 ViewGroup parent = (ViewGroup) container.getChildAt(i);
528 return (CellLayoutChildren) parent.getChildAt(0);
529 }
530
531 /**
532 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
533 * from top left to bottom right.
534 */
535 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
536 ViewGroup parent) {
537 // First we order each the CellLayout children by their x,y coordinates
538 final int cellCountX = layout.getCountX();
539 final int count = parent.getChildCount();
540 ArrayList<View> views = new ArrayList<View>();
541 for (int j = 0; j < count; ++j) {
542 views.add(parent.getChildAt(j));
543 }
544 Collections.sort(views, new Comparator<View>() {
545 @Override
546 public int compare(View lhs, View rhs) {
547 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
548 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
549 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
550 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
551 return lvIndex - rvIndex;
552 }
553 });
554 return views;
555 }
556 /**
557 * Private helper method to find the index of the next BubbleTextView in the delta direction.
558 * @param delta either -1 or 1 depending on the direction we want to search
559 */
560 private static View findIndexOfBubbleTextView(ArrayList<View> views, int i, int delta) {
561 // Then we find the next BubbleTextView offset by delta from i
562 final int count = views.size();
563 int newI = i + delta;
564 while (0 <= newI && newI < count) {
565 View newV = views.get(newI);
566 if (newV instanceof BubbleTextView) {
567 return newV;
568 }
569 newI += delta;
570 }
571 return null;
572 }
573 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i,
574 int delta) {
575 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
576 return findIndexOfBubbleTextView(views, i, delta);
577 }
578 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v,
579 int delta) {
580 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
581 return findIndexOfBubbleTextView(views, views.indexOf(v), delta);
582 }
583 /**
584 * Private helper method to find the next closest BubbleTextView in the delta direction on the
585 * next line.
586 * @param delta either -1 or 1 depending on the line and direction we want to search
587 */
588 private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v,
589 int lineDelta) {
590 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
591 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
592 final int cellCountX = layout.getCountX();
593 final int cellCountY = layout.getCountY();
594 final int row = lp.cellY;
595 final int newRow = row + lineDelta;
596 if (0 <= newRow && newRow < cellCountY) {
597 float closestDistance = Float.MAX_VALUE;
598 int closestIndex = -1;
599 int index = views.indexOf(v);
600 int endIndex = (lineDelta < 0) ? -1 : views.size();
601 while (index != endIndex) {
602 View newV = views.get(index);
603 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
604 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
605 if (satisfiesRow && newV instanceof BubbleTextView) {
606 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
607 Math.pow(tmpLp.cellY - lp.cellY, 2));
608 if (tmpDistance < closestDistance) {
609 closestIndex = index;
610 closestDistance = tmpDistance;
611 }
612 }
613 if (index <= endIndex) {
614 ++index;
615 } else {
616 --index;
617 }
618 }
619 if (closestIndex > -1) {
620 return views.get(closestIndex);
621 }
622 }
623 return null;
624 }
625
626 /**
627 * Handles key events in a Workspace containing BubbleTextView.
628 */
629 static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700630 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
631 final CellLayout layout = (CellLayout) parent.getParent();
632 final Workspace workspace = (Workspace) layout.getParent();
633 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700634 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700635 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700636 int iconIndex = parent.indexOfChild(v);
637 int iconCount = parent.getChildCount();
638 int pageIndex = workspace.indexOfChild(layout);
639 int pageCount = workspace.getChildCount();
640
641 final int action = e.getAction();
642 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
643 boolean wasHandled = false;
644 switch (keyCode) {
645 case KeyEvent.KEYCODE_DPAD_LEFT:
646 if (handleKeyEvent) {
647 // Select the previous icon or the last icon on the previous page if possible
648 View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1);
649 if (newIcon != null) {
650 newIcon.requestFocus();
651 } else {
652 if (pageIndex > 0) {
653 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
654 newIcon = getBubbleTextViewInDirection(layout, parent,
655 parent.getChildCount(), -1);
656 if (newIcon != null) {
657 newIcon.requestFocus();
658 } else {
659 // Snap to the previous page
660 workspace.snapToPage(pageIndex - 1);
661 }
662 }
663 }
664 }
665 wasHandled = true;
666 break;
667 case KeyEvent.KEYCODE_DPAD_RIGHT:
668 if (handleKeyEvent) {
669 // Select the next icon or the first icon on the next page if possible
670 View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1);
671 if (newIcon != null) {
672 newIcon.requestFocus();
673 } else {
674 if (pageIndex < (pageCount - 1)) {
675 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
676 newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
677 if (newIcon != null) {
678 newIcon.requestFocus();
679 } else {
680 // Snap to the next page
681 workspace.snapToPage(pageIndex + 1);
682 }
683 }
684 }
685 }
686 wasHandled = true;
687 break;
688 case KeyEvent.KEYCODE_DPAD_UP:
689 if (handleKeyEvent) {
690 // Select the closest icon in the previous line, otherwise select the tab bar
691 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1);
692 if (newIcon != null) {
693 newIcon.requestFocus();
694 wasHandled = true;
695 } else {
696 tabs.requestFocus();
697 }
698 }
699 break;
700 case KeyEvent.KEYCODE_DPAD_DOWN:
701 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700702 // Select the closest icon in the next line, otherwise select the button bar
Winson Chung97d85d22011-04-13 11:27:36 -0700703 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1);
704 if (newIcon != null) {
705 newIcon.requestFocus();
706 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700707 } else if (hotseat != null) {
708 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700709 }
710 }
711 break;
712 case KeyEvent.KEYCODE_PAGE_UP:
713 if (handleKeyEvent) {
714 // Select the first icon on the previous page or the first icon on this page
715 // if there is no previous page
716 if (pageIndex > 0) {
717 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
718 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
719 if (newIcon != null) {
720 newIcon.requestFocus();
721 } else {
722 // Snap to the previous page
723 workspace.snapToPage(pageIndex - 1);
724 }
725 } else {
726 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
727 if (newIcon != null) {
728 newIcon.requestFocus();
729 }
730 }
731 }
732 wasHandled = true;
733 break;
734 case KeyEvent.KEYCODE_PAGE_DOWN:
735 if (handleKeyEvent) {
736 // Select the first icon on the next page or the last icon on this page
737 // if there is no previous page
738 if (pageIndex < (pageCount - 1)) {
739 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
740 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
741 if (newIcon != null) {
742 newIcon.requestFocus();
743 } else {
744 // Snap to the next page
745 workspace.snapToPage(pageIndex + 1);
746 }
747 } else {
748 View newIcon = getBubbleTextViewInDirection(layout, parent,
749 parent.getChildCount(), -1);
750 if (newIcon != null) {
751 newIcon.requestFocus();
752 }
753 }
754 }
755 wasHandled = true;
756 break;
757 case KeyEvent.KEYCODE_MOVE_HOME:
758 if (handleKeyEvent) {
759 // Select the first icon on this page
760 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
761 if (newIcon != null) {
762 newIcon.requestFocus();
763 }
764 }
765 wasHandled = true;
766 break;
767 case KeyEvent.KEYCODE_MOVE_END:
768 if (handleKeyEvent) {
769 // Select the last icon on this page
770 View newIcon = getBubbleTextViewInDirection(layout, parent,
771 parent.getChildCount(), -1);
772 if (newIcon != null) {
773 newIcon.requestFocus();
774 }
775 }
776 wasHandled = true;
777 break;
778 default: break;
779 }
780 return wasHandled;
781 }
782}