blob: a591433f2e058fb1a9d6165a49ce5dd729a884ab [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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
Winson Chung97d85d22011-04-13 11:27:36 -070018
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
Winson Chungfaa13252011-06-13 18:15:54 -070027import java.util.ArrayList;
28import java.util.Collections;
29import java.util.Comparator;
30
Winson Chung97d85d22011-04-13 11:27:36 -070031/**
Winson Chung4d279d92011-07-21 11:46:32 -070032 * A keyboard listener we set on all the workspace icons.
33 */
Adam Cohenac56cff2011-09-28 20:45:37 -070034class IconKeyEventListener implements View.OnKeyListener {
Winson Chung4d279d92011-07-21 11:46:32 -070035 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070036 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
37 }
38}
39
40/**
41 * A keyboard listener we set on all the workspace icons.
42 */
43class FolderKeyEventListener implements View.OnKeyListener {
44 public boolean onKey(View v, int keyCode, KeyEvent event) {
45 return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
Winson Chung4d279d92011-07-21 11:46:32 -070046 }
47}
48
49/**
Winson Chung3d503fb2011-07-13 17:25:49 -070050 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070051 */
Adam Cohenac56cff2011-09-28 20:45:37 -070052class HotseatIconKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070053 public boolean onKey(View v, int keyCode, KeyEvent event) {
54 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070055 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070056 }
57}
58
59/**
Winson Chungfaa13252011-06-13 18:15:54 -070060 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070061 * market icon and vice versa.
62 */
Winson Chungfaa13252011-06-13 18:15:54 -070063class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070064 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070065 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070066 }
67}
68
69public class FocusHelper {
70 /**
71 * Private helper to get the parent TabHost in the view hiearchy.
72 */
73 private static TabHost findTabHostParent(View v) {
74 ViewParent p = v.getParent();
75 while (p != null && !(p instanceof TabHost)) {
76 p = p.getParent();
77 }
78 return (TabHost) p;
79 }
80
81 /**
Winson Chungfaa13252011-06-13 18:15:54 -070082 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070083 */
Winson Chungfaa13252011-06-13 18:15:54 -070084 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070085 final TabHost tabHost = findTabHostParent(v);
Michael Jurka8b805b12012-04-18 14:23:14 -070086 final ViewGroup contents = tabHost.getTabContentView();
Winson Chung97d85d22011-04-13 11:27:36 -070087 final View shop = tabHost.findViewById(R.id.market_button);
88
89 final int action = e.getAction();
90 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
91 boolean wasHandled = false;
92 switch (keyCode) {
93 case KeyEvent.KEYCODE_DPAD_RIGHT:
94 if (handleKeyEvent) {
95 // Select the shop button if we aren't on it
96 if (v != shop) {
97 shop.requestFocus();
98 }
99 }
100 wasHandled = true;
101 break;
102 case KeyEvent.KEYCODE_DPAD_DOWN:
103 if (handleKeyEvent) {
104 // Select the content view (down is handled by the tab key handler otherwise)
105 if (v == shop) {
106 contents.requestFocus();
107 wasHandled = true;
108 }
109 }
110 break;
111 default: break;
112 }
113 return wasHandled;
114 }
115
116 /**
Adam Cohenae4f1552011-10-20 00:15:42 -0700117 * Returns the Viewgroup containing page contents for the page at the index specified.
118 */
119 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
120 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
121 if (page instanceof PagedViewCellLayout) {
122 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
123 page = (ViewGroup) page.getChildAt(0);
124 }
125 return page;
126 }
127
128 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700129 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700130 */
131 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
132 KeyEvent e) {
133
134 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -0700135 final PagedView container = (PagedView) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -0700136 final TabHost tabHost = findTabHostParent(container);
Michael Jurka8b805b12012-04-18 14:23:14 -0700137 final TabWidget tabs = tabHost.getTabWidget();
Winson Chung4e6a9762011-05-09 11:56:34 -0700138 final int widgetIndex = parent.indexOfChild(w);
139 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700140 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -0700141 final int pageCount = container.getChildCount();
142 final int cellCountX = parent.getCellCountX();
143 final int cellCountY = parent.getCellCountY();
144 final int x = widgetIndex % cellCountX;
145 final int y = widgetIndex / cellCountX;
146
147 final int action = e.getAction();
148 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700149 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700150 // Now that we load items in the bg asynchronously, we can't just focus
151 // child siblings willy-nilly
152 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700153 boolean wasHandled = false;
154 switch (keyCode) {
155 case KeyEvent.KEYCODE_DPAD_LEFT:
156 if (handleKeyEvent) {
157 // Select the previous widget or the last widget on the previous page
158 if (widgetIndex > 0) {
159 parent.getChildAt(widgetIndex - 1).requestFocus();
160 } else {
161 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700162 newParent = getAppsCustomizePage(container, pageIndex - 1);
163 if (newParent != null) {
164 child = newParent.getChildAt(newParent.getChildCount() - 1);
165 if (child != null) child.requestFocus();
166 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700167 }
168 }
169 }
170 wasHandled = true;
171 break;
172 case KeyEvent.KEYCODE_DPAD_RIGHT:
173 if (handleKeyEvent) {
174 // Select the next widget or the first widget on the next page
175 if (widgetIndex < (widgetCount - 1)) {
176 parent.getChildAt(widgetIndex + 1).requestFocus();
177 } else {
178 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700179 newParent = getAppsCustomizePage(container, pageIndex + 1);
180 if (newParent != null) {
181 child = newParent.getChildAt(0);
182 if (child != null) child.requestFocus();
183 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700184 }
185 }
186 }
187 wasHandled = true;
188 break;
189 case KeyEvent.KEYCODE_DPAD_UP:
190 if (handleKeyEvent) {
191 // Select the closest icon in the previous row, otherwise select the tab bar
192 if (y > 0) {
193 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700194 child = parent.getChildAt(newWidgetIndex);
195 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700196 } else {
197 tabs.requestFocus();
198 }
199 }
200 wasHandled = true;
201 break;
202 case KeyEvent.KEYCODE_DPAD_DOWN:
203 if (handleKeyEvent) {
204 // Select the closest icon in the previous row, otherwise do nothing
205 if (y < (cellCountY - 1)) {
206 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700207 child = parent.getChildAt(newWidgetIndex);
208 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700209 }
210 }
211 wasHandled = true;
212 break;
213 case KeyEvent.KEYCODE_ENTER:
214 case KeyEvent.KEYCODE_DPAD_CENTER:
215 if (handleKeyEvent) {
216 // Simulate a click on the widget
217 View.OnClickListener clickListener = (View.OnClickListener) container;
218 clickListener.onClick(w);
219 }
220 wasHandled = true;
221 break;
222 case KeyEvent.KEYCODE_PAGE_UP:
223 if (handleKeyEvent) {
224 // Select the first item on the previous page, or the first item on this page
225 // if there is no previous page
226 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700227 newParent = getAppsCustomizePage(container, pageIndex - 1);
228 if (newParent != null) {
229 child = newParent.getChildAt(0);
230 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700231 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700232 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700233 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700234 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700235 }
236 wasHandled = true;
237 break;
238 case KeyEvent.KEYCODE_PAGE_DOWN:
239 if (handleKeyEvent) {
240 // Select the first item on the next page, or the last item on this page
241 // if there is no next page
242 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700243 newParent = getAppsCustomizePage(container, pageIndex + 1);
244 if (newParent != null) {
245 child = newParent.getChildAt(0);
246 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700247 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700248 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700249 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700250 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700251 }
252 wasHandled = true;
253 break;
254 case KeyEvent.KEYCODE_MOVE_HOME:
255 if (handleKeyEvent) {
256 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700257 child = parent.getChildAt(0);
258 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700259 }
260 wasHandled = true;
261 break;
262 case KeyEvent.KEYCODE_MOVE_END:
263 if (handleKeyEvent) {
264 // Select the last item on this page
265 parent.getChildAt(widgetCount - 1).requestFocus();
266 }
267 wasHandled = true;
268 break;
269 default: break;
270 }
271 return wasHandled;
272 }
273
274 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700275 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
276 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700277 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
278 ViewGroup parentLayout;
279 ViewGroup itemContainer;
280 int countX;
281 int countY;
282 if (v.getParent() instanceof PagedViewCellLayoutChildren) {
283 itemContainer = (ViewGroup) v.getParent();
284 parentLayout = (ViewGroup) itemContainer.getParent();
285 countX = ((PagedViewCellLayout) parentLayout).getCellCountX();
286 countY = ((PagedViewCellLayout) parentLayout).getCellCountY();
287 } else {
288 itemContainer = parentLayout = (ViewGroup) v.getParent();
289 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
290 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
291 }
292
Winson Chung97d85d22011-04-13 11:27:36 -0700293 // Note we have an extra parent because of the
294 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700295 final PagedView container = (PagedView) parentLayout.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700296 final TabHost tabHost = findTabHostParent(container);
Michael Jurka8b805b12012-04-18 14:23:14 -0700297 final TabWidget tabs = tabHost.getTabWidget();
Adam Cohenae4f1552011-10-20 00:15:42 -0700298 final int iconIndex = itemContainer.indexOfChild(v);
299 final int itemCount = itemContainer.getChildCount();
300 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700301 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700302
303 final int x = iconIndex % countX;
304 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700305
306 final int action = e.getAction();
307 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700308 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700309 // Side pages do not always load synchronously, so check before focusing child siblings
310 // willy-nilly
311 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700312 boolean wasHandled = false;
313 switch (keyCode) {
314 case KeyEvent.KEYCODE_DPAD_LEFT:
315 if (handleKeyEvent) {
316 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700317 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700318 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700319 } else {
320 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700321 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700322 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700323 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700324 child = newParent.getChildAt(newParent.getChildCount() - 1);
325 if (child != null) child.requestFocus();
326 }
Winson Chung97d85d22011-04-13 11:27:36 -0700327 }
328 }
329 }
330 wasHandled = true;
331 break;
332 case KeyEvent.KEYCODE_DPAD_RIGHT:
333 if (handleKeyEvent) {
334 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700335 if (iconIndex < (itemCount - 1)) {
336 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700337 } else {
338 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700339 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700340 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700341 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700342 child = newParent.getChildAt(0);
343 if (child != null) child.requestFocus();
344 }
Winson Chung97d85d22011-04-13 11:27:36 -0700345 }
346 }
347 }
348 wasHandled = true;
349 break;
350 case KeyEvent.KEYCODE_DPAD_UP:
351 if (handleKeyEvent) {
352 // Select the closest icon in the previous row, otherwise select the tab bar
353 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700354 int newiconIndex = ((y - 1) * countX) + x;
355 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700356 } else {
357 tabs.requestFocus();
358 }
359 }
360 wasHandled = true;
361 break;
362 case KeyEvent.KEYCODE_DPAD_DOWN:
363 if (handleKeyEvent) {
364 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700365 if (y < (countY - 1)) {
366 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
367 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700368 }
369 }
370 wasHandled = true;
371 break;
372 case KeyEvent.KEYCODE_ENTER:
373 case KeyEvent.KEYCODE_DPAD_CENTER:
374 if (handleKeyEvent) {
375 // Simulate a click on the icon
376 View.OnClickListener clickListener = (View.OnClickListener) container;
377 clickListener.onClick(v);
378 }
379 wasHandled = true;
380 break;
381 case KeyEvent.KEYCODE_PAGE_UP:
382 if (handleKeyEvent) {
383 // Select the first icon on the previous page, or the first icon on this page
384 // if there is no previous page
385 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700386 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700387 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700388 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700389 child = newParent.getChildAt(0);
390 if (child != null) child.requestFocus();
391 }
Winson Chung97d85d22011-04-13 11:27:36 -0700392 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700393 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700394 }
395 }
396 wasHandled = true;
397 break;
398 case KeyEvent.KEYCODE_PAGE_DOWN:
399 if (handleKeyEvent) {
400 // Select the first icon on the next page, or the last icon on this page
401 // if there is no next page
402 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700403 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700404 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700405 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700406 child = newParent.getChildAt(0);
407 if (child != null) child.requestFocus();
408 }
Winson Chung97d85d22011-04-13 11:27:36 -0700409 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700410 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700411 }
412 }
413 wasHandled = true;
414 break;
415 case KeyEvent.KEYCODE_MOVE_HOME:
416 if (handleKeyEvent) {
417 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700418 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700419 }
420 wasHandled = true;
421 break;
422 case KeyEvent.KEYCODE_MOVE_END:
423 if (handleKeyEvent) {
424 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700425 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700426 }
427 wasHandled = true;
428 break;
429 default: break;
430 }
431 return wasHandled;
432 }
433
434 /**
435 * Handles key events in the tab widget.
436 */
437 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400438 if (!LauncherAppState.getInstance().isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700439
440 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
441 final TabHost tabHost = findTabHostParent(parent);
Michael Jurka8b805b12012-04-18 14:23:14 -0700442 final ViewGroup contents = tabHost.getTabContentView();
Winson Chung97d85d22011-04-13 11:27:36 -0700443 final int tabCount = parent.getTabCount();
444 final int tabIndex = parent.getChildTabIndex(v);
445
446 final int action = e.getAction();
447 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
448 boolean wasHandled = false;
449 switch (keyCode) {
450 case KeyEvent.KEYCODE_DPAD_LEFT:
451 if (handleKeyEvent) {
452 // Select the previous tab
453 if (tabIndex > 0) {
454 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
455 }
456 }
457 wasHandled = true;
458 break;
459 case KeyEvent.KEYCODE_DPAD_RIGHT:
460 if (handleKeyEvent) {
461 // Select the next tab, or if the last tab has a focus right id, select that
462 if (tabIndex < (tabCount - 1)) {
463 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
464 } else {
465 if (v.getNextFocusRightId() != View.NO_ID) {
466 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
467 }
468 }
469 }
470 wasHandled = true;
471 break;
472 case KeyEvent.KEYCODE_DPAD_UP:
473 // Do nothing
474 wasHandled = true;
475 break;
476 case KeyEvent.KEYCODE_DPAD_DOWN:
477 if (handleKeyEvent) {
478 // Select the content view
479 contents.requestFocus();
480 }
481 wasHandled = true;
482 break;
483 default: break;
484 }
485 return wasHandled;
486 }
487
488 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700489 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700490 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700491 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700492 final ViewGroup parent = (ViewGroup) v.getParent();
493 final ViewGroup launcher = (ViewGroup) parent.getParent();
494 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
495 final int buttonIndex = parent.indexOfChild(v);
496 final int buttonCount = parent.getChildCount();
497 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700498
499 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700500 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700501 // is to ensure that accessibility consistency is maintained across rotations.
502
503 final int action = e.getAction();
504 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
505 boolean wasHandled = false;
506 switch (keyCode) {
507 case KeyEvent.KEYCODE_DPAD_LEFT:
508 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700509 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700510 if (buttonIndex > 0) {
511 parent.getChildAt(buttonIndex - 1).requestFocus();
512 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700513 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700514 }
515 }
516 wasHandled = true;
517 break;
518 case KeyEvent.KEYCODE_DPAD_RIGHT:
519 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700520 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700521 if (buttonIndex < (buttonCount - 1)) {
522 parent.getChildAt(buttonIndex + 1).requestFocus();
523 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700524 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700525 }
526 }
527 wasHandled = true;
528 break;
529 case KeyEvent.KEYCODE_DPAD_UP:
530 if (handleKeyEvent) {
531 // Select the first bubble text view in the current page of the workspace
532 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700533 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
Adam Cohenac56cff2011-09-28 20:45:37 -0700534 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700535 if (newIcon != null) {
536 newIcon.requestFocus();
537 } else {
538 workspace.requestFocus();
539 }
540 }
541 wasHandled = true;
542 break;
543 case KeyEvent.KEYCODE_DPAD_DOWN:
544 // Do nothing
545 wasHandled = true;
546 break;
547 default: break;
548 }
549 return wasHandled;
550 }
551
552 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700553 * Private helper method to get the CellLayoutChildren given a CellLayout index.
554 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700555 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
556 ViewGroup container, int i) {
Winson Chung97d85d22011-04-13 11:27:36 -0700557 ViewGroup parent = (ViewGroup) container.getChildAt(i);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700558 return (ShortcutAndWidgetContainer) parent.getChildAt(0);
Winson Chung97d85d22011-04-13 11:27:36 -0700559 }
560
561 /**
562 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
563 * from top left to bottom right.
564 */
565 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
566 ViewGroup parent) {
567 // First we order each the CellLayout children by their x,y coordinates
568 final int cellCountX = layout.getCountX();
569 final int count = parent.getChildCount();
570 ArrayList<View> views = new ArrayList<View>();
571 for (int j = 0; j < count; ++j) {
572 views.add(parent.getChildAt(j));
573 }
574 Collections.sort(views, new Comparator<View>() {
575 @Override
576 public int compare(View lhs, View rhs) {
577 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
578 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
579 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
580 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
581 return lvIndex - rvIndex;
582 }
583 });
584 return views;
585 }
586 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700587 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
588 * direction delta.
589 *
Winson Chung97d85d22011-04-13 11:27:36 -0700590 * @param delta either -1 or 1 depending on the direction we want to search
591 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700592 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700593 // Then we find the next BubbleTextView offset by delta from i
594 final int count = views.size();
595 int newI = i + delta;
596 while (0 <= newI && newI < count) {
597 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700598 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700599 return newV;
600 }
601 newI += delta;
602 }
603 return null;
604 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700605 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700606 int delta) {
607 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700608 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700609 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700610 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700611 int delta) {
612 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700613 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700614 }
615 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700616 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
617 * delta on the next line.
618 *
Winson Chung97d85d22011-04-13 11:27:36 -0700619 * @param delta either -1 or 1 depending on the line and direction we want to search
620 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700621 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700622 int lineDelta) {
623 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
624 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700625 final int cellCountY = layout.getCountY();
626 final int row = lp.cellY;
627 final int newRow = row + lineDelta;
628 if (0 <= newRow && newRow < cellCountY) {
629 float closestDistance = Float.MAX_VALUE;
630 int closestIndex = -1;
631 int index = views.indexOf(v);
632 int endIndex = (lineDelta < 0) ? -1 : views.size();
633 while (index != endIndex) {
634 View newV = views.get(index);
635 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
636 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700637 if (satisfiesRow &&
638 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700639 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
640 Math.pow(tmpLp.cellY - lp.cellY, 2));
641 if (tmpDistance < closestDistance) {
642 closestIndex = index;
643 closestDistance = tmpDistance;
644 }
645 }
646 if (index <= endIndex) {
647 ++index;
648 } else {
649 --index;
650 }
651 }
652 if (closestIndex > -1) {
653 return views.get(closestIndex);
654 }
655 }
656 return null;
657 }
658
659 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700660 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700661 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700662 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700663 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700664 final CellLayout layout = (CellLayout) parent.getParent();
665 final Workspace workspace = (Workspace) layout.getParent();
666 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700667 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700668 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700669 int pageIndex = workspace.indexOfChild(layout);
670 int pageCount = workspace.getChildCount();
671
672 final int action = e.getAction();
673 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
674 boolean wasHandled = false;
675 switch (keyCode) {
676 case KeyEvent.KEYCODE_DPAD_LEFT:
677 if (handleKeyEvent) {
678 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700679 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700680 if (newIcon != null) {
681 newIcon.requestFocus();
682 } else {
683 if (pageIndex > 0) {
684 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700685 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700686 parent.getChildCount(), -1);
687 if (newIcon != null) {
688 newIcon.requestFocus();
689 } else {
690 // Snap to the previous page
691 workspace.snapToPage(pageIndex - 1);
692 }
693 }
694 }
695 }
696 wasHandled = true;
697 break;
698 case KeyEvent.KEYCODE_DPAD_RIGHT:
699 if (handleKeyEvent) {
700 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700701 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700702 if (newIcon != null) {
703 newIcon.requestFocus();
704 } else {
705 if (pageIndex < (pageCount - 1)) {
706 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700707 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700708 if (newIcon != null) {
709 newIcon.requestFocus();
710 } else {
711 // Snap to the next page
712 workspace.snapToPage(pageIndex + 1);
713 }
714 }
715 }
716 }
717 wasHandled = true;
718 break;
719 case KeyEvent.KEYCODE_DPAD_UP:
720 if (handleKeyEvent) {
721 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700722 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700723 if (newIcon != null) {
724 newIcon.requestFocus();
725 wasHandled = true;
726 } else {
727 tabs.requestFocus();
728 }
729 }
730 break;
731 case KeyEvent.KEYCODE_DPAD_DOWN:
732 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700733 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700734 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700735 if (newIcon != null) {
736 newIcon.requestFocus();
737 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700738 } else if (hotseat != null) {
739 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700740 }
741 }
742 break;
743 case KeyEvent.KEYCODE_PAGE_UP:
744 if (handleKeyEvent) {
745 // Select the first icon on the previous page or the first icon on this page
746 // if there is no previous page
747 if (pageIndex > 0) {
748 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
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 } else {
753 // Snap to the previous page
754 workspace.snapToPage(pageIndex - 1);
755 }
756 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700757 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700758 if (newIcon != null) {
759 newIcon.requestFocus();
760 }
761 }
762 }
763 wasHandled = true;
764 break;
765 case KeyEvent.KEYCODE_PAGE_DOWN:
766 if (handleKeyEvent) {
767 // Select the first icon on the next page or the last icon on this page
768 // if there is no previous page
769 if (pageIndex < (pageCount - 1)) {
770 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700771 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700772 if (newIcon != null) {
773 newIcon.requestFocus();
774 } else {
775 // Snap to the next page
776 workspace.snapToPage(pageIndex + 1);
777 }
778 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700779 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700780 parent.getChildCount(), -1);
781 if (newIcon != null) {
782 newIcon.requestFocus();
783 }
784 }
785 }
786 wasHandled = true;
787 break;
788 case KeyEvent.KEYCODE_MOVE_HOME:
789 if (handleKeyEvent) {
790 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700791 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700792 if (newIcon != null) {
793 newIcon.requestFocus();
794 }
795 }
796 wasHandled = true;
797 break;
798 case KeyEvent.KEYCODE_MOVE_END:
799 if (handleKeyEvent) {
800 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700801 View newIcon = getIconInDirection(layout, parent,
802 parent.getChildCount(), -1);
803 if (newIcon != null) {
804 newIcon.requestFocus();
805 }
806 }
807 wasHandled = true;
808 break;
809 default: break;
810 }
811 return wasHandled;
812 }
813
814 /**
815 * Handles key events for items in a Folder.
816 */
817 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700818 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700819 final CellLayout layout = (CellLayout) parent.getParent();
820 final Folder folder = (Folder) layout.getParent();
821 View title = folder.mFolderName;
822
823 final int action = e.getAction();
824 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
825 boolean wasHandled = false;
826 switch (keyCode) {
827 case KeyEvent.KEYCODE_DPAD_LEFT:
828 if (handleKeyEvent) {
829 // Select the previous icon
830 View newIcon = getIconInDirection(layout, parent, v, -1);
831 if (newIcon != null) {
832 newIcon.requestFocus();
833 }
834 }
835 wasHandled = true;
836 break;
837 case KeyEvent.KEYCODE_DPAD_RIGHT:
838 if (handleKeyEvent) {
839 // Select the next icon
840 View newIcon = getIconInDirection(layout, parent, v, 1);
841 if (newIcon != null) {
842 newIcon.requestFocus();
843 } else {
844 title.requestFocus();
845 }
846 }
847 wasHandled = true;
848 break;
849 case KeyEvent.KEYCODE_DPAD_UP:
850 if (handleKeyEvent) {
851 // Select the closest icon in the previous line
852 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
853 if (newIcon != null) {
854 newIcon.requestFocus();
855 }
856 }
857 wasHandled = true;
858 break;
859 case KeyEvent.KEYCODE_DPAD_DOWN:
860 if (handleKeyEvent) {
861 // Select the closest icon in the next line
862 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
863 if (newIcon != null) {
864 newIcon.requestFocus();
865 } else {
866 title.requestFocus();
867 }
868 }
869 wasHandled = true;
870 break;
871 case KeyEvent.KEYCODE_MOVE_HOME:
872 if (handleKeyEvent) {
873 // Select the first icon on this page
874 View newIcon = getIconInDirection(layout, parent, -1, 1);
875 if (newIcon != null) {
876 newIcon.requestFocus();
877 }
878 }
879 wasHandled = true;
880 break;
881 case KeyEvent.KEYCODE_MOVE_END:
882 if (handleKeyEvent) {
883 // Select the last icon on this page
884 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700885 parent.getChildCount(), -1);
886 if (newIcon != null) {
887 newIcon.requestFocus();
888 }
889 }
890 wasHandled = true;
891 break;
892 default: break;
893 }
894 return wasHandled;
895 }
896}