blob: b1250ceead5c18ae29232f472d74381505c5d8e6 [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 */
Adam Cohenc956cba2014-07-24 09:17:37 -070074 private static AppsCustomizeTabHost findTabHostParent(View v) {
Winson Chung97d85d22011-04-13 11:27:36 -070075 ViewParent p = v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -070076 while (p != null && !(p instanceof AppsCustomizeTabHost)) {
Winson Chung97d85d22011-04-13 11:27:36 -070077 p = p.getParent();
78 }
Adam Cohenc956cba2014-07-24 09:17:37 -070079 return (AppsCustomizeTabHost) p;
Winson Chung97d85d22011-04-13 11:27:36 -070080 }
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) {
Adam Cohenc956cba2014-07-24 09:17:37 -070086 final AppsCustomizeTabHost tabHost = findTabHostParent(v);
87 final ViewGroup contents = tabHost.getContent();
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);
Winson Chungc58497e2013-09-03 17:48:37 -0700122 if (page instanceof CellLayout) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700123 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
Winson Chungc58497e2013-09-03 17:48:37 -0700124 page = ((CellLayout) page).getShortcutsAndWidgets();
Adam Cohenae4f1552011-10-20 00:15:42 -0700125 }
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();
Adam Cohenc956cba2014-07-24 09:17:37 -0700137 final AppsCustomizeTabHost tabHost = findTabHostParent(container);
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 }
197 }
198 wasHandled = true;
199 break;
200 case KeyEvent.KEYCODE_DPAD_DOWN:
201 if (handleKeyEvent) {
202 // Select the closest icon in the previous row, otherwise do nothing
203 if (y < (cellCountY - 1)) {
204 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
Winson Chung3b0b46a2011-07-28 10:45:09 -0700205 child = parent.getChildAt(newWidgetIndex);
206 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700207 }
208 }
209 wasHandled = true;
210 break;
211 case KeyEvent.KEYCODE_ENTER:
212 case KeyEvent.KEYCODE_DPAD_CENTER:
213 if (handleKeyEvent) {
214 // Simulate a click on the widget
215 View.OnClickListener clickListener = (View.OnClickListener) container;
216 clickListener.onClick(w);
217 }
218 wasHandled = true;
219 break;
220 case KeyEvent.KEYCODE_PAGE_UP:
221 if (handleKeyEvent) {
222 // Select the first item on the previous page, or the first item on this page
223 // if there is no previous page
224 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700225 newParent = getAppsCustomizePage(container, pageIndex - 1);
226 if (newParent != null) {
227 child = newParent.getChildAt(0);
228 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700229 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700230 child = parent.getChildAt(0);
Winson Chung4e6a9762011-05-09 11:56:34 -0700231 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700232 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700233 }
234 wasHandled = true;
235 break;
236 case KeyEvent.KEYCODE_PAGE_DOWN:
237 if (handleKeyEvent) {
238 // Select the first item on the next page, or the last item on this page
239 // if there is no next page
240 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700241 newParent = getAppsCustomizePage(container, pageIndex + 1);
242 if (newParent != null) {
243 child = newParent.getChildAt(0);
244 }
Winson Chung4e6a9762011-05-09 11:56:34 -0700245 } else {
Winson Chung3b0b46a2011-07-28 10:45:09 -0700246 child = parent.getChildAt(widgetCount - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700247 }
Winson Chung3b0b46a2011-07-28 10:45:09 -0700248 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700249 }
250 wasHandled = true;
251 break;
252 case KeyEvent.KEYCODE_MOVE_HOME:
253 if (handleKeyEvent) {
254 // Select the first item on this page
Winson Chung3b0b46a2011-07-28 10:45:09 -0700255 child = parent.getChildAt(0);
256 if (child != null) child.requestFocus();
Winson Chung4e6a9762011-05-09 11:56:34 -0700257 }
258 wasHandled = true;
259 break;
260 case KeyEvent.KEYCODE_MOVE_END:
261 if (handleKeyEvent) {
262 // Select the last item on this page
263 parent.getChildAt(widgetCount - 1).requestFocus();
264 }
265 wasHandled = true;
266 break;
267 default: break;
268 }
269 return wasHandled;
270 }
271
272 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700273 * Handles key events in a PageViewCellLayout containing PagedViewIcons.
274 */
Adam Cohenae4f1552011-10-20 00:15:42 -0700275 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
276 ViewGroup parentLayout;
277 ViewGroup itemContainer;
278 int countX;
279 int countY;
Winson Chungc58497e2013-09-03 17:48:37 -0700280 if (v.getParent() instanceof ShortcutAndWidgetContainer) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700281 itemContainer = (ViewGroup) v.getParent();
282 parentLayout = (ViewGroup) itemContainer.getParent();
Winson Chungc58497e2013-09-03 17:48:37 -0700283 countX = ((CellLayout) parentLayout).getCountX();
284 countY = ((CellLayout) parentLayout).getCountY();
Adam Cohenae4f1552011-10-20 00:15:42 -0700285 } else {
286 itemContainer = parentLayout = (ViewGroup) v.getParent();
287 countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
288 countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
289 }
290
Winson Chung97d85d22011-04-13 11:27:36 -0700291 // Note we have an extra parent because of the
292 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
Adam Cohenae4f1552011-10-20 00:15:42 -0700293 final PagedView container = (PagedView) parentLayout.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -0700294 final AppsCustomizeTabHost tabHost = findTabHostParent(container);
Adam Cohenae4f1552011-10-20 00:15:42 -0700295 final int iconIndex = itemContainer.indexOfChild(v);
296 final int itemCount = itemContainer.getChildCount();
297 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
Winson Chung97d85d22011-04-13 11:27:36 -0700298 final int pageCount = container.getChildCount();
Adam Cohenae4f1552011-10-20 00:15:42 -0700299
300 final int x = iconIndex % countX;
301 final int y = iconIndex / countX;
Winson Chung97d85d22011-04-13 11:27:36 -0700302
303 final int action = e.getAction();
304 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
Adam Cohenae4f1552011-10-20 00:15:42 -0700305 ViewGroup newParent = null;
Winson Chung90576b52011-09-27 17:45:27 -0700306 // Side pages do not always load synchronously, so check before focusing child siblings
307 // willy-nilly
308 View child = null;
Winson Chung97d85d22011-04-13 11:27:36 -0700309 boolean wasHandled = false;
310 switch (keyCode) {
311 case KeyEvent.KEYCODE_DPAD_LEFT:
312 if (handleKeyEvent) {
313 // Select the previous icon or the last icon on the previous page
Winson Chung90576b52011-09-27 17:45:27 -0700314 if (iconIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700315 itemContainer.getChildAt(iconIndex - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700316 } else {
317 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700318 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700319 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700320 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700321 child = newParent.getChildAt(newParent.getChildCount() - 1);
322 if (child != null) child.requestFocus();
323 }
Winson Chung97d85d22011-04-13 11:27:36 -0700324 }
325 }
326 }
327 wasHandled = true;
328 break;
329 case KeyEvent.KEYCODE_DPAD_RIGHT:
330 if (handleKeyEvent) {
331 // Select the next icon or the first icon on the next page
Adam Cohenae4f1552011-10-20 00:15:42 -0700332 if (iconIndex < (itemCount - 1)) {
333 itemContainer.getChildAt(iconIndex + 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700334 } else {
335 if (pageIndex < (pageCount - 1)) {
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 }
343 }
344 }
345 wasHandled = true;
346 break;
347 case KeyEvent.KEYCODE_DPAD_UP:
348 if (handleKeyEvent) {
349 // Select the closest icon in the previous row, otherwise select the tab bar
350 if (y > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700351 int newiconIndex = ((y - 1) * countX) + x;
352 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700353 }
354 }
355 wasHandled = true;
356 break;
357 case KeyEvent.KEYCODE_DPAD_DOWN:
358 if (handleKeyEvent) {
359 // Select the closest icon in the previous row, otherwise do nothing
Adam Cohenae4f1552011-10-20 00:15:42 -0700360 if (y < (countY - 1)) {
361 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
362 itemContainer.getChildAt(newiconIndex).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700363 }
364 }
365 wasHandled = true;
366 break;
367 case KeyEvent.KEYCODE_ENTER:
368 case KeyEvent.KEYCODE_DPAD_CENTER:
369 if (handleKeyEvent) {
370 // Simulate a click on the icon
371 View.OnClickListener clickListener = (View.OnClickListener) container;
372 clickListener.onClick(v);
373 }
374 wasHandled = true;
375 break;
376 case KeyEvent.KEYCODE_PAGE_UP:
377 if (handleKeyEvent) {
378 // Select the first icon on the previous page, or the first icon on this page
379 // if there is no previous page
380 if (pageIndex > 0) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700381 newParent = getAppsCustomizePage(container, pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700382 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700383 container.snapToPage(pageIndex - 1);
Winson Chung90576b52011-09-27 17:45:27 -0700384 child = newParent.getChildAt(0);
385 if (child != null) child.requestFocus();
386 }
Winson Chung97d85d22011-04-13 11:27:36 -0700387 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700388 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700389 }
390 }
391 wasHandled = true;
392 break;
393 case KeyEvent.KEYCODE_PAGE_DOWN:
394 if (handleKeyEvent) {
395 // Select the first icon on the next page, or the last icon on this page
396 // if there is no next page
397 if (pageIndex < (pageCount - 1)) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700398 newParent = getAppsCustomizePage(container, pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700399 if (newParent != null) {
Adam Cohenae4f1552011-10-20 00:15:42 -0700400 container.snapToPage(pageIndex + 1);
Winson Chung90576b52011-09-27 17:45:27 -0700401 child = newParent.getChildAt(0);
402 if (child != null) child.requestFocus();
403 }
Winson Chung97d85d22011-04-13 11:27:36 -0700404 } else {
Adam Cohenae4f1552011-10-20 00:15:42 -0700405 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700406 }
407 }
408 wasHandled = true;
409 break;
410 case KeyEvent.KEYCODE_MOVE_HOME:
411 if (handleKeyEvent) {
412 // Select the first icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700413 itemContainer.getChildAt(0).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700414 }
415 wasHandled = true;
416 break;
417 case KeyEvent.KEYCODE_MOVE_END:
418 if (handleKeyEvent) {
419 // Select the last icon on this page
Adam Cohenae4f1552011-10-20 00:15:42 -0700420 itemContainer.getChildAt(itemCount - 1).requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700421 }
422 wasHandled = true;
423 break;
424 default: break;
425 }
426 return wasHandled;
427 }
428
429 /**
430 * Handles key events in the tab widget.
431 */
432 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
Daniel Sandlere4f98912013-06-25 15:13:26 -0400433 if (!LauncherAppState.getInstance().isScreenLarge()) return false;
Winson Chung97d85d22011-04-13 11:27:36 -0700434
435 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
Adam Cohenc956cba2014-07-24 09:17:37 -0700436 final AppsCustomizeTabHost tabHost = findTabHostParent(parent);
437 final ViewGroup contents = tabHost.getContent();
Winson Chung97d85d22011-04-13 11:27:36 -0700438 final int tabCount = parent.getTabCount();
439 final int tabIndex = parent.getChildTabIndex(v);
440
441 final int action = e.getAction();
442 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
443 boolean wasHandled = false;
444 switch (keyCode) {
445 case KeyEvent.KEYCODE_DPAD_LEFT:
446 if (handleKeyEvent) {
447 // Select the previous tab
448 if (tabIndex > 0) {
449 parent.getChildTabViewAt(tabIndex - 1).requestFocus();
450 }
451 }
452 wasHandled = true;
453 break;
454 case KeyEvent.KEYCODE_DPAD_RIGHT:
455 if (handleKeyEvent) {
456 // Select the next tab, or if the last tab has a focus right id, select that
457 if (tabIndex < (tabCount - 1)) {
458 parent.getChildTabViewAt(tabIndex + 1).requestFocus();
459 } else {
460 if (v.getNextFocusRightId() != View.NO_ID) {
461 tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
462 }
463 }
464 }
465 wasHandled = true;
466 break;
467 case KeyEvent.KEYCODE_DPAD_UP:
468 // Do nothing
469 wasHandled = true;
470 break;
471 case KeyEvent.KEYCODE_DPAD_DOWN:
472 if (handleKeyEvent) {
473 // Select the content view
474 contents.requestFocus();
475 }
476 wasHandled = true;
477 break;
478 default: break;
479 }
480 return wasHandled;
481 }
482
483 /**
Winson Chung3d503fb2011-07-13 17:25:49 -0700484 * Handles key events in the workspace hotseat (bottom of the screen).
Winson Chung4e6a9762011-05-09 11:56:34 -0700485 */
Winson Chung3d503fb2011-07-13 17:25:49 -0700486 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
Winson Chung4e6a9762011-05-09 11:56:34 -0700487 final ViewGroup parent = (ViewGroup) v.getParent();
488 final ViewGroup launcher = (ViewGroup) parent.getParent();
489 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
490 final int buttonIndex = parent.indexOfChild(v);
491 final int buttonCount = parent.getChildCount();
492 final int pageIndex = workspace.getCurrentPage();
Winson Chung4e6a9762011-05-09 11:56:34 -0700493
494 // NOTE: currently we don't special case for the phone UI in different
Winson Chung3d503fb2011-07-13 17:25:49 -0700495 // orientations, even though the hotseat is on the side in landscape mode. This
Winson Chung4e6a9762011-05-09 11:56:34 -0700496 // is to ensure that accessibility consistency is maintained across rotations.
497
498 final int action = e.getAction();
499 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
500 boolean wasHandled = false;
501 switch (keyCode) {
502 case KeyEvent.KEYCODE_DPAD_LEFT:
503 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700504 // Select the previous button, otherwise snap to the previous page
Winson Chung4e6a9762011-05-09 11:56:34 -0700505 if (buttonIndex > 0) {
506 parent.getChildAt(buttonIndex - 1).requestFocus();
507 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700508 workspace.snapToPage(pageIndex - 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700509 }
510 }
511 wasHandled = true;
512 break;
513 case KeyEvent.KEYCODE_DPAD_RIGHT:
514 if (handleKeyEvent) {
Winson Chung74608b52011-06-23 13:23:20 -0700515 // Select the next button, otherwise snap to the next page
Winson Chung4e6a9762011-05-09 11:56:34 -0700516 if (buttonIndex < (buttonCount - 1)) {
517 parent.getChildAt(buttonIndex + 1).requestFocus();
518 } else {
Winson Chung74608b52011-06-23 13:23:20 -0700519 workspace.snapToPage(pageIndex + 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700520 }
521 }
522 wasHandled = true;
523 break;
524 case KeyEvent.KEYCODE_DPAD_UP:
525 if (handleKeyEvent) {
526 // Select the first bubble text view in the current page of the workspace
527 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700528 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
Adam Cohenac56cff2011-09-28 20:45:37 -0700529 final View newIcon = getIconInDirection(layout, children, -1, 1);
Winson Chung4e6a9762011-05-09 11:56:34 -0700530 if (newIcon != null) {
531 newIcon.requestFocus();
532 } else {
533 workspace.requestFocus();
534 }
535 }
536 wasHandled = true;
537 break;
538 case KeyEvent.KEYCODE_DPAD_DOWN:
539 // Do nothing
540 wasHandled = true;
541 break;
542 default: break;
543 }
544 return wasHandled;
545 }
546
547 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700548 * Private helper method to get the CellLayoutChildren given a CellLayout index.
549 */
Michael Jurkaa52570f2012-03-20 03:18:20 -0700550 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
551 ViewGroup container, int i) {
Winson Chung97d85d22011-04-13 11:27:36 -0700552 ViewGroup parent = (ViewGroup) container.getChildAt(i);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700553 return (ShortcutAndWidgetContainer) parent.getChildAt(0);
Winson Chung97d85d22011-04-13 11:27:36 -0700554 }
555
556 /**
557 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
558 * from top left to bottom right.
559 */
560 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
561 ViewGroup parent) {
562 // First we order each the CellLayout children by their x,y coordinates
563 final int cellCountX = layout.getCountX();
564 final int count = parent.getChildCount();
565 ArrayList<View> views = new ArrayList<View>();
566 for (int j = 0; j < count; ++j) {
567 views.add(parent.getChildAt(j));
568 }
569 Collections.sort(views, new Comparator<View>() {
570 @Override
571 public int compare(View lhs, View rhs) {
572 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
573 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
574 int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
575 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
576 return lvIndex - rvIndex;
577 }
578 });
579 return views;
580 }
581 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700582 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
583 * direction delta.
584 *
Winson Chung97d85d22011-04-13 11:27:36 -0700585 * @param delta either -1 or 1 depending on the direction we want to search
586 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700587 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
Winson Chung97d85d22011-04-13 11:27:36 -0700588 // Then we find the next BubbleTextView offset by delta from i
589 final int count = views.size();
590 int newI = i + delta;
591 while (0 <= newI && newI < count) {
592 View newV = views.get(newI);
Adam Cohenac56cff2011-09-28 20:45:37 -0700593 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
Winson Chung97d85d22011-04-13 11:27:36 -0700594 return newV;
595 }
596 newI += delta;
597 }
598 return null;
599 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700600 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
Winson Chung97d85d22011-04-13 11:27:36 -0700601 int delta) {
602 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
Adam Cohenac56cff2011-09-28 20:45:37 -0700603 return findIndexOfIcon(views, i, delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700604 }
Adam Cohenac56cff2011-09-28 20:45:37 -0700605 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
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, views.indexOf(v), delta);
Winson Chung97d85d22011-04-13 11:27:36 -0700609 }
610 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700611 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
612 * delta on the next line.
613 *
Winson Chung97d85d22011-04-13 11:27:36 -0700614 * @param delta either -1 or 1 depending on the line and direction we want to search
615 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700616 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
Winson Chung97d85d22011-04-13 11:27:36 -0700617 int lineDelta) {
618 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
619 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
Winson Chung97d85d22011-04-13 11:27:36 -0700620 final int cellCountY = layout.getCountY();
621 final int row = lp.cellY;
622 final int newRow = row + lineDelta;
623 if (0 <= newRow && newRow < cellCountY) {
624 float closestDistance = Float.MAX_VALUE;
625 int closestIndex = -1;
626 int index = views.indexOf(v);
627 int endIndex = (lineDelta < 0) ? -1 : views.size();
628 while (index != endIndex) {
629 View newV = views.get(index);
630 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
631 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
Adam Cohenac56cff2011-09-28 20:45:37 -0700632 if (satisfiesRow &&
633 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
Winson Chung97d85d22011-04-13 11:27:36 -0700634 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
635 Math.pow(tmpLp.cellY - lp.cellY, 2));
636 if (tmpDistance < closestDistance) {
637 closestIndex = index;
638 closestDistance = tmpDistance;
639 }
640 }
641 if (index <= endIndex) {
642 ++index;
643 } else {
644 --index;
645 }
646 }
647 if (closestIndex > -1) {
648 return views.get(closestIndex);
649 }
650 }
651 return null;
652 }
653
654 /**
Adam Cohenac56cff2011-09-28 20:45:37 -0700655 * Handles key events in a Workspace containing.
Winson Chung97d85d22011-04-13 11:27:36 -0700656 */
Adam Cohenac56cff2011-09-28 20:45:37 -0700657 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700658 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Winson Chung97d85d22011-04-13 11:27:36 -0700659 final CellLayout layout = (CellLayout) parent.getParent();
660 final Workspace workspace = (Workspace) layout.getParent();
661 final ViewGroup launcher = (ViewGroup) workspace.getParent();
Adam Cohen24ce0b32014-01-14 16:18:14 -0800662 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
Winson Chung4d279d92011-07-21 11:46:32 -0700663 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
Winson Chung97d85d22011-04-13 11:27:36 -0700664 int pageIndex = workspace.indexOfChild(layout);
665 int pageCount = workspace.getChildCount();
666
667 final int action = e.getAction();
668 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
669 boolean wasHandled = false;
670 switch (keyCode) {
671 case KeyEvent.KEYCODE_DPAD_LEFT:
672 if (handleKeyEvent) {
673 // Select the previous icon or the last icon on the previous page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700674 View newIcon = getIconInDirection(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700675 if (newIcon != null) {
676 newIcon.requestFocus();
677 } else {
678 if (pageIndex > 0) {
679 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700680 newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700681 parent.getChildCount(), -1);
682 if (newIcon != null) {
683 newIcon.requestFocus();
684 } else {
685 // Snap to the previous page
686 workspace.snapToPage(pageIndex - 1);
687 }
688 }
689 }
690 }
691 wasHandled = true;
692 break;
693 case KeyEvent.KEYCODE_DPAD_RIGHT:
694 if (handleKeyEvent) {
695 // Select the next icon or the first icon on the next page if possible
Adam Cohenac56cff2011-09-28 20:45:37 -0700696 View newIcon = getIconInDirection(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700697 if (newIcon != null) {
698 newIcon.requestFocus();
699 } else {
700 if (pageIndex < (pageCount - 1)) {
701 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700702 newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700703 if (newIcon != null) {
704 newIcon.requestFocus();
705 } else {
706 // Snap to the next page
707 workspace.snapToPage(pageIndex + 1);
708 }
709 }
710 }
711 }
712 wasHandled = true;
713 break;
714 case KeyEvent.KEYCODE_DPAD_UP:
715 if (handleKeyEvent) {
716 // Select the closest icon in the previous line, otherwise select the tab bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700717 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
Winson Chung97d85d22011-04-13 11:27:36 -0700718 if (newIcon != null) {
719 newIcon.requestFocus();
720 wasHandled = true;
721 } else {
722 tabs.requestFocus();
723 }
724 }
725 break;
726 case KeyEvent.KEYCODE_DPAD_DOWN:
727 if (handleKeyEvent) {
Winson Chung4d279d92011-07-21 11:46:32 -0700728 // Select the closest icon in the next line, otherwise select the button bar
Adam Cohenac56cff2011-09-28 20:45:37 -0700729 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700730 if (newIcon != null) {
731 newIcon.requestFocus();
732 wasHandled = true;
Winson Chung4d279d92011-07-21 11:46:32 -0700733 } else if (hotseat != null) {
734 hotseat.requestFocus();
Winson Chung97d85d22011-04-13 11:27:36 -0700735 }
736 }
737 break;
738 case KeyEvent.KEYCODE_PAGE_UP:
739 if (handleKeyEvent) {
740 // Select the first icon on the previous page or the first icon on this page
741 // if there is no previous page
742 if (pageIndex > 0) {
743 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700744 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700745 if (newIcon != null) {
746 newIcon.requestFocus();
747 } else {
748 // Snap to the previous page
749 workspace.snapToPage(pageIndex - 1);
750 }
751 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700752 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700753 if (newIcon != null) {
754 newIcon.requestFocus();
755 }
756 }
757 }
758 wasHandled = true;
759 break;
760 case KeyEvent.KEYCODE_PAGE_DOWN:
761 if (handleKeyEvent) {
762 // Select the first icon on the next page or the last icon on this page
763 // if there is no previous page
764 if (pageIndex < (pageCount - 1)) {
765 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
Adam Cohenac56cff2011-09-28 20:45:37 -0700766 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700767 if (newIcon != null) {
768 newIcon.requestFocus();
769 } else {
770 // Snap to the next page
771 workspace.snapToPage(pageIndex + 1);
772 }
773 } else {
Adam Cohenac56cff2011-09-28 20:45:37 -0700774 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700775 parent.getChildCount(), -1);
776 if (newIcon != null) {
777 newIcon.requestFocus();
778 }
779 }
780 }
781 wasHandled = true;
782 break;
783 case KeyEvent.KEYCODE_MOVE_HOME:
784 if (handleKeyEvent) {
785 // Select the first icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700786 View newIcon = getIconInDirection(layout, parent, -1, 1);
Winson Chung97d85d22011-04-13 11:27:36 -0700787 if (newIcon != null) {
788 newIcon.requestFocus();
789 }
790 }
791 wasHandled = true;
792 break;
793 case KeyEvent.KEYCODE_MOVE_END:
794 if (handleKeyEvent) {
795 // Select the last icon on this page
Adam Cohenac56cff2011-09-28 20:45:37 -0700796 View newIcon = getIconInDirection(layout, parent,
797 parent.getChildCount(), -1);
798 if (newIcon != null) {
799 newIcon.requestFocus();
800 }
801 }
802 wasHandled = true;
803 break;
804 default: break;
805 }
806 return wasHandled;
807 }
808
809 /**
810 * Handles key events for items in a Folder.
811 */
812 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700813 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700814 final CellLayout layout = (CellLayout) parent.getParent();
Winson Chunga1f133d2013-07-25 11:14:30 -0700815 final ScrollView scrollView = (ScrollView) layout.getParent();
816 final Folder folder = (Folder) scrollView.getParent();
Adam Cohenac56cff2011-09-28 20:45:37 -0700817 View title = folder.mFolderName;
818
819 final int action = e.getAction();
820 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
821 boolean wasHandled = false;
822 switch (keyCode) {
823 case KeyEvent.KEYCODE_DPAD_LEFT:
824 if (handleKeyEvent) {
825 // Select the previous icon
826 View newIcon = getIconInDirection(layout, parent, v, -1);
827 if (newIcon != null) {
828 newIcon.requestFocus();
829 }
830 }
831 wasHandled = true;
832 break;
833 case KeyEvent.KEYCODE_DPAD_RIGHT:
834 if (handleKeyEvent) {
835 // Select the next icon
836 View newIcon = getIconInDirection(layout, parent, v, 1);
837 if (newIcon != null) {
838 newIcon.requestFocus();
839 } else {
840 title.requestFocus();
841 }
842 }
843 wasHandled = true;
844 break;
845 case KeyEvent.KEYCODE_DPAD_UP:
846 if (handleKeyEvent) {
847 // Select the closest icon in the previous line
848 View newIcon = getClosestIconOnLine(layout, parent, v, -1);
849 if (newIcon != null) {
850 newIcon.requestFocus();
851 }
852 }
853 wasHandled = true;
854 break;
855 case KeyEvent.KEYCODE_DPAD_DOWN:
856 if (handleKeyEvent) {
857 // Select the closest icon in the next line
858 View newIcon = getClosestIconOnLine(layout, parent, v, 1);
859 if (newIcon != null) {
860 newIcon.requestFocus();
861 } else {
862 title.requestFocus();
863 }
864 }
865 wasHandled = true;
866 break;
867 case KeyEvent.KEYCODE_MOVE_HOME:
868 if (handleKeyEvent) {
869 // Select the first icon on this page
870 View newIcon = getIconInDirection(layout, parent, -1, 1);
871 if (newIcon != null) {
872 newIcon.requestFocus();
873 }
874 }
875 wasHandled = true;
876 break;
877 case KeyEvent.KEYCODE_MOVE_END:
878 if (handleKeyEvent) {
879 // Select the last icon on this page
880 View newIcon = getIconInDirection(layout, parent,
Winson Chung97d85d22011-04-13 11:27:36 -0700881 parent.getChildCount(), -1);
882 if (newIcon != null) {
883 newIcon.requestFocus();
884 }
885 }
886 wasHandled = true;
887 break;
888 default: break;
889 }
890 return wasHandled;
891 }
892}