blob: df5e0fc33ca19f88a50ac338e24270818a5daebf [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
Winson Chung97d85d22011-04-13 11:27:36 -070060public class FocusHelper {
61 /**
62 * Private helper to get the parent TabHost in the view hiearchy.
63 */
Adam Cohenc956cba2014-07-24 09:17:37 -070064 private static AppsCustomizeTabHost findTabHostParent(View v) {
Winson Chung97d85d22011-04-13 11:27:36 -070065 ViewParent p = v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -070066 while (p != null && !(p instanceof AppsCustomizeTabHost)) {
Winson Chung97d85d22011-04-13 11:27:36 -070067 p = p.getParent();
68 }
Adam Cohenc956cba2014-07-24 09:17:37 -070069 return (AppsCustomizeTabHost) p;
Winson Chung97d85d22011-04-13 11:27:36 -070070 }
71
72 /**
Adam Cohenae4f1552011-10-20 00:15:42 -070073 * Returns the Viewgroup containing page contents for the page at the index specified.
74 */
75 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
76 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
Winson Chungc58497e2013-09-03 17:48:37 -070077 if (page instanceof CellLayout) {
Adam Cohenae4f1552011-10-20 00:15:42 -070078 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
Winson Chungc58497e2013-09-03 17:48:37 -070079 page = ((CellLayout) page).getShortcutsAndWidgets();
Adam Cohenae4f1552011-10-20 00:15:42 -070080 }
81 return page;
82 }
83
84 /**
Winson Chung97d85d22011-04-13 11:27:36 -070085 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
Winson Chung4e6a9762011-05-09 11:56:34 -070086 */
87 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
88 KeyEvent e) {
89
90 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
Adam Cohenae4f1552011-10-20 00:15:42 -070091 final PagedView container = (PagedView) parent.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -070092 final AppsCustomizeTabHost tabHost = findTabHostParent(container);
Winson Chung4e6a9762011-05-09 11:56:34 -070093 final int widgetIndex = parent.indexOfChild(w);
94 final int widgetCount = parent.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -070095 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
Winson Chung4e6a9762011-05-09 11:56:34 -070096 final int pageCount = container.getChildCount();
97 final int cellCountX = parent.getCellCountX();
98 final int cellCountY = parent.getCellCountY();
99 final int x = widgetIndex % cellCountX;
100 final int y = widgetIndex / cellCountX;
101
102 final int action = e.getAction();
103 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700104 ViewGroup newParent = null;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700105 // Now that we load items in the bg asynchronously, we can't just focus
106 // child siblings willy-nilly
107 View child = null;
Winson Chung4e6a9762011-05-09 11:56:34 -0700108 boolean wasHandled = false;
109 switch (keyCode) {
110 case KeyEvent.KEYCODE_DPAD_LEFT:
111 if (handleKeyEvent) {
112 // Select the previous widget or the last widget on the previous page
113 if (widgetIndex > 0) {
114 parent.getChildAt(widgetIndex - 1).requestFocus();
115 } else {
116 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700117 newParent = getAppsCustomizePage(container, pageIndex - 1);
118 if (newParent != null) {
119 child = newParent.getChildAt(newParent.getChildCount() - 1);
120 if (child != null) child.requestFocus();
121 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700122 }
123 }
124 }
125 wasHandled = true;
126 break;
127 case KeyEvent.KEYCODE_DPAD_RIGHT:
128 if (handleKeyEvent) {
129 // Select the next widget or the first widget on the next page
130 if (widgetIndex < (widgetCount - 1)) {
131 parent.getChildAt(widgetIndex + 1).requestFocus();
132 } else {
133 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700134 newParent = getAppsCustomizePage(container, pageIndex + 1);
135 if (newParent != null) {
136 child = newParent.getChildAt(0);
137 if (child != null) child.requestFocus();
138 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700139 }
140 }
141 }
142 wasHandled = true;
143 break;
144 case KeyEvent.KEYCODE_DPAD_UP:
145 if (handleKeyEvent) {
146 // Select the closest icon in the previous row, otherwise select the tab bar
147 if (y > 0) {
148 int newWidgetIndex = ((y - 1) * cellCountX) + x;
Winson Chung3b0b46a2011-07-28 10:45:09 -0700149 child = parent.getChildAt(newWidgetIndex);
150 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700151 }
152 }
153 wasHandled = true;
154 break;
155 case KeyEvent.KEYCODE_DPAD_DOWN:
156 if (handleKeyEvent) {
157 // Select the closest icon in the previous row, otherwise do nothing
158 if (y < (cellCountY - 1)) {
159 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700160 child = parent.getChildAt(newWidgetIndex);
161 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700162 }
163 }
164 wasHandled = true;
165 break;
166 case KeyEvent.KEYCODE_ENTER:
167 case KeyEvent.KEYCODE_DPAD_CENTER:
168 if (handleKeyEvent) {
169 // Simulate a click on the widget
170 View.OnClickListener clickListener = (View.OnClickListener) container;
171 clickListener.onClick(w);
172 }
173 wasHandled = true;
174 break;
175 case KeyEvent.KEYCODE_PAGE_UP:
176 if (handleKeyEvent) {
177 // Select the first item on the previous page, or the first item on this page
178 // if there is no previous page
179 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700180 newParent = getAppsCustomizePage(container, pageIndex - 1);
181 if (newParent != null) {
182 child = newParent.getChildAt(0);
183 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700184 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700185 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700186 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700187 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700188 }
189 wasHandled = true;
190 break;
191 case KeyEvent.KEYCODE_PAGE_DOWN:
192 if (handleKeyEvent) {
193 // Select the first item on the next page, or the last item on this page
194 // if there is no next page
195 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700196 newParent = getAppsCustomizePage(container, pageIndex + 1);
197 if (newParent != null) {
198 child = newParent.getChildAt(0);
199 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700200 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700201 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700202 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700203 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700204 }
205 wasHandled = true;
206 break;
207 case KeyEvent.KEYCODE_MOVE_HOME:
208 if (handleKeyEvent) {
209 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700210 child = parent.getChildAt(0);
211 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700212 }
213 wasHandled = true;
214 break;
215 case KeyEvent.KEYCODE_MOVE_END:
216 if (handleKeyEvent) {
217 // Select the last item on this page
218 parent.getChildAt(widgetCount - 1).requestFocus();
219 }
220 wasHandled = true;
221 break;
222 default: break;
223 }
224 return wasHandled;
225 }
226
227 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700228 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
229 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700230 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
231 ViewGroup parentLayout;
232 ViewGroup itemContainer;
233 int countX;
234 int countY;
Winson Chungc58497e2013-09-03 17:48:37 -0700235 if (v.getParent() instanceof ShortcutAndWidgetContainer) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700236 itemContainer = (ViewGroup) v.getParent();
237 parentLayout = (ViewGroup) itemContainer.getParent();
Winson Chungc58497e2013-09-03 17:48:37 -0700238 countX = ((CellLayout) parentLayout).getCountX();
239 countY = ((CellLayout) parentLayout).getCountY();
Adam Cohenae4f1552011-10-20 00:15:42 -0700240 } else {
241 itemContainer = parentLayout = (ViewGroup) v.getParent();
242 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
243 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
244 }
245
Winson Chung97d85d22011-04-13 11:27:36 -0700246 // Note we have an extra parent because of the
247 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700248 final PagedView container = (PagedView) parentLayout.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -0700249 final AppsCustomizeTabHost tabHost = findTabHostParent(container);
Adam Cohenae4f1552011-10-20 00:15:42 -0700250 final int iconIndex = itemContainer.indexOfChild(v);
251 final int itemCount = itemContainer.getChildCount();
252 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700253 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700254
255 final int x = iconIndex % countX;
256 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700257
258 final int action = e.getAction();
259 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700260 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700261 // Side pages do not always load synchronously, so check before focusing child siblings
262 // willy-nilly
263 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700264 boolean wasHandled = false;
265 switch (keyCode) {
266 case KeyEvent.KEYCODE_DPAD_LEFT:
267 if (handleKeyEvent) {
268 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700269 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700270 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700271 } else {
272 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700273 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700274 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700275 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700276 child = newParent.getChildAt(newParent.getChildCount() - 1);
277 if (child != null) child.requestFocus();
278 }
Winson Chung97d85d22011-04-13 11:27:36 -0700279 }
280 }
281 }
282 wasHandled = true;
283 break;
284 case KeyEvent.KEYCODE_DPAD_RIGHT:
285 if (handleKeyEvent) {
286 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700287 if (iconIndex < (itemCount - 1)) {
288 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700289 } else {
290 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700291 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700292 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700293 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700294 child = newParent.getChildAt(0);
295 if (child != null) child.requestFocus();
296 }
Winson Chung97d85d22011-04-13 11:27:36 -0700297 }
298 }
299 }
300 wasHandled = true;
301 break;
302 case KeyEvent.KEYCODE_DPAD_UP:
303 if (handleKeyEvent) {
304 // Select the closest icon in the previous row, otherwise select the tab bar
305 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700306 int newiconIndex = ((y - 1) * countX) + x;
307 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700308 }
309 }
310 wasHandled = true;
311 break;
312 case KeyEvent.KEYCODE_DPAD_DOWN:
313 if (handleKeyEvent) {
314 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700315 if (y < (countY - 1)) {
316 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
317 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700318 }
319 }
320 wasHandled = true;
321 break;
322 case KeyEvent.KEYCODE_ENTER:
323 case KeyEvent.KEYCODE_DPAD_CENTER:
324 if (handleKeyEvent) {
325 // Simulate a click on the icon
326 View.OnClickListener clickListener = (View.OnClickListener) container;
327 clickListener.onClick(v);
328 }
329 wasHandled = true;
330 break;
331 case KeyEvent.KEYCODE_PAGE_UP:
332 if (handleKeyEvent) {
333 // Select the first icon on the previous page, or the first icon on this page
334 // if there is no previous page
335 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700336 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700337 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700338 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700339 child = newParent.getChildAt(0);
340 if (child != null) child.requestFocus();
341 }
Winson Chung97d85d22011-04-13 11:27:36 -0700342 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700343 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700344 }
345 }
346 wasHandled = true;
347 break;
348 case KeyEvent.KEYCODE_PAGE_DOWN:
349 if (handleKeyEvent) {
350 // Select the first icon on the next page, or the last icon on this page
351 // if there is no next page
352 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700353 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700354 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700355 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700356 child = newParent.getChildAt(0);
357 if (child != null) child.requestFocus();
358 }
Winson Chung97d85d22011-04-13 11:27:36 -0700359 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700360 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700361 }
362 }
363 wasHandled = true;
364 break;
365 case KeyEvent.KEYCODE_MOVE_HOME:
366 if (handleKeyEvent) {
367 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700368 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700369 }
370 wasHandled = true;
371 break;
372 case KeyEvent.KEYCODE_MOVE_END:
373 if (handleKeyEvent) {
374 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700375 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700376 }
377 wasHandled = true;
378 break;
379 default: break;
380 }
381 return wasHandled;
382 }
383
384 /**
385 * Handles key events in the tab widget.
386 */
387 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400388 if (!LauncherAppState.getInstance().isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700389
390 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -0700391 final AppsCustomizeTabHost tabHost = findTabHostParent(parent);
392 final ViewGroup contents = tabHost.getContent();
Winson Chung97d85d22011-04-13 11:27:36 -0700393 final int tabCount = parent.getTabCount();
394 final int tabIndex = parent.getChildTabIndex(v);
395
396 final int action = e.getAction();
397 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
398 boolean wasHandled = false;
399 switch (keyCode) {
400 case KeyEvent.KEYCODE_DPAD_LEFT:
401 if (handleKeyEvent) {
402 // Select the previous tab
403 if (tabIndex > 0) {
404 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
405 }
406 }
407 wasHandled = true;
408 break;
409 case KeyEvent.KEYCODE_DPAD_RIGHT:
410 if (handleKeyEvent) {
411 // Select the next tab, or if the last tab has a focus right id, select that
412 if (tabIndex < (tabCount - 1)) {
413 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
414 } else {
415 if (v.getNextFocusRightId() != View.NO_ID) {
416 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
417 }
418 }
419 }
420 wasHandled = true;
421 break;
422 case KeyEvent.KEYCODE_DPAD_UP:
423 // Do nothing
424 wasHandled = true;
425 break;
426 case KeyEvent.KEYCODE_DPAD_DOWN:
427 if (handleKeyEvent) {
428 // Select the content view
429 contents.requestFocus();
430 }
431 wasHandled = true;
432 break;
433 default: break;
434 }
435 return wasHandled;
436 }
437
438 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700439 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700440 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700441 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700442 final ViewGroup parent = (ViewGroup) v.getParent();
443 final ViewGroup launcher = (ViewGroup) parent.getParent();
444 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
445 final int buttonIndex = parent.indexOfChild(v);
446 final int buttonCount = parent.getChildCount();
447 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700448
449 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700450 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700451 // is to ensure that accessibility consistency is maintained across rotations.
452
453 final int action = e.getAction();
454 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
455 boolean wasHandled = false;
456 switch (keyCode) {
457 case KeyEvent.KEYCODE_DPAD_LEFT:
458 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700459 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700460 if (buttonIndex > 0) {
461 parent.getChildAt(buttonIndex - 1).requestFocus();
462 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700463 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700464 }
465 }
466 wasHandled = true;
467 break;
468 case KeyEvent.KEYCODE_DPAD_RIGHT:
469 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700470 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700471 if (buttonIndex < (buttonCount - 1)) {
472 parent.getChildAt(buttonIndex + 1).requestFocus();
473 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700474 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700475 }
476 }
477 wasHandled = true;
478 break;
479 case KeyEvent.KEYCODE_DPAD_UP:
480 if (handleKeyEvent) {
481 // Select the first bubble text view in the current page of the workspace
482 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700483 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
Adam Cohenac56cff2011-09-28 20:45:37 -0700484 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700485 if (newIcon != null) {
486 newIcon.requestFocus();
487 } else {
488 workspace.requestFocus();
489 }
490 }
491 wasHandled = true;
492 break;
493 case KeyEvent.KEYCODE_DPAD_DOWN:
494 // Do nothing
495 wasHandled = true;
496 break;
497 default: break;
498 }
499 return wasHandled;
500 }
501
502 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700503 * Private helper method to get the CellLayoutChildren given a CellLayout index.
504 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700505 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
506 ViewGroup container, int i) {
Winson Chung97d85d22011-04-13 11:27:36 -0700507 ViewGroup parent = (ViewGroup) container.getChildAt(i);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700508 return (ShortcutAndWidgetContainer) parent.getChildAt(0);
Winson Chung97d85d22011-04-13 11:27:36 -0700509 }
510
511 /**
512 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
513 * from top left to bottom right.
514 */
515 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
516 ViewGroup parent) {
517 // First we order each the CellLayout children by their x,y coordinates
518 final int cellCountX = layout.getCountX();
519 final int count = parent.getChildCount();
520 ArrayList<View> views = new ArrayList<View>();
521 for (int j = 0; j < count; ++j) {
522 views.add(parent.getChildAt(j));
523 }
524 Collections.sort(views, new Comparator<View>() {
525 @Override
526 public int compare(View lhs, View rhs) {
527 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
528 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
529 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
530 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
531 return lvIndex - rvIndex;
532 }
533 });
534 return views;
535 }
536 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700537 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
538 * direction delta.
539 *
Winson Chung97d85d22011-04-13 11:27:36 -0700540 * @param delta either -1 or 1 depending on the direction we want to search
541 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700542 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700543 // Then we find the next BubbleTextView offset by delta from i
544 final int count = views.size();
545 int newI = i + delta;
546 while (0 <= newI && newI < count) {
547 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700548 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700549 return newV;
550 }
551 newI += delta;
552 }
553 return null;
554 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700555 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700556 int delta) {
557 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700558 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700559 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700560 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700561 int delta) {
562 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700563 return findIndexOfIcon(views, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700564 }
565 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700566 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
567 * delta on the next line.
568 *
Winson Chung97d85d22011-04-13 11:27:36 -0700569 * @param delta either -1 or 1 depending on the line and direction we want to search
570 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700571 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700572 int lineDelta) {
573 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
574 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700575 final int cellCountY = layout.getCountY();
576 final int row = lp.cellY;
577 final int newRow = row + lineDelta;
578 if (0 <= newRow && newRow < cellCountY) {
579 float closestDistance = Float.MAX_VALUE;
580 int closestIndex = -1;
581 int index = views.indexOf(v);
582 int endIndex = (lineDelta < 0) ? -1 : views.size();
583 while (index != endIndex) {
584 View newV = views.get(index);
585 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
586 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700587 if (satisfiesRow &&
588 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700589 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
590 Math.pow(tmpLp.cellY - lp.cellY, 2));
591 if (tmpDistance < closestDistance) {
592 closestIndex = index;
593 closestDistance = tmpDistance;
594 }
595 }
596 if (index <= endIndex) {
597 ++index;
598 } else {
599 --index;
600 }
601 }
602 if (closestIndex > -1) {
603 return views.get(closestIndex);
604 }
605 }
606 return null;
607 }
608
609 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700610 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700611 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700612 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700613 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700614 final CellLayout layout = (CellLayout) parent.getParent();
615 final Workspace workspace = (Workspace) layout.getParent();
616 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Adam Cohen24ce0b32014-01-14 16:18:14 -0800617 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700618 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700619 int pageIndex = workspace.indexOfChild(layout);
620 int pageCount = workspace.getChildCount();
621
622 final int action = e.getAction();
623 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
624 boolean wasHandled = false;
625 switch (keyCode) {
626 case KeyEvent.KEYCODE_DPAD_LEFT:
627 if (handleKeyEvent) {
628 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700629 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700630 if (newIcon != null) {
631 newIcon.requestFocus();
632 } else {
633 if (pageIndex > 0) {
634 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700635 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700636 parent.getChildCount(), -1);
637 if (newIcon != null) {
638 newIcon.requestFocus();
639 } else {
640 // Snap to the previous page
641 workspace.snapToPage(pageIndex - 1);
642 }
643 }
644 }
645 }
646 wasHandled = true;
647 break;
648 case KeyEvent.KEYCODE_DPAD_RIGHT:
649 if (handleKeyEvent) {
650 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700651 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700652 if (newIcon != null) {
653 newIcon.requestFocus();
654 } else {
655 if (pageIndex < (pageCount - 1)) {
656 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700657 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700658 if (newIcon != null) {
659 newIcon.requestFocus();
660 } else {
661 // Snap to the next page
662 workspace.snapToPage(pageIndex + 1);
663 }
664 }
665 }
666 }
667 wasHandled = true;
668 break;
669 case KeyEvent.KEYCODE_DPAD_UP:
670 if (handleKeyEvent) {
671 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700672 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700673 if (newIcon != null) {
674 newIcon.requestFocus();
675 wasHandled = true;
676 } else {
677 tabs.requestFocus();
678 }
679 }
680 break;
681 case KeyEvent.KEYCODE_DPAD_DOWN:
682 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700683 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700684 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700685 if (newIcon != null) {
686 newIcon.requestFocus();
687 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700688 } else if (hotseat != null) {
689 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700690 }
691 }
692 break;
693 case KeyEvent.KEYCODE_PAGE_UP:
694 if (handleKeyEvent) {
695 // Select the first icon on the previous page or the first icon on this page
696 // if there is no previous page
697 if (pageIndex > 0) {
698 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700699 View 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 previous page
704 workspace.snapToPage(pageIndex - 1);
705 }
706 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700707 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700708 if (newIcon != null) {
709 newIcon.requestFocus();
710 }
711 }
712 }
713 wasHandled = true;
714 break;
715 case KeyEvent.KEYCODE_PAGE_DOWN:
716 if (handleKeyEvent) {
717 // Select the first icon on the next page or the last icon on this page
718 // if there is no previous page
719 if (pageIndex < (pageCount - 1)) {
720 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700721 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700722 if (newIcon != null) {
723 newIcon.requestFocus();
724 } else {
725 // Snap to the next page
726 workspace.snapToPage(pageIndex + 1);
727 }
728 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700729 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700730 parent.getChildCount(), -1);
731 if (newIcon != null) {
732 newIcon.requestFocus();
733 }
734 }
735 }
736 wasHandled = true;
737 break;
738 case KeyEvent.KEYCODE_MOVE_HOME:
739 if (handleKeyEvent) {
740 // Select the first icon on this page
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 }
745 }
746 wasHandled = true;
747 break;
748 case KeyEvent.KEYCODE_MOVE_END:
749 if (handleKeyEvent) {
750 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700751 View newIcon = getIconInDirection(layout, parent,
752 parent.getChildCount(), -1);
753 if (newIcon != null) {
754 newIcon.requestFocus();
755 }
756 }
757 wasHandled = true;
758 break;
759 default: break;
760 }
761 return wasHandled;
762 }
763
764 /**
765 * Handles key events for items in a Folder.
766 */
767 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700768 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700769 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chunga1f133d2013-07-25 11:14:30 -0700770 final ScrollView scrollView = (ScrollView) layout.getParent();
771 final Folder folder = (Folder) scrollView.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700772 View title = folder.mFolderName;
773
774 final int action = e.getAction();
775 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
776 boolean wasHandled = false;
777 switch (keyCode) {
778 case KeyEvent.KEYCODE_DPAD_LEFT:
779 if (handleKeyEvent) {
780 // Select the previous icon
781 View newIcon = getIconInDirection(layout, parent, v, -1);
782 if (newIcon != null) {
783 newIcon.requestFocus();
784 }
785 }
786 wasHandled = true;
787 break;
788 case KeyEvent.KEYCODE_DPAD_RIGHT:
789 if (handleKeyEvent) {
790 // Select the next icon
791 View newIcon = getIconInDirection(layout, parent, v, 1);
792 if (newIcon != null) {
793 newIcon.requestFocus();
794 } else {
795 title.requestFocus();
796 }
797 }
798 wasHandled = true;
799 break;
800 case KeyEvent.KEYCODE_DPAD_UP:
801 if (handleKeyEvent) {
802 // Select the closest icon in the previous line
803 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
804 if (newIcon != null) {
805 newIcon.requestFocus();
806 }
807 }
808 wasHandled = true;
809 break;
810 case KeyEvent.KEYCODE_DPAD_DOWN:
811 if (handleKeyEvent) {
812 // Select the closest icon in the next line
813 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
814 if (newIcon != null) {
815 newIcon.requestFocus();
816 } else {
817 title.requestFocus();
818 }
819 }
820 wasHandled = true;
821 break;
822 case KeyEvent.KEYCODE_MOVE_HOME:
823 if (handleKeyEvent) {
824 // Select the first icon on this page
825 View newIcon = getIconInDirection(layout, parent, -1, 1);
826 if (newIcon != null) {
827 newIcon.requestFocus();
828 }
829 }
830 wasHandled = true;
831 break;
832 case KeyEvent.KEYCODE_MOVE_END:
833 if (handleKeyEvent) {
834 // Select the last icon on this page
835 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700836 parent.getChildCount(), -1);
837 if (newIcon != null) {
838 newIcon.requestFocus();
839 }
840 }
841 wasHandled = true;
842 break;
843 default: break;
844 }
845 return wasHandled;
846 }
847}