blob: 967b02ff46868c111c7d4f3a0108661ca4eee308 [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;
Adam Cohenac56cff2011-09-28 20:45:37 -070026import android.widget.TextView;
Winson Chung97d85d22011-04-13 11:27:36 -070027
28import com.android.launcher.R;
29
Winson Chungfaa13252011-06-13 18:15:54 -070030import java.util.ArrayList;
31import java.util.Collections;
32import java.util.Comparator;
33
Winson Chung97d85d22011-04-13 11:27:36 -070034/**
Winson Chung4d279d92011-07-21 11:46:32 -070035 * A keyboard listener we set on all the workspace icons.
36 */
Adam Cohenac56cff2011-09-28 20:45:37 -070037class IconKeyEventListener implements View.OnKeyListener {
Winson Chung4d279d92011-07-21 11:46:32 -070038 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070039 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
40 }
41}
42
43/**
44 * A keyboard listener we set on all the workspace icons.
45 */
46class FolderKeyEventListener implements View.OnKeyListener {
47 public boolean onKey(View v, int keyCode, KeyEvent event) {
48 return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
Winson Chung4d279d92011-07-21 11:46:32 -070049 }
50}
51
52/**
Winson Chung3d503fb2011-07-13 17:25:49 -070053 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070054 */
Adam Cohenac56cff2011-09-28 20:45:37 -070055class HotseatIconKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070056 public boolean onKey(View v, int keyCode, KeyEvent event) {
57 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070058 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070059 }
60}
61
62/**
Winson Chungfaa13252011-06-13 18:15:54 -070063 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070064 * market icon and vice versa.
65 */
Winson Chungfaa13252011-06-13 18:15:54 -070066class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070067 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070068 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070069 }
70}
71
72public class FocusHelper {
73 /**
74 * Private helper to get the parent TabHost in the view hiearchy.
75 */
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 /**
Winson Chungfaa13252011-06-13 18:15:54 -070085 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070086 */
Winson Chungfaa13252011-06-13 18:15:54 -070087 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070088 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 */
123 private static boolean isVisible(View v) {
124 return v.getVisibility() == View.VISIBLE;
125 }
126
127 /**
128 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700129 */
130 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
131 KeyEvent e) {
132
133 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
134 final ViewGroup container = (ViewGroup) parent.getParent();
135 final TabHost tabHost = findTabHostParent(container);
136 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
137 final int widgetIndex = parent.indexOfChild(w);
138 final int widgetCount = parent.getChildCount();
139 final int pageIndex = container.indexOfChild(parent);
140 final int pageCount = container.getChildCount();
141 final int cellCountX = parent.getCellCountX();
142 final int cellCountY = parent.getCellCountY();
143 final int x = widgetIndex % cellCountX;
144 final int y = widgetIndex / cellCountX;
145
146 final int action = e.getAction();
147 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
148 PagedViewGridLayout newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700149 // Now that we load items in the bg asynchronously, we can't just focus
150 // child siblings willy-nilly
151 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700152 boolean wasHandled = false;
153 switch (keyCode) {
154 case KeyEvent.KEYCODE_DPAD_LEFT:
155 if (handleKeyEvent) {
156 // Select the previous widget or the last widget on the previous page
157 if (widgetIndex > 0) {
158 parent.getChildAt(widgetIndex - 1).requestFocus();
159 } else {
160 if (pageIndex > 0) {
161 newParent = (PagedViewGridLayout)
162 container.getChildAt(pageIndex - 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700163 child = newParent.getChildAt(newParent.getChildCount() - 1);
164 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700165 }
166 }
167 }
168 wasHandled = true;
169 break;
170 case KeyEvent.KEYCODE_DPAD_RIGHT:
171 if (handleKeyEvent) {
172 // Select the next widget or the first widget on the next page
173 if (widgetIndex < (widgetCount - 1)) {
174 parent.getChildAt(widgetIndex + 1).requestFocus();
175 } else {
176 if (pageIndex < (pageCount - 1)) {
177 newParent = (PagedViewGridLayout)
178 container.getChildAt(pageIndex + 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700179 child = newParent.getChildAt(0);
180 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700181 }
182 }
183 }
184 wasHandled = true;
185 break;
186 case KeyEvent.KEYCODE_DPAD_UP:
187 if (handleKeyEvent) {
188 // Select the closest icon in the previous row, otherwise select the tab bar
189 if (y > 0) {
190 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700191 child = parent.getChildAt(newWidgetIndex);
192 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700193 } else {
194 tabs.requestFocus();
195 }
196 }
197 wasHandled = true;
198 break;
199 case KeyEvent.KEYCODE_DPAD_DOWN:
200 if (handleKeyEvent) {
201 // Select the closest icon in the previous row, otherwise do nothing
202 if (y < (cellCountY - 1)) {
203 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700204 child = parent.getChildAt(newWidgetIndex);
205 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700206 }
207 }
208 wasHandled = true;
209 break;
210 case KeyEvent.KEYCODE_ENTER:
211 case KeyEvent.KEYCODE_DPAD_CENTER:
212 if (handleKeyEvent) {
213 // Simulate a click on the widget
214 View.OnClickListener clickListener = (View.OnClickListener) container;
215 clickListener.onClick(w);
216 }
217 wasHandled = true;
218 break;
219 case KeyEvent.KEYCODE_PAGE_UP:
220 if (handleKeyEvent) {
221 // Select the first item on the previous page, or the first item on this page
222 // if there is no previous page
223 if (pageIndex > 0) {
224 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex - 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700225 child = newParent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700226 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700227 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700228 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700229 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700230 }
231 wasHandled = true;
232 break;
233 case KeyEvent.KEYCODE_PAGE_DOWN:
234 if (handleKeyEvent) {
235 // Select the first item on the next page, or the last item on this page
236 // if there is no next page
237 if (pageIndex < (pageCount - 1)) {
238 newParent = (PagedViewGridLayout) container.getChildAt(pageIndex + 1);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700239 child = newParent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700240 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700241 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700242 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700243 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700244 }
245 wasHandled = true;
246 break;
247 case KeyEvent.KEYCODE_MOVE_HOME:
248 if (handleKeyEvent) {
249 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700250 child = parent.getChildAt(0);
251 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700252 }
253 wasHandled = true;
254 break;
255 case KeyEvent.KEYCODE_MOVE_END:
256 if (handleKeyEvent) {
257 // Select the last item on this page
258 parent.getChildAt(widgetCount - 1).requestFocus();
259 }
260 wasHandled = true;
261 break;
262 default: break;
263 }
264 return wasHandled;
265 }
266
267 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700268 * Private helper method to get the PagedViewCellLayoutChildren given a PagedViewCellLayout
269 * index.
270 */
271 private static PagedViewCellLayoutChildren getPagedViewCellLayoutChildrenForIndex(
272 ViewGroup container, int i) {
273 ViewGroup parent = (ViewGroup) container.getChildAt(i);
274 return (PagedViewCellLayoutChildren) parent.getChildAt(0);
275 }
276
277 /**
278 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
279 */
280 static boolean handlePagedViewIconKeyEvent(PagedViewIcon v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700281 final PagedViewCellLayoutChildren parent = (PagedViewCellLayoutChildren) v.getParent();
282 final PagedViewCellLayout parentLayout = (PagedViewCellLayout) parent.getParent();
283 // Note we have an extra parent because of the
284 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
285 final ViewGroup container = (ViewGroup) parentLayout.getParent();
286 final TabHost tabHost = findTabHostParent(container);
287 final TabWidget tabs = (TabWidget) tabHost.findViewById(com.android.internal.R.id.tabs);
Winson Chung90576b52011-09-27 17:45:27 -0700288 final int iconIndex = parent.indexOfChild(v);
Winson Chung97d85d22011-04-13 11:27:36 -0700289 final int widgetCount = parent.getChildCount();
290 final int pageIndex = container.indexOfChild(parentLayout);
291 final int pageCount = container.getChildCount();
292 final int cellCountX = parentLayout.getCellCountX();
293 final int cellCountY = parentLayout.getCellCountY();
Winson Chung90576b52011-09-27 17:45:27 -0700294 final int x = iconIndex % cellCountX;
295 final int y = iconIndex / cellCountX;
Winson Chung97d85d22011-04-13 11:27:36 -0700296
297 final int action = e.getAction();
298 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
299 PagedViewCellLayoutChildren newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700300 // Side pages do not always load synchronously, so check before focusing child siblings
301 // willy-nilly
302 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700303 boolean wasHandled = false;
304 switch (keyCode) {
305 case KeyEvent.KEYCODE_DPAD_LEFT:
306 if (handleKeyEvent) {
307 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700308 if (iconIndex > 0) {
309 parent.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700310 } else {
311 if (pageIndex > 0) {
312 newParent = getPagedViewCellLayoutChildrenForIndex(container,
313 pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700314 if (newParent != null) {
315 child = newParent.getChildAt(newParent.getChildCount() - 1);
316 if (child != null) child.requestFocus();
317 }
Winson Chung97d85d22011-04-13 11:27:36 -0700318 }
319 }
320 }
321 wasHandled = true;
322 break;
323 case KeyEvent.KEYCODE_DPAD_RIGHT:
324 if (handleKeyEvent) {
325 // Select the next icon or the first icon on the next page
Winson Chung90576b52011-09-27 17:45:27 -0700326 if (iconIndex < (widgetCount - 1)) {
327 parent.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700328 } else {
329 if (pageIndex < (pageCount - 1)) {
330 newParent = getPagedViewCellLayoutChildrenForIndex(container,
331 pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700332 if (newParent != null) {
333 child = newParent.getChildAt(0);
334 if (child != null) child.requestFocus();
335 }
Winson Chung97d85d22011-04-13 11:27:36 -0700336 }
337 }
338 }
339 wasHandled = true;
340 break;
341 case KeyEvent.KEYCODE_DPAD_UP:
342 if (handleKeyEvent) {
343 // Select the closest icon in the previous row, otherwise select the tab bar
344 if (y > 0) {
Winson Chung90576b52011-09-27 17:45:27 -0700345 int newiconIndex = ((y - 1) * cellCountX) + x;
346 parent.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700347 } else {
348 tabs.requestFocus();
349 }
350 }
351 wasHandled = true;
352 break;
353 case KeyEvent.KEYCODE_DPAD_DOWN:
354 if (handleKeyEvent) {
355 // Select the closest icon in the previous row, otherwise do nothing
356 if (y < (cellCountY - 1)) {
Winson Chung90576b52011-09-27 17:45:27 -0700357 int newiconIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
358 parent.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700359 }
360 }
361 wasHandled = true;
362 break;
363 case KeyEvent.KEYCODE_ENTER:
364 case KeyEvent.KEYCODE_DPAD_CENTER:
365 if (handleKeyEvent) {
366 // Simulate a click on the icon
367 View.OnClickListener clickListener = (View.OnClickListener) container;
368 clickListener.onClick(v);
369 }
370 wasHandled = true;
371 break;
372 case KeyEvent.KEYCODE_PAGE_UP:
373 if (handleKeyEvent) {
374 // Select the first icon on the previous page, or the first icon on this page
375 // if there is no previous page
376 if (pageIndex > 0) {
377 newParent = getPagedViewCellLayoutChildrenForIndex(container,
378 pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700379 if (newParent != null) {
380 child = newParent.getChildAt(0);
381 if (child != null) child.requestFocus();
382 }
Winson Chung97d85d22011-04-13 11:27:36 -0700383 } else {
384 parent.getChildAt(0).requestFocus();
385 }
386 }
387 wasHandled = true;
388 break;
389 case KeyEvent.KEYCODE_PAGE_DOWN:
390 if (handleKeyEvent) {
391 // Select the first icon on the next page, or the last icon on this page
392 // if there is no next page
393 if (pageIndex < (pageCount - 1)) {
394 newParent = getPagedViewCellLayoutChildrenForIndex(container,
395 pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700396 if (newParent != null) {
397 child = newParent.getChildAt(0);
398 if (child != null) child.requestFocus();
399 }
Winson Chung97d85d22011-04-13 11:27:36 -0700400 } else {
401 parent.getChildAt(widgetCount - 1).requestFocus();
402 }
403 }
404 wasHandled = true;
405 break;
406 case KeyEvent.KEYCODE_MOVE_HOME:
407 if (handleKeyEvent) {
408 // Select the first icon on this page
409 parent.getChildAt(0).requestFocus();
410 }
411 wasHandled = true;
412 break;
413 case KeyEvent.KEYCODE_MOVE_END:
414 if (handleKeyEvent) {
415 // Select the last icon on this page
416 parent.getChildAt(widgetCount - 1).requestFocus();
417 }
418 wasHandled = true;
419 break;
420 default: break;
421 }
422 return wasHandled;
423 }
424
425 /**
426 * Handles key events in the tab widget.
427 */
428 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Michael Jurkaa2eb1702011-05-12 14:57:05 -0700429 if (!LauncherApplication.isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700430
431 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
432 final TabHost tabHost = findTabHostParent(parent);
433 final ViewGroup contents = (ViewGroup)
434 tabHost.findViewById(com.android.internal.R.id.tabcontent);
435 final int tabCount = parent.getTabCount();
436 final int tabIndex = parent.getChildTabIndex(v);
437
438 final int action = e.getAction();
439 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
440 boolean wasHandled = false;
441 switch (keyCode) {
442 case KeyEvent.KEYCODE_DPAD_LEFT:
443 if (handleKeyEvent) {
444 // Select the previous tab
445 if (tabIndex > 0) {
446 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
447 }
448 }
449 wasHandled = true;
450 break;
451 case KeyEvent.KEYCODE_DPAD_RIGHT:
452 if (handleKeyEvent) {
453 // Select the next tab, or if the last tab has a focus right id, select that
454 if (tabIndex < (tabCount - 1)) {
455 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
456 } else {
457 if (v.getNextFocusRightId() != View.NO_ID) {
458 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
459 }
460 }
461 }
462 wasHandled = true;
463 break;
464 case KeyEvent.KEYCODE_DPAD_UP:
465 // Do nothing
466 wasHandled = true;
467 break;
468 case KeyEvent.KEYCODE_DPAD_DOWN:
469 if (handleKeyEvent) {
470 // Select the content view
471 contents.requestFocus();
472 }
473 wasHandled = true;
474 break;
475 default: break;
476 }
477 return wasHandled;
478 }
479
480 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700481 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700482 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700483 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700484 final ViewGroup parent = (ViewGroup) v.getParent();
485 final ViewGroup launcher = (ViewGroup) parent.getParent();
486 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
487 final int buttonIndex = parent.indexOfChild(v);
488 final int buttonCount = parent.getChildCount();
489 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700490
491 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700492 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700493 // is to ensure that accessibility consistency is maintained across rotations.
494
495 final int action = e.getAction();
496 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
497 boolean wasHandled = false;
498 switch (keyCode) {
499 case KeyEvent.KEYCODE_DPAD_LEFT:
500 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700501 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700502 if (buttonIndex > 0) {
503 parent.getChildAt(buttonIndex - 1).requestFocus();
504 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700505 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700506 }
507 }
508 wasHandled = true;
509 break;
510 case KeyEvent.KEYCODE_DPAD_RIGHT:
511 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700512 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700513 if (buttonIndex < (buttonCount - 1)) {
514 parent.getChildAt(buttonIndex + 1).requestFocus();
515 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700516 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700517 }
518 }
519 wasHandled = true;
520 break;
521 case KeyEvent.KEYCODE_DPAD_UP:
522 if (handleKeyEvent) {
523 // Select the first bubble text view in the current page of the workspace
524 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
525 final CellLayoutChildren children = layout.getChildrenLayout();
Adam Cohenac56cff2011-09-28 20:45:37 -0700526 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700527 if (newIcon != null) {
528 newIcon.requestFocus();
529 } else {
530 workspace.requestFocus();
531 }
532 }
533 wasHandled = true;
534 break;
535 case KeyEvent.KEYCODE_DPAD_DOWN:
536 // Do nothing
537 wasHandled = true;
538 break;
539 default: break;
540 }
541 return wasHandled;
542 }
543
544 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700545 * Private helper method to get the CellLayoutChildren given a CellLayout index.
546 */
547 private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
548 ViewGroup parent = (ViewGroup) container.getChildAt(i);
549 return (CellLayoutChildren) parent.getChildAt(0);
550 }
551
552 /**
553 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
554 * from top left to bottom right.
555 */
556 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
557 ViewGroup parent) {
558 // First we order each the CellLayout children by their x,y coordinates
559 final int cellCountX = layout.getCountX();
560 final int count = parent.getChildCount();
561 ArrayList<View> views = new ArrayList<View>();
562 for (int j = 0; j < count; ++j) {
563 views.add(parent.getChildAt(j));
564 }
565 Collections.sort(views, new Comparator<View>() {
566 @Override
567 public int compare(View lhs, View rhs) {
568 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
569 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
570 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
571 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
572 return lvIndex - rvIndex;
573 }
574 });
575 return views;
576 }
577 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700578 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
579 * direction delta.
580 *
Winson Chung97d85d22011-04-13 11:27:36 -0700581 * @param delta either -1 or 1 depending on the direction we want to search
582 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700583 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700584 // Then we find the next BubbleTextView offset by delta from i
585 final int count = views.size();
586 int newI = i + delta;
587 while (0 <= newI && newI < count) {
588 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700589 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700590 return newV;
591 }
592 newI += delta;
593 }
594 return null;
595 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700596 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700597 int delta) {
598 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700599 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700600 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700601 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700602 int delta) {
603 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700604 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700605 }
606 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700607 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
608 * delta on the next line.
609 *
Winson Chung97d85d22011-04-13 11:27:36 -0700610 * @param delta either -1 or 1 depending on the line and direction we want to search
611 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700612 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700613 int lineDelta) {
614 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
615 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
616 final int cellCountX = layout.getCountX();
617 final int cellCountY = layout.getCountY();
618 final int row = lp.cellY;
619 final int newRow = row + lineDelta;
620 if (0 <= newRow && newRow < cellCountY) {
621 float closestDistance = Float.MAX_VALUE;
622 int closestIndex = -1;
623 int index = views.indexOf(v);
624 int endIndex = (lineDelta < 0) ? -1 : views.size();
625 while (index != endIndex) {
626 View newV = views.get(index);
627 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
628 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700629 if (satisfiesRow &&
630 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700631 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
632 Math.pow(tmpLp.cellY - lp.cellY, 2));
633 if (tmpDistance < closestDistance) {
634 closestIndex = index;
635 closestDistance = tmpDistance;
636 }
637 }
638 if (index <= endIndex) {
639 ++index;
640 } else {
641 --index;
642 }
643 }
644 if (closestIndex > -1) {
645 return views.get(closestIndex);
646 }
647 }
648 return null;
649 }
650
651 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700652 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700653 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700654 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -0700655 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
656 final CellLayout layout = (CellLayout) parent.getParent();
657 final Workspace workspace = (Workspace) layout.getParent();
658 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700659 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700660 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700661 int pageIndex = workspace.indexOfChild(layout);
662 int pageCount = workspace.getChildCount();
663
664 final int action = e.getAction();
665 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
666 boolean wasHandled = false;
667 switch (keyCode) {
668 case KeyEvent.KEYCODE_DPAD_LEFT:
669 if (handleKeyEvent) {
670 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700671 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700672 if (newIcon != null) {
673 newIcon.requestFocus();
674 } else {
675 if (pageIndex > 0) {
676 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700677 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700678 parent.getChildCount(), -1);
679 if (newIcon != null) {
680 newIcon.requestFocus();
681 } else {
682 // Snap to the previous page
683 workspace.snapToPage(pageIndex - 1);
684 }
685 }
686 }
687 }
688 wasHandled = true;
689 break;
690 case KeyEvent.KEYCODE_DPAD_RIGHT:
691 if (handleKeyEvent) {
692 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700693 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700694 if (newIcon != null) {
695 newIcon.requestFocus();
696 } else {
697 if (pageIndex < (pageCount - 1)) {
698 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700699 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700700 if (newIcon != null) {
701 newIcon.requestFocus();
702 } else {
703 // Snap to the next page
704 workspace.snapToPage(pageIndex + 1);
705 }
706 }
707 }
708 }
709 wasHandled = true;
710 break;
711 case KeyEvent.KEYCODE_DPAD_UP:
712 if (handleKeyEvent) {
713 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700714 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700715 if (newIcon != null) {
716 newIcon.requestFocus();
717 wasHandled = true;
718 } else {
719 tabs.requestFocus();
720 }
721 }
722 break;
723 case KeyEvent.KEYCODE_DPAD_DOWN:
724 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700725 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700726 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700727 if (newIcon != null) {
728 newIcon.requestFocus();
729 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700730 } else if (hotseat != null) {
731 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700732 }
733 }
734 break;
735 case KeyEvent.KEYCODE_PAGE_UP:
736 if (handleKeyEvent) {
737 // Select the first icon on the previous page or the first icon on this page
738 // if there is no previous page
739 if (pageIndex > 0) {
740 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700741 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700742 if (newIcon != null) {
743 newIcon.requestFocus();
744 } else {
745 // Snap to the previous page
746 workspace.snapToPage(pageIndex - 1);
747 }
748 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700749 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700750 if (newIcon != null) {
751 newIcon.requestFocus();
752 }
753 }
754 }
755 wasHandled = true;
756 break;
757 case KeyEvent.KEYCODE_PAGE_DOWN:
758 if (handleKeyEvent) {
759 // Select the first icon on the next page or the last icon on this page
760 // if there is no previous page
761 if (pageIndex < (pageCount - 1)) {
762 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700763 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700764 if (newIcon != null) {
765 newIcon.requestFocus();
766 } else {
767 // Snap to the next page
768 workspace.snapToPage(pageIndex + 1);
769 }
770 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700771 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700772 parent.getChildCount(), -1);
773 if (newIcon != null) {
774 newIcon.requestFocus();
775 }
776 }
777 }
778 wasHandled = true;
779 break;
780 case KeyEvent.KEYCODE_MOVE_HOME:
781 if (handleKeyEvent) {
782 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700783 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700784 if (newIcon != null) {
785 newIcon.requestFocus();
786 }
787 }
788 wasHandled = true;
789 break;
790 case KeyEvent.KEYCODE_MOVE_END:
791 if (handleKeyEvent) {
792 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700793 View newIcon = getIconInDirection(layout, parent,
794 parent.getChildCount(), -1);
795 if (newIcon != null) {
796 newIcon.requestFocus();
797 }
798 }
799 wasHandled = true;
800 break;
801 default: break;
802 }
803 return wasHandled;
804 }
805
806 /**
807 * Handles key events for items in a Folder.
808 */
809 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
810 CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
811 final CellLayout layout = (CellLayout) parent.getParent();
812 final Folder folder = (Folder) layout.getParent();
813 View title = folder.mFolderName;
814
815 final int action = e.getAction();
816 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
817 boolean wasHandled = false;
818 switch (keyCode) {
819 case KeyEvent.KEYCODE_DPAD_LEFT:
820 if (handleKeyEvent) {
821 // Select the previous icon
822 View newIcon = getIconInDirection(layout, parent, v, -1);
823 if (newIcon != null) {
824 newIcon.requestFocus();
825 }
826 }
827 wasHandled = true;
828 break;
829 case KeyEvent.KEYCODE_DPAD_RIGHT:
830 if (handleKeyEvent) {
831 // Select the next icon
832 View newIcon = getIconInDirection(layout, parent, v, 1);
833 if (newIcon != null) {
834 newIcon.requestFocus();
835 } else {
836 title.requestFocus();
837 }
838 }
839 wasHandled = true;
840 break;
841 case KeyEvent.KEYCODE_DPAD_UP:
842 if (handleKeyEvent) {
843 // Select the closest icon in the previous line
844 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
845 if (newIcon != null) {
846 newIcon.requestFocus();
847 }
848 }
849 wasHandled = true;
850 break;
851 case KeyEvent.KEYCODE_DPAD_DOWN:
852 if (handleKeyEvent) {
853 // Select the closest icon in the next line
854 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
855 if (newIcon != null) {
856 newIcon.requestFocus();
857 } else {
858 title.requestFocus();
859 }
860 }
861 wasHandled = true;
862 break;
863 case KeyEvent.KEYCODE_MOVE_HOME:
864 if (handleKeyEvent) {
865 // Select the first icon on this page
866 View newIcon = getIconInDirection(layout, parent, -1, 1);
867 if (newIcon != null) {
868 newIcon.requestFocus();
869 }
870 }
871 wasHandled = true;
872 break;
873 case KeyEvent.KEYCODE_MOVE_END:
874 if (handleKeyEvent) {
875 // Select the last icon on this page
876 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700877 parent.getChildCount(), -1);
878 if (newIcon != null) {
879 newIcon.requestFocus();
880 }
881 }
882 wasHandled = true;
883 break;
884 default: break;
885 }
886 return wasHandled;
887 }
888}