blob: 1acaf3e3bab0b1e87273200f7700a4990736bbda [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;
Winson Chunga1f133d2013-07-25 11:14:30 -070024import android.widget.ScrollView;
Winson Chung97d85d22011-04-13 11:27:36 -070025import android.widget.TabHost;
26import android.widget.TabWidget;
27
Winson Chungfaa13252011-06-13 18:15:54 -070028import java.util.ArrayList;
29import java.util.Collections;
30import java.util.Comparator;
31
Winson Chung97d85d22011-04-13 11:27:36 -070032/**
Winson Chung4d279d92011-07-21 11:46:32 -070033 * A keyboard listener we set on all the workspace icons.
34 */
Adam Cohenac56cff2011-09-28 20:45:37 -070035class IconKeyEventListener implements View.OnKeyListener {
Winson Chung4d279d92011-07-21 11:46:32 -070036 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070037 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
38 }
39}
40
41/**
42 * A keyboard listener we set on all the workspace icons.
43 */
44class FolderKeyEventListener implements View.OnKeyListener {
45 public boolean onKey(View v, int keyCode, KeyEvent event) {
46 return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
Winson Chung4d279d92011-07-21 11:46:32 -070047 }
48}
49
50/**
Winson Chung3d503fb2011-07-13 17:25:49 -070051 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070052 */
Adam Cohenac56cff2011-09-28 20:45:37 -070053class HotseatIconKeyEventListener implements View.OnKeyListener {
Winson Chung4e6a9762011-05-09 11:56:34 -070054 public boolean onKey(View v, int keyCode, KeyEvent event) {
55 final Configuration configuration = v.getResources().getConfiguration();
Winson Chung3d503fb2011-07-13 17:25:49 -070056 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
Winson Chung4e6a9762011-05-09 11:56:34 -070057 }
58}
59
60/**
Winson Chungfaa13252011-06-13 18:15:54 -070061 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
Winson Chung97d85d22011-04-13 11:27:36 -070062 * market icon and vice versa.
63 */
Winson Chungfaa13252011-06-13 18:15:54 -070064class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
Winson Chung97d85d22011-04-13 11:27:36 -070065 public boolean onKey(View v, int keyCode, KeyEvent event) {
Winson Chungfaa13252011-06-13 18:15:54 -070066 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
Winson Chung97d85d22011-04-13 11:27:36 -070067 }
68}
69
70public class FocusHelper {
71 /**
72 * Private helper to get the parent TabHost in the view hiearchy.
73 */
74 private static TabHost findTabHostParent(View v) {
75 ViewParent p = v.getParent();
76 while (p != null && !(p instanceof TabHost)) {
77 p = p.getParent();
78 }
79 return (TabHost) p;
80 }
81
82 /**
Winson Chungfaa13252011-06-13 18:15:54 -070083 * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
Winson Chung97d85d22011-04-13 11:27:36 -070084 */
Winson Chungfaa13252011-06-13 18:15:54 -070085 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
Winson Chung97d85d22011-04-13 11:27:36 -070086 final TabHost tabHost = findTabHostParent(v);
Michael Jurka8b805b12012-04-18 14:23:14 -070087 final ViewGroup contents = tabHost.getTabContentView();
Winson Chung97d85d22011-04-13 11:27:36 -070088 final View shop = tabHost.findViewById(R.id.market_button);
89
90 final int action = e.getAction();
91 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
92 boolean wasHandled = false;
93 switch (keyCode) {
94 case KeyEvent.KEYCODE_DPAD_RIGHT:
95 if (handleKeyEvent) {
96 // Select the shop button if we aren't on it
97 if (v != shop) {
98 shop.requestFocus();
99 }
100 }
101 wasHandled = true;
102 break;
103 case KeyEvent.KEYCODE_DPAD_DOWN:
104 if (handleKeyEvent) {
105 // Select the content view (down is handled by the tab key handler otherwise)
106 if (v == shop) {
107 contents.requestFocus();
108 wasHandled = true;
109 }
110 }
111 break;
112 default: break;
113 }
114 return wasHandled;
115 }
116
117 /**
Adam Cohenae4f1552011-10-20 00:15:42 -0700118 * Returns the Viewgroup containing page contents for the page at the index specified.
119 */
120 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
121 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
122 if (page instanceof PagedViewCellLayout) {
123 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
124 page = (ViewGroup) page.getChildAt(0);
125 }
126 return page;
127 }
128
129 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700130 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -0700131 */
132 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
133 KeyEvent e) {
134
135 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -0700136 final PagedView container = (PagedView) parent.getParent();
Winson Chung4e6a9762011-05-09 11:56:34 -0700137 final TabHost tabHost = findTabHostParent(container);
Michael Jurka8b805b12012-04-18 14:23:14 -0700138 final TabWidget tabs = tabHost.getTabWidget();
Winson Chung4e6a9762011-05-09 11:56:34 -0700139 final int widgetIndex = parent.indexOfChild(w);
140 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700141 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -0700142 final int pageCount = container.getChildCount();
143 final int cellCountX = parent.getCellCountX();
144 final int cellCountY = parent.getCellCountY();
145 final int x = widgetIndex % cellCountX;
146 final int y = widgetIndex / cellCountX;
147
148 final int action = e.getAction();
149 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700150 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700151 // Now that we load items in the bg asynchronously, we can't just focus
152 // child siblings willy-nilly
153 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700154 boolean wasHandled = false;
155 switch (keyCode) {
156 case KeyEvent.KEYCODE_DPAD_LEFT:
157 if (handleKeyEvent) {
158 // Select the previous widget or the last widget on the previous page
159 if (widgetIndex > 0) {
160 parent.getChildAt(widgetIndex - 1).requestFocus();
161 } else {
162 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700163 newParent = getAppsCustomizePage(container, pageIndex - 1);
164 if (newParent != null) {
165 child = newParent.getChildAt(newParent.getChildCount() - 1);
166 if (child != null) child.requestFocus();
167 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700168 }
169 }
170 }
171 wasHandled = true;
172 break;
173 case KeyEvent.KEYCODE_DPAD_RIGHT:
174 if (handleKeyEvent) {
175 // Select the next widget or the first widget on the next page
176 if (widgetIndex < (widgetCount - 1)) {
177 parent.getChildAt(widgetIndex + 1).requestFocus();
178 } else {
179 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700180 newParent = getAppsCustomizePage(container, pageIndex + 1);
181 if (newParent != null) {
182 child = newParent.getChildAt(0);
183 if (child != null) child.requestFocus();
184 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700185 }
186 }
187 }
188 wasHandled = true;
189 break;
190 case KeyEvent.KEYCODE_DPAD_UP:
191 if (handleKeyEvent) {
192 // Select the closest icon in the previous row, otherwise select the tab bar
193 if (y > 0) {
194 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700195 child = parent.getChildAt(newWidgetIndex);
196 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700197 } else {
198 tabs.requestFocus();
199 }
200 }
201 wasHandled = true;
202 break;
203 case KeyEvent.KEYCODE_DPAD_DOWN:
204 if (handleKeyEvent) {
205 // Select the closest icon in the previous row, otherwise do nothing
206 if (y < (cellCountY - 1)) {
207 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700208 child = parent.getChildAt(newWidgetIndex);
209 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700210 }
211 }
212 wasHandled = true;
213 break;
214 case KeyEvent.KEYCODE_ENTER:
215 case KeyEvent.KEYCODE_DPAD_CENTER:
216 if (handleKeyEvent) {
217 // Simulate a click on the widget
218 View.OnClickListener clickListener = (View.OnClickListener) container;
219 clickListener.onClick(w);
220 }
221 wasHandled = true;
222 break;
223 case KeyEvent.KEYCODE_PAGE_UP:
224 if (handleKeyEvent) {
225 // Select the first item on the previous page, or the first item on this page
226 // if there is no previous page
227 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700228 newParent = getAppsCustomizePage(container, pageIndex - 1);
229 if (newParent != null) {
230 child = newParent.getChildAt(0);
231 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700232 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700233 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700234 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700235 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700236 }
237 wasHandled = true;
238 break;
239 case KeyEvent.KEYCODE_PAGE_DOWN:
240 if (handleKeyEvent) {
241 // Select the first item on the next page, or the last item on this page
242 // if there is no next page
243 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700244 newParent = getAppsCustomizePage(container, pageIndex + 1);
245 if (newParent != null) {
246 child = newParent.getChildAt(0);
247 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700248 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700249 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700250 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700251 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700252 }
253 wasHandled = true;
254 break;
255 case KeyEvent.KEYCODE_MOVE_HOME:
256 if (handleKeyEvent) {
257 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700258 child = parent.getChildAt(0);
259 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700260 }
261 wasHandled = true;
262 break;
263 case KeyEvent.KEYCODE_MOVE_END:
264 if (handleKeyEvent) {
265 // Select the last item on this page
266 parent.getChildAt(widgetCount - 1).requestFocus();
267 }
268 wasHandled = true;
269 break;
270 default: break;
271 }
272 return wasHandled;
273 }
274
275 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700276 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
277 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700278 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
279 ViewGroup parentLayout;
280 ViewGroup itemContainer;
281 int countX;
282 int countY;
283 if (v.getParent() instanceof PagedViewCellLayoutChildren) {
284 itemContainer = (ViewGroup) v.getParent();
285 parentLayout = (ViewGroup) itemContainer.getParent();
286 countX = ((PagedViewCellLayout) parentLayout).getCellCountX();
287 countY = ((PagedViewCellLayout) parentLayout).getCellCountY();
288 } else {
289 itemContainer = parentLayout = (ViewGroup) v.getParent();
290 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
291 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
292 }
293
Winson Chung97d85d22011-04-13 11:27:36 -0700294 // Note we have an extra parent because of the
295 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700296 final PagedView container = (PagedView) parentLayout.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700297 final TabHost tabHost = findTabHostParent(container);
Michael Jurka8b805b12012-04-18 14:23:14 -0700298 final TabWidget tabs = tabHost.getTabWidget();
Adam Cohenae4f1552011-10-20 00:15:42 -0700299 final int iconIndex = itemContainer.indexOfChild(v);
300 final int itemCount = itemContainer.getChildCount();
301 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700302 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700303
304 final int x = iconIndex % countX;
305 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700306
307 final int action = e.getAction();
308 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700309 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700310 // Side pages do not always load synchronously, so check before focusing child siblings
311 // willy-nilly
312 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700313 boolean wasHandled = false;
314 switch (keyCode) {
315 case KeyEvent.KEYCODE_DPAD_LEFT:
316 if (handleKeyEvent) {
317 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700318 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700319 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700320 } else {
321 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700322 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700323 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700324 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700325 child = newParent.getChildAt(newParent.getChildCount() - 1);
326 if (child != null) child.requestFocus();
327 }
Winson Chung97d85d22011-04-13 11:27:36 -0700328 }
329 }
330 }
331 wasHandled = true;
332 break;
333 case KeyEvent.KEYCODE_DPAD_RIGHT:
334 if (handleKeyEvent) {
335 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700336 if (iconIndex < (itemCount - 1)) {
337 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700338 } else {
339 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700340 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700341 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700342 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700343 child = newParent.getChildAt(0);
344 if (child != null) child.requestFocus();
345 }
Winson Chung97d85d22011-04-13 11:27:36 -0700346 }
347 }
348 }
349 wasHandled = true;
350 break;
351 case KeyEvent.KEYCODE_DPAD_UP:
352 if (handleKeyEvent) {
353 // Select the closest icon in the previous row, otherwise select the tab bar
354 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700355 int newiconIndex = ((y - 1) * countX) + x;
356 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700357 } else {
358 tabs.requestFocus();
359 }
360 }
361 wasHandled = true;
362 break;
363 case KeyEvent.KEYCODE_DPAD_DOWN:
364 if (handleKeyEvent) {
365 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700366 if (y < (countY - 1)) {
367 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
368 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700369 }
370 }
371 wasHandled = true;
372 break;
373 case KeyEvent.KEYCODE_ENTER:
374 case KeyEvent.KEYCODE_DPAD_CENTER:
375 if (handleKeyEvent) {
376 // Simulate a click on the icon
377 View.OnClickListener clickListener = (View.OnClickListener) container;
378 clickListener.onClick(v);
379 }
380 wasHandled = true;
381 break;
382 case KeyEvent.KEYCODE_PAGE_UP:
383 if (handleKeyEvent) {
384 // Select the first icon on the previous page, or the first icon on this page
385 // if there is no previous page
386 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700387 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700388 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700389 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700390 child = newParent.getChildAt(0);
391 if (child != null) child.requestFocus();
392 }
Winson Chung97d85d22011-04-13 11:27:36 -0700393 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700394 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700395 }
396 }
397 wasHandled = true;
398 break;
399 case KeyEvent.KEYCODE_PAGE_DOWN:
400 if (handleKeyEvent) {
401 // Select the first icon on the next page, or the last icon on this page
402 // if there is no next page
403 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700404 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700405 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700406 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700407 child = newParent.getChildAt(0);
408 if (child != null) child.requestFocus();
409 }
Winson Chung97d85d22011-04-13 11:27:36 -0700410 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700411 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700412 }
413 }
414 wasHandled = true;
415 break;
416 case KeyEvent.KEYCODE_MOVE_HOME:
417 if (handleKeyEvent) {
418 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700419 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700420 }
421 wasHandled = true;
422 break;
423 case KeyEvent.KEYCODE_MOVE_END:
424 if (handleKeyEvent) {
425 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700426 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700427 }
428 wasHandled = true;
429 break;
430 default: break;
431 }
432 return wasHandled;
433 }
434
435 /**
436 * Handles key events in the tab widget.
437 */
438 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400439 if (!LauncherAppState.getInstance().isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700440
441 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
442 final TabHost tabHost = findTabHostParent(parent);
Michael Jurka8b805b12012-04-18 14:23:14 -0700443 final ViewGroup contents = tabHost.getTabContentView();
Winson Chung97d85d22011-04-13 11:27:36 -0700444 final int tabCount = parent.getTabCount();
445 final int tabIndex = parent.getChildTabIndex(v);
446
447 final int action = e.getAction();
448 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
449 boolean wasHandled = false;
450 switch (keyCode) {
451 case KeyEvent.KEYCODE_DPAD_LEFT:
452 if (handleKeyEvent) {
453 // Select the previous tab
454 if (tabIndex > 0) {
455 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
456 }
457 }
458 wasHandled = true;
459 break;
460 case KeyEvent.KEYCODE_DPAD_RIGHT:
461 if (handleKeyEvent) {
462 // Select the next tab, or if the last tab has a focus right id, select that
463 if (tabIndex < (tabCount - 1)) {
464 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
465 } else {
466 if (v.getNextFocusRightId() != View.NO_ID) {
467 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
468 }
469 }
470 }
471 wasHandled = true;
472 break;
473 case KeyEvent.KEYCODE_DPAD_UP:
474 // Do nothing
475 wasHandled = true;
476 break;
477 case KeyEvent.KEYCODE_DPAD_DOWN:
478 if (handleKeyEvent) {
479 // Select the content view
480 contents.requestFocus();
481 }
482 wasHandled = true;
483 break;
484 default: break;
485 }
486 return wasHandled;
487 }
488
489 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700490 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700491 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700492 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700493 final ViewGroup parent = (ViewGroup) v.getParent();
494 final ViewGroup launcher = (ViewGroup) parent.getParent();
495 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
496 final int buttonIndex = parent.indexOfChild(v);
497 final int buttonCount = parent.getChildCount();
498 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700499
500 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700501 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700502 // is to ensure that accessibility consistency is maintained across rotations.
503
504 final int action = e.getAction();
505 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
506 boolean wasHandled = false;
507 switch (keyCode) {
508 case KeyEvent.KEYCODE_DPAD_LEFT:
509 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700510 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700511 if (buttonIndex > 0) {
512 parent.getChildAt(buttonIndex - 1).requestFocus();
513 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700514 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700515 }
516 }
517 wasHandled = true;
518 break;
519 case KeyEvent.KEYCODE_DPAD_RIGHT:
520 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700521 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700522 if (buttonIndex < (buttonCount - 1)) {
523 parent.getChildAt(buttonIndex + 1).requestFocus();
524 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700525 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700526 }
527 }
528 wasHandled = true;
529 break;
530 case KeyEvent.KEYCODE_DPAD_UP:
531 if (handleKeyEvent) {
532 // Select the first bubble text view in the current page of the workspace
533 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700534 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
Adam Cohenac56cff2011-09-28 20:45:37 -0700535 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700536 if (newIcon != null) {
537 newIcon.requestFocus();
538 } else {
539 workspace.requestFocus();
540 }
541 }
542 wasHandled = true;
543 break;
544 case KeyEvent.KEYCODE_DPAD_DOWN:
545 // Do nothing
546 wasHandled = true;
547 break;
548 default: break;
549 }
550 return wasHandled;
551 }
552
553 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700554 * Private helper method to get the CellLayoutChildren given a CellLayout index.
555 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700556 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
557 ViewGroup container, int i) {
Winson Chung97d85d22011-04-13 11:27:36 -0700558 ViewGroup parent = (ViewGroup) container.getChildAt(i);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700559 return (ShortcutAndWidgetContainer) parent.getChildAt(0);
Winson Chung97d85d22011-04-13 11:27:36 -0700560 }
561
562 /**
563 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
564 * from top left to bottom right.
565 */
566 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
567 ViewGroup parent) {
568 // First we order each the CellLayout children by their x,y coordinates
569 final int cellCountX = layout.getCountX();
570 final int count = parent.getChildCount();
571 ArrayList<View> views = new ArrayList<View>();
572 for (int j = 0; j < count; ++j) {
573 views.add(parent.getChildAt(j));
574 }
575 Collections.sort(views, new Comparator<View>() {
576 @Override
577 public int compare(View lhs, View rhs) {
578 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
579 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
580 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
581 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
582 return lvIndex - rvIndex;
583 }
584 });
585 return views;
586 }
587 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700588 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
589 * direction delta.
590 *
Winson Chung97d85d22011-04-13 11:27:36 -0700591 * @param delta either -1 or 1 depending on the direction we want to search
592 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700593 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700594 // Then we find the next BubbleTextView offset by delta from i
595 final int count = views.size();
596 int newI = i + delta;
597 while (0 <= newI && newI < count) {
598 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700599 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700600 return newV;
601 }
602 newI += delta;
603 }
604 return null;
605 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700606 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700607 int delta) {
608 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700609 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700610 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700611 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700612 int delta) {
613 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700614 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700615 }
616 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700617 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
618 * delta on the next line.
619 *
Winson Chung97d85d22011-04-13 11:27:36 -0700620 * @param delta either -1 or 1 depending on the line and direction we want to search
621 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700622 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700623 int lineDelta) {
624 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
625 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700626 final int cellCountY = layout.getCountY();
627 final int row = lp.cellY;
628 final int newRow = row + lineDelta;
629 if (0 <= newRow && newRow < cellCountY) {
630 float closestDistance = Float.MAX_VALUE;
631 int closestIndex = -1;
632 int index = views.indexOf(v);
633 int endIndex = (lineDelta < 0) ? -1 : views.size();
634 while (index != endIndex) {
635 View newV = views.get(index);
636 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
637 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700638 if (satisfiesRow &&
639 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700640 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
641 Math.pow(tmpLp.cellY - lp.cellY, 2));
642 if (tmpDistance < closestDistance) {
643 closestIndex = index;
644 closestDistance = tmpDistance;
645 }
646 }
647 if (index <= endIndex) {
648 ++index;
649 } else {
650 --index;
651 }
652 }
653 if (closestIndex > -1) {
654 return views.get(closestIndex);
655 }
656 }
657 return null;
658 }
659
660 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700661 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700662 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700663 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700664 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700665 final CellLayout layout = (CellLayout) parent.getParent();
666 final Workspace workspace = (Workspace) layout.getParent();
667 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Winson Chungfaa13252011-06-13 18:15:54 -0700668 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700669 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700670 int pageIndex = workspace.indexOfChild(layout);
671 int pageCount = workspace.getChildCount();
672
673 final int action = e.getAction();
674 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
675 boolean wasHandled = false;
676 switch (keyCode) {
677 case KeyEvent.KEYCODE_DPAD_LEFT:
678 if (handleKeyEvent) {
679 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700680 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700681 if (newIcon != null) {
682 newIcon.requestFocus();
683 } else {
684 if (pageIndex > 0) {
685 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700686 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700687 parent.getChildCount(), -1);
688 if (newIcon != null) {
689 newIcon.requestFocus();
690 } else {
691 // Snap to the previous page
692 workspace.snapToPage(pageIndex - 1);
693 }
694 }
695 }
696 }
697 wasHandled = true;
698 break;
699 case KeyEvent.KEYCODE_DPAD_RIGHT:
700 if (handleKeyEvent) {
701 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700702 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700703 if (newIcon != null) {
704 newIcon.requestFocus();
705 } else {
706 if (pageIndex < (pageCount - 1)) {
707 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700708 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700709 if (newIcon != null) {
710 newIcon.requestFocus();
711 } else {
712 // Snap to the next page
713 workspace.snapToPage(pageIndex + 1);
714 }
715 }
716 }
717 }
718 wasHandled = true;
719 break;
720 case KeyEvent.KEYCODE_DPAD_UP:
721 if (handleKeyEvent) {
722 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700723 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700724 if (newIcon != null) {
725 newIcon.requestFocus();
726 wasHandled = true;
727 } else {
728 tabs.requestFocus();
729 }
730 }
731 break;
732 case KeyEvent.KEYCODE_DPAD_DOWN:
733 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700734 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700735 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700736 if (newIcon != null) {
737 newIcon.requestFocus();
738 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700739 } else if (hotseat != null) {
740 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700741 }
742 }
743 break;
744 case KeyEvent.KEYCODE_PAGE_UP:
745 if (handleKeyEvent) {
746 // Select the first icon on the previous page or the first icon on this page
747 // if there is no previous page
748 if (pageIndex > 0) {
749 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700750 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700751 if (newIcon != null) {
752 newIcon.requestFocus();
753 } else {
754 // Snap to the previous page
755 workspace.snapToPage(pageIndex - 1);
756 }
757 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700758 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700759 if (newIcon != null) {
760 newIcon.requestFocus();
761 }
762 }
763 }
764 wasHandled = true;
765 break;
766 case KeyEvent.KEYCODE_PAGE_DOWN:
767 if (handleKeyEvent) {
768 // Select the first icon on the next page or the last icon on this page
769 // if there is no previous page
770 if (pageIndex < (pageCount - 1)) {
771 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700772 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700773 if (newIcon != null) {
774 newIcon.requestFocus();
775 } else {
776 // Snap to the next page
777 workspace.snapToPage(pageIndex + 1);
778 }
779 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700780 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700781 parent.getChildCount(), -1);
782 if (newIcon != null) {
783 newIcon.requestFocus();
784 }
785 }
786 }
787 wasHandled = true;
788 break;
789 case KeyEvent.KEYCODE_MOVE_HOME:
790 if (handleKeyEvent) {
791 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700792 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700793 if (newIcon != null) {
794 newIcon.requestFocus();
795 }
796 }
797 wasHandled = true;
798 break;
799 case KeyEvent.KEYCODE_MOVE_END:
800 if (handleKeyEvent) {
801 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700802 View newIcon = getIconInDirection(layout, parent,
803 parent.getChildCount(), -1);
804 if (newIcon != null) {
805 newIcon.requestFocus();
806 }
807 }
808 wasHandled = true;
809 break;
810 default: break;
811 }
812 return wasHandled;
813 }
814
815 /**
816 * Handles key events for items in a Folder.
817 */
818 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700819 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700820 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chunga1f133d2013-07-25 11:14:30 -0700821 final ScrollView scrollView = (ScrollView) layout.getParent();
822 final Folder folder = (Folder) scrollView.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700823 View title = folder.mFolderName;
824
825 final int action = e.getAction();
826 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
827 boolean wasHandled = false;
828 switch (keyCode) {
829 case KeyEvent.KEYCODE_DPAD_LEFT:
830 if (handleKeyEvent) {
831 // Select the previous icon
832 View newIcon = getIconInDirection(layout, parent, v, -1);
833 if (newIcon != null) {
834 newIcon.requestFocus();
835 }
836 }
837 wasHandled = true;
838 break;
839 case KeyEvent.KEYCODE_DPAD_RIGHT:
840 if (handleKeyEvent) {
841 // Select the next icon
842 View newIcon = getIconInDirection(layout, parent, v, 1);
843 if (newIcon != null) {
844 newIcon.requestFocus();
845 } else {
846 title.requestFocus();
847 }
848 }
849 wasHandled = true;
850 break;
851 case KeyEvent.KEYCODE_DPAD_UP:
852 if (handleKeyEvent) {
853 // Select the closest icon in the previous line
854 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
855 if (newIcon != null) {
856 newIcon.requestFocus();
857 }
858 }
859 wasHandled = true;
860 break;
861 case KeyEvent.KEYCODE_DPAD_DOWN:
862 if (handleKeyEvent) {
863 // Select the closest icon in the next line
864 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
865 if (newIcon != null) {
866 newIcon.requestFocus();
867 } else {
868 title.requestFocus();
869 }
870 }
871 wasHandled = true;
872 break;
873 case KeyEvent.KEYCODE_MOVE_HOME:
874 if (handleKeyEvent) {
875 // Select the first icon on this page
876 View newIcon = getIconInDirection(layout, parent, -1, 1);
877 if (newIcon != null) {
878 newIcon.requestFocus();
879 }
880 }
881 wasHandled = true;
882 break;
883 case KeyEvent.KEYCODE_MOVE_END:
884 if (handleKeyEvent) {
885 // Select the last icon on this page
886 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700887 parent.getChildCount(), -1);
888 if (newIcon != null) {
889 newIcon.requestFocus();
890 }
891 }
892 wasHandled = true;
893 break;
894 default: break;
895 }
896 return wasHandled;
897 }
898}