blob: 3783d566cee20a1a36da6324da7a0a62782009b2 [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;
142 boolean wasHandled = false;
143 switch (keyCode) {
144 case KeyEvent.KEYCODE_DPAD_LEFT:
145 if (handleKeyEvent) {
146 // Select the previous widget or the last widget on the previous page
147 if (widgetIndex > 0) {
148 parent.getChildAt(widgetIndex - 1).requestFocus();
149 } else {
150 if (pageIndex > 0) {
151 newParent = (PagedViewGridLayout)
152 container.getChildAt(pageIndex - 1);
153 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
154 }
155 }
156 }
157 wasHandled = true;
158 break;
159 case KeyEvent.KEYCODE_DPAD_RIGHT:
160 if (handleKeyEvent) {
161 // Select the next widget or the first widget on the next page
162 if (widgetIndex < (widgetCount - 1)) {
163 parent.getChildAt(widgetIndex + 1).requestFocus();
164 } else {
165 if (pageIndex < (pageCount - 1)) {
166 newParent = (PagedViewGridLayout)
167 container.getChildAt(pageIndex + 1);
168 newParent.getChildAt(0).requestFocus();
169 }
170 }
171 }
172 wasHandled = true;
173 break;
174 case KeyEvent.KEYCODE_DPAD_UP:
175 if (handleKeyEvent) {
176 // Select the closest icon in the previous row, otherwise select the tab bar
177 if (y > 0) {
178 int newWidgetIndex = ((y - 1) * cellCountX) + x;
179 parent.getChildAt(newWidgetIndex).requestFocus();
180 } else {
181 tabs.requestFocus();
182 }
183 }
184 wasHandled = true;
185 break;
186 case KeyEvent.KEYCODE_DPAD_DOWN:
187 if (handleKeyEvent) {
188 // Select the closest icon in the previous row, otherwise do nothing
189 if (y < (cellCountY - 1)) {
190 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
191 parent.getChildAt(newWidgetIndex).requestFocus();
192 }
193 }
194 wasHandled = true;
195 break;
196 case KeyEvent.KEYCODE_ENTER:
197 case KeyEvent.KEYCODE_DPAD_CENTER:
198 if (handleKeyEvent) {
199 // Simulate a click on the widget
200 View.OnClickListener clickListener = (View.OnClickListener) container;
201 clickListener.onClick(w);
202 }
203 wasHandled = true;
204 break;
205 case KeyEvent.KEYCODE_PAGE_UP:
206 if (handleKeyEvent) {
207 // Select the first item on the previous page, or the first item on this page
208 // if there is no previous page
209 if (pageIndex > 0) {
210 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
211 newParent.getChildAt(0).requestFocus();
212 } else {
213 parent.getChildAt(0).requestFocus();
214 }
215 }
216 wasHandled = true;
217 break;
218 case KeyEvent.KEYCODE_PAGE_DOWN:
219 if (handleKeyEvent) {
220 // Select the first item on the next page, or the last item on this page
221 // if there is no next page
222 if (pageIndex < (pageCount - 1)) {
223 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
224 newParent.getChildAt(0).requestFocus();
225 } else {
226 parent.getChildAt(widgetCount - 1).requestFocus();
227 }
228 }
229 wasHandled = true;
230 break;
231 case KeyEvent.KEYCODE_MOVE_HOME:
232 if (handleKeyEvent) {
233 // Select the first item on this page
234 parent.getChildAt(0).requestFocus();
235 }
236 wasHandled = true;
237 break;
238 case KeyEvent.KEYCODE_MOVE_END:
239 if (handleKeyEvent) {
240 // Select the last item on this page
241 parent.getChildAt(widgetCount - 1).requestFocus();
242 }
243 wasHandled = true;
244 break;
245 default: break;
246 }
247 return wasHandled;
248 }
249
250 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700251 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
252 * index.
253 */
254 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex(
255 ViewGroup container, int i) {
256 ViewGroup parent = (ViewGroup) container.getChildAt(i);
257 return (PagedViewCellLayoutChildren) parent.getChildAt(0);
258 }
259
260 /**
261 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
262 */
263 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700264 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
265 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
266 // Note we have an extra parent because of the
267 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
268 final ViewGroup container = (ViewGroup) parentLayout.getParent();
269 final TabHost tabHost = findTabHostParent(container);
270 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
271 final int widgetIndex = parent.indexOfChild(v);
272 final int widgetCount = parent.getChildCount();
273 final int pageIndex = container.indexOfChild(parentLayout);
274 final int pageCount = container.getChildCount();
275 final int cellCountX = parentLayout.getCellCountX();
276 final int cellCountY = parentLayout.getCellCountY();
277 final int x = widgetIndex % cellCountX;
278 final int y = widgetIndex / cellCountX;
279
280 final int action = e.getAction();
281 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
282 PagedViewCellLayoutChildren newParent = null;
283 boolean wasHandled = false;
284 switch (keyCode) {
285 case KeyEvent.KEYCODE_DPAD_LEFT:
286 if (handleKeyEvent) {
287 // Select the previous icon or the last icon on the previous page
288 if (widgetIndex > 0) {
289 parent.getChildAt(widgetIndex - 1).requestFocus();
290 } else {
291 if (pageIndex > 0) {
292 newParent = getPagedViewCellLayoutChildrenForIndex(container,
293 pageIndex - 1);
294 newParent.getChildAt(newParent.getChildCount() - 1).requestFocus();
295 }
296 }
297 }
298 wasHandled = true;
299 break;
300 case KeyEvent.KEYCODE_DPAD_RIGHT:
301 if (handleKeyEvent) {
302 // Select the next icon or the first icon on the next page
303 if (widgetIndex < (widgetCount - 1)) {
304 parent.getChildAt(widgetIndex + 1).requestFocus();
305 } else {
306 if (pageIndex < (pageCount - 1)) {
307 newParent = getPagedViewCellLayoutChildrenForIndex(container,
308 pageIndex + 1);
309 newParent.getChildAt(0).requestFocus();
310 }
311 }
312 }
313 wasHandled = true;
314 break;
315 case KeyEvent.KEYCODE_DPAD_UP:
316 if (handleKeyEvent) {
317 // Select the closest icon in the previous row, otherwise select the tab bar
318 if (y > 0) {
319 int newWidgetIndex = ((y - 1) * cellCountX) + x;
320 parent.getChildAt(newWidgetIndex).requestFocus();
321 } else {
322 tabs.requestFocus();
323 }
324 }
325 wasHandled = true;
326 break;
327 case KeyEvent.KEYCODE_DPAD_DOWN:
328 if (handleKeyEvent) {
329 // Select the closest icon in the previous row, otherwise do nothing
330 if (y < (cellCountY - 1)) {
331 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
332 parent.getChildAt(newWidgetIndex).requestFocus();
333 }
334 }
335 wasHandled = true;
336 break;
337 case KeyEvent.KEYCODE_ENTER:
338 case KeyEvent.KEYCODE_DPAD_CENTER:
339 if (handleKeyEvent) {
340 // Simulate a click on the icon
341 View.OnClickListener clickListener = (View.OnClickListener) container;
342 clickListener.onClick(v);
343 }
344 wasHandled = true;
345 break;
346 case KeyEvent.KEYCODE_PAGE_UP:
347 if (handleKeyEvent) {
348 // Select the first icon on the previous page, or the first icon on this page
349 // if there is no previous page
350 if (pageIndex > 0) {
351 newParent = getPagedViewCellLayoutChildrenForIndex(container,
352 pageIndex - 1);
353 newParent.getChildAt(0).requestFocus();
354 } else {
355 parent.getChildAt(0).requestFocus();
356 }
357 }
358 wasHandled = true;
359 break;
360 case KeyEvent.KEYCODE_PAGE_DOWN:
361 if (handleKeyEvent) {
362 // Select the first icon on the next page, or the last icon on this page
363 // if there is no next page
364 if (pageIndex < (pageCount - 1)) {
365 newParent = getPagedViewCellLayoutChildrenForIndex(container,
366 pageIndex + 1);
367 newParent.getChildAt(0).requestFocus();
368 } else {
369 parent.getChildAt(widgetCount - 1).requestFocus();
370 }
371 }
372 wasHandled = true;
373 break;
374 case KeyEvent.KEYCODE_MOVE_HOME:
375 if (handleKeyEvent) {
376 // Select the first icon on this page
377 parent.getChildAt(0).requestFocus();
378 }
379 wasHandled = true;
380 break;
381 case KeyEvent.KEYCODE_MOVE_END:
382 if (handleKeyEvent) {
383 // Select the last icon on this page
384 parent.getChildAt(widgetCount - 1).requestFocus();
385 }
386 wasHandled = true;
387 break;
388 default: break;
389 }
390 return wasHandled;
391 }
392
393 /**
394 * Handles key events in the tab widget.
395 */
396 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700397 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700398
399 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
400 final TabHost tabHost = findTabHostParent(parent);
401 final ViewGroup contents = (ViewGroup)
402 tabHost.findViewById(com.android.internal.R.id.tabcontent);
403 final int tabCount = parent.getTabCount();
404 final int tabIndex = parent.getChildTabIndex(v);
405
406 final int action = e.getAction();
407 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
408 boolean wasHandled = false;
409 switch (keyCode) {
410 case KeyEvent.KEYCODE_DPAD_LEFT:
411 if (handleKeyEvent) {
412 // Select the previous tab
413 if (tabIndex > 0) {
414 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
415 }
416 }
417 wasHandled = true;
418 break;
419 case KeyEvent.KEYCODE_DPAD_RIGHT:
420 if (handleKeyEvent) {
421 // Select the next tab, or if the last tab has a focus right id, select that
422 if (tabIndex < (tabCount - 1)) {
423 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
424 } else {
425 if (v.getNextFocusRightId() != View.NO_ID) {
426 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
427 }
428 }
429 }
430 wasHandled = true;
431 break;
432 case KeyEvent.KEYCODE_DPAD_UP:
433 // Do nothing
434 wasHandled = true;
435 break;
436 case KeyEvent.KEYCODE_DPAD_DOWN:
437 if (handleKeyEvent) {
438 // Select the content view
439 contents.requestFocus();
440 }
441 wasHandled = true;
442 break;
443 default: break;
444 }
445 return wasHandled;
446 }
447
448 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700449 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700450 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700451 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700452 final ViewGroup parent = (ViewGroup) v.getParent();
453 final ViewGroup launcher = (ViewGroup) parent.getParent();
454 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
455 final int buttonIndex = parent.indexOfChild(v);
456 final int buttonCount = parent.getChildCount();
457 final int pageIndex = workspace.getCurrentPage();
458 final int pageCount = workspace.getChildCount();
Winson Chung4e6a9762011-05-09 11:56:34 -0700459
460 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700461 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700462 // is to ensure that accessibility consistency is maintained across rotations.
463
464 final int action = e.getAction();
465 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
466 boolean wasHandled = false;
467 switch (keyCode) {
468 case KeyEvent.KEYCODE_DPAD_LEFT:
469 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700470 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700471 if (buttonIndex > 0) {
472 parent.getChildAt(buttonIndex - 1).requestFocus();
473 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700474 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700475 }
476 }
477 wasHandled = true;
478 break;
479 case KeyEvent.KEYCODE_DPAD_RIGHT:
480 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700481 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700482 if (buttonIndex < (buttonCount - 1)) {
483 parent.getChildAt(buttonIndex + 1).requestFocus();
484 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700485 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700486 }
487 }
488 wasHandled = true;
489 break;
490 case KeyEvent.KEYCODE_DPAD_UP:
491 if (handleKeyEvent) {
492 // Select the first bubble text view in the current page of the workspace
493 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
494 final CellLayoutChildren children = layout.getChildrenLayout();
495 final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1);
496 if (newIcon != null) {
497 newIcon.requestFocus();
498 } else {
499 workspace.requestFocus();
500 }
501 }
502 wasHandled = true;
503 break;
504 case KeyEvent.KEYCODE_DPAD_DOWN:
505 // Do nothing
506 wasHandled = true;
507 break;
508 default: break;
509 }
510 return wasHandled;
511 }
512
513 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700514 * Private helper method to get the CellLayoutChildren given a CellLayout index.
515 */
516 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
517 ViewGroup parent = (ViewGroup) container.getChildAt(i);
518 return (CellLayoutChildren) parent.getChildAt(0);
519 }
520
521 /**
522 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
523 * from top left to bottom right.
524 */
525 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
526 ViewGroup parent) {
527 // First we order each the CellLayout children by their x,y coordinates
528 final int cellCountX = layout.getCountX();
529 final int count = parent.getChildCount();
530 ArrayList<View> views = new ArrayList<View>();
531 for (int j = 0; j < count; ++j) {
532 views.add(parent.getChildAt(j));
533 }
534 Collections.sort(views, new Comparator<View>() {
535 @Override
536 public int compare(View lhs, View rhs) {
537 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
538 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
539 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
540 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
541 return lvIndex - rvIndex;
542 }
543 });
544 return views;
545 }
546 /**
547 * Private helper method to find the index of the next BubbleTextView in the delta direction.
548 * @param delta either -1 or 1 depending on the direction we want to search
549 */
550 private static View findIndexOfBubbleTextView(ArrayList<View> views, int i, int delta) {
551 // Then we find the next BubbleTextView offset by delta from i
552 final int count = views.size();
553 int newI = i + delta;
554 while (0 <= newI && newI < count) {
555 View newV = views.get(newI);
556 if (newV instanceof BubbleTextView) {
557 return newV;
558 }
559 newI += delta;
560 }
561 return null;
562 }
563 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i,
564 int delta) {
565 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
566 return findIndexOfBubbleTextView(views, i, delta);
567 }
568 private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v,
569 int delta) {
570 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
571 return findIndexOfBubbleTextView(views, views.indexOf(v), delta);
572 }
573 /**
574 * Private helper method to find the next closest BubbleTextView in the delta direction on the
575 * next line.
576 * @param delta either -1 or 1 depending on the line and direction we want to search
577 */
578 private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v,
579 int lineDelta) {
580 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
581 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
582 final int cellCountX = layout.getCountX();
583 final int cellCountY = layout.getCountY();
584 final int row = lp.cellY;
585 final int newRow = row + lineDelta;
586 if (0 <= newRow && newRow < cellCountY) {
587 float closestDistance = Float.MAX_VALUE;
588 int closestIndex = -1;
589 int index = views.indexOf(v);
590 int endIndex = (lineDelta < 0) ? -1 : views.size();
591 while (index != endIndex) {
592 View newV = views.get(index);
593 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
594 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
595 if (satisfiesRow && newV instanceof BubbleTextView) {
596 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
597 Math.pow(tmpLp.cellY - lp.cellY, 2));
598 if (tmpDistance < closestDistance) {
599 closestIndex = index;
600 closestDistance = tmpDistance;
601 }
602 }
603 if (index <= endIndex) {
604 ++index;
605 } else {
606 --index;
607 }
608 }
609 if (closestIndex > -1) {
610 return views.get(closestIndex);
611 }
612 }
613 return null;
614 }
615
616 /**
617 * Handles key events in a Workspace containing BubbleTextView.
618 */
619 static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700620 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
621 final CellLayout layout = (CellLayout) parent.getParent();
622 final Workspace workspace = (Workspace) layout.getParent();
623 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700624 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700625 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700626 int iconIndex = parent.indexOfChild(v);
627 int iconCount = parent.getChildCount();
628 int pageIndex = workspace.indexOfChild(layout);
629 int pageCount = workspace.getChildCount();
630
631 final int action = e.getAction();
632 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
633 boolean wasHandled = false;
634 switch (keyCode) {
635 case KeyEvent.KEYCODE_DPAD_LEFT:
636 if (handleKeyEvent) {
637 // Select the previous icon or the last icon on the previous page if possible
638 View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1);
639 if (newIcon != null) {
640 newIcon.requestFocus();
641 } else {
642 if (pageIndex > 0) {
643 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
644 newIcon = getBubbleTextViewInDirection(layout, parent,
645 parent.getChildCount(), -1);
646 if (newIcon != null) {
647 newIcon.requestFocus();
648 } else {
649 // Snap to the previous page
650 workspace.snapToPage(pageIndex - 1);
651 }
652 }
653 }
654 }
655 wasHandled = true;
656 break;
657 case KeyEvent.KEYCODE_DPAD_RIGHT:
658 if (handleKeyEvent) {
659 // Select the next icon or the first icon on the next page if possible
660 View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1);
661 if (newIcon != null) {
662 newIcon.requestFocus();
663 } else {
664 if (pageIndex < (pageCount - 1)) {
665 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
666 newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
667 if (newIcon != null) {
668 newIcon.requestFocus();
669 } else {
670 // Snap to the next page
671 workspace.snapToPage(pageIndex + 1);
672 }
673 }
674 }
675 }
676 wasHandled = true;
677 break;
678 case KeyEvent.KEYCODE_DPAD_UP:
679 if (handleKeyEvent) {
680 // Select the closest icon in the previous line, otherwise select the tab bar
681 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1);
682 if (newIcon != null) {
683 newIcon.requestFocus();
684 wasHandled = true;
685 } else {
686 tabs.requestFocus();
687 }
688 }
689 break;
690 case KeyEvent.KEYCODE_DPAD_DOWN:
691 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700692 // Select the closest icon in the next line, otherwise select the button bar
Winson Chung97d85d22011-04-13 11:27:36 -0700693 View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1);
694 if (newIcon != null) {
695 newIcon.requestFocus();
696 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700697 } else if (hotseat != null) {
698 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700699 }
700 }
701 break;
702 case KeyEvent.KEYCODE_PAGE_UP:
703 if (handleKeyEvent) {
704 // Select the first icon on the previous page or the first icon on this page
705 // if there is no previous page
706 if (pageIndex > 0) {
707 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
708 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
709 if (newIcon != null) {
710 newIcon.requestFocus();
711 } else {
712 // Snap to the previous page
713 workspace.snapToPage(pageIndex - 1);
714 }
715 } else {
716 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
717 if (newIcon != null) {
718 newIcon.requestFocus();
719 }
720 }
721 }
722 wasHandled = true;
723 break;
724 case KeyEvent.KEYCODE_PAGE_DOWN:
725 if (handleKeyEvent) {
726 // Select the first icon on the next page or the last icon on this page
727 // if there is no previous page
728 if (pageIndex < (pageCount - 1)) {
729 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
730 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
731 if (newIcon != null) {
732 newIcon.requestFocus();
733 } else {
734 // Snap to the next page
735 workspace.snapToPage(pageIndex + 1);
736 }
737 } else {
738 View newIcon = getBubbleTextViewInDirection(layout, parent,
739 parent.getChildCount(), -1);
740 if (newIcon != null) {
741 newIcon.requestFocus();
742 }
743 }
744 }
745 wasHandled = true;
746 break;
747 case KeyEvent.KEYCODE_MOVE_HOME:
748 if (handleKeyEvent) {
749 // Select the first icon on this page
750 View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1);
751 if (newIcon != null) {
752 newIcon.requestFocus();
753 }
754 }
755 wasHandled = true;
756 break;
757 case KeyEvent.KEYCODE_MOVE_END:
758 if (handleKeyEvent) {
759 // Select the last icon on this page
760 View newIcon = getBubbleTextViewInDirection(layout, parent,
761 parent.getChildCount(), -1);
762 if (newIcon != null) {
763 newIcon.requestFocus();
764 }
765 }
766 wasHandled = true;
767 break;
768 default: break;
769 }
770 return wasHandled;
771 }
772}