blob: 95b562328f544b2c07936f769988ba086812b85e [file] [log] [blame]
Winson Chung97d85d22011-04-13 11:27:36 -07001/*
Hyunyoung Songee3e6a72015-02-20 14:25:27 -08002 * Copyright (C) 2015 The Android Open Source Project
Winson Chung97d85d22011-04-13 11:27:36 -07003 *
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
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080019import android.util.Log;
Winson Chung97d85d22011-04-13 11:27:36 -070020import android.view.KeyEvent;
Sunny Goyalb3726d92014-08-20 16:58:17 -070021import android.view.SoundEffectConstants;
Winson Chung97d85d22011-04-13 11:27:36 -070022import android.view.View;
23import android.view.ViewGroup;
Winson Chung97d85d22011-04-13 11:27:36 -070024
Sunny Goyal6c56c682015-07-16 14:09:05 -070025import com.android.launcher3.config.ProviderConfig;
Adam Cohenf9c184a2016-01-15 16:47:43 -080026import com.android.launcher3.folder.FolderPagedView;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080027import com.android.launcher3.util.FocusLogic;
Adam Cohen091440a2015-03-18 14:16:05 -070028import com.android.launcher3.util.Thunk;
Winson Chungfaa13252011-06-13 18:15:54 -070029
Winson Chung97d85d22011-04-13 11:27:36 -070030/**
Winson Chung4d279d92011-07-21 11:46:32 -070031 * A keyboard listener we set on all the workspace icons.
32 */
Adam Cohenac56cff2011-09-28 20:45:37 -070033class IconKeyEventListener implements View.OnKeyListener {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080034 @Override
Winson Chung4d279d92011-07-21 11:46:32 -070035 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070036 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
37 }
38}
39
40/**
Winson Chung3d503fb2011-07-13 17:25:49 -070041 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070042 */
Adam Cohenac56cff2011-09-28 20:45:37 -070043class HotseatIconKeyEventListener implements View.OnKeyListener {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080044 @Override
Winson Chung4e6a9762011-05-09 11:56:34 -070045 public boolean onKey(View v, int keyCode, KeyEvent event) {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080046 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
Winson Chung4e6a9762011-05-09 11:56:34 -070047 }
48}
49
Tony Wickham0fa5ada2015-11-13 17:32:20 -080050/**
51 * A keyboard listener we set on full screen pages (e.g. custom content).
52 */
53class FullscreenKeyEventListener implements View.OnKeyListener {
54 @Override
55 public boolean onKey(View v, int keyCode, KeyEvent event) {
56 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
57 || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
58 // Handle the key event just like a workspace icon would in these cases. In this case,
59 // it will basically act as if there is a single icon in the top left (so you could
60 // think of the fullscreen page as a focusable fullscreen widget).
61 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
62 }
63 return false;
64 }
65}
66
Winson Chung97d85d22011-04-13 11:27:36 -070067public class FocusHelper {
Winson Chung97d85d22011-04-13 11:27:36 -070068
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080069 private static final String TAG = "FocusHelper";
70 private static final boolean DEBUG = false;
71
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080072 /**
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070073 * Handles key events in paged folder.
Sunny Goyal290800b2015-03-05 11:33:33 -080074 */
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070075 public static class PagedFolderKeyEventListener implements View.OnKeyListener {
Sunny Goyal290800b2015-03-05 11:33:33 -080076
77 private final Folder mFolder;
78
79 public PagedFolderKeyEventListener(Folder folder) {
80 mFolder = folder;
81 }
82
83 @Override
Hyunyoung Songada50982015-04-10 14:35:23 -070084 public boolean onKey(View v, int keyCode, KeyEvent e) {
85 boolean consume = FocusLogic.shouldConsume(keyCode);
86 if (e.getAction() == KeyEvent.ACTION_UP) {
87 return consume;
Sunny Goyal290800b2015-03-05 11:33:33 -080088 }
Hyunyoung Songada50982015-04-10 14:35:23 -070089 if (DEBUG) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070090 Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
Hyunyoung Songada50982015-04-10 14:35:23 -070091 KeyEvent.keyCodeToString(keyCode)));
92 }
Sunny Goyal290800b2015-03-05 11:33:33 -080093
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070094 if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
Sunny Goyal6c56c682015-07-16 14:09:05 -070095 if (ProviderConfig.IS_DOGFOOD_BUILD) {
Hyunyoung Songada50982015-04-10 14:35:23 -070096 throw new IllegalStateException("Parent of the focused item is not supported.");
97 } else {
98 return false;
99 }
100 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800101
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700102 // Initialize variables.
103 final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
104 final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
Hyunyoung Songada50982015-04-10 14:35:23 -0700105
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700106 final int iconIndex = itemContainer.indexOfChild(v);
107 final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
108
109 final int pageIndex = pagedView.indexOfChild(cellLayout);
110 final int pageCount = pagedView.getPageCount();
Sunny Goyalc6205602015-05-21 20:46:33 -0700111 final boolean isLayoutRtl = Utilities.isRtl(v.getResources());
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700112
113 int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
Hyunyoung Songada50982015-04-10 14:35:23 -0700114 // Process focus.
Tony Wickham329d8bf2015-12-04 09:48:17 -0800115 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
116 pageCount, isLayoutRtl);
Hyunyoung Songada50982015-04-10 14:35:23 -0700117 if (newIconIndex == FocusLogic.NOOP) {
118 handleNoopKey(keyCode, v);
119 return consume;
120 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700121 ShortcutAndWidgetContainer newParent = null;
122 View child = null;
123
Hyunyoung Songada50982015-04-10 14:35:23 -0700124 switch (newIconIndex) {
125 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700126 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
127 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700128 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700129 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
130 pagedView.snapToPage(pageIndex - 1);
131 child = newParent.getChildAt(
132 ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
Tony Wickham329d8bf2015-12-04 09:48:17 -0800133 ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
134 row);
Hyunyoung Songada50982015-04-10 14:35:23 -0700135 }
136 break;
137 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700138 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700139 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700140 pagedView.snapToPage(pageIndex - 1);
141 child = newParent.getChildAt(0, 0);
Hyunyoung Songada50982015-04-10 14:35:23 -0700142 }
143 break;
144 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700145 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700146 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700147 pagedView.snapToPage(pageIndex - 1);
Tony Wickham329d8bf2015-12-04 09:48:17 -0800148 child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700149 }
150 break;
151 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700152 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700153 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700154 pagedView.snapToPage(pageIndex + 1);
155 child = newParent.getChildAt(0, 0);
Hyunyoung Songada50982015-04-10 14:35:23 -0700156 }
157 break;
158 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700159 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
160 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700161 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700162 pagedView.snapToPage(pageIndex + 1);
Tony Wickham25189852015-11-04 17:44:32 -0800163 child = FocusLogic.getAdjacentChildInNextFolderPage(
164 newParent, v, newIconIndex);
Hyunyoung Songada50982015-04-10 14:35:23 -0700165 }
166 break;
167 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700168 child = cellLayout.getChildAt(0, 0);
Hyunyoung Songada50982015-04-10 14:35:23 -0700169 break;
170 case FocusLogic.CURRENT_PAGE_LAST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700171 child = pagedView.getLastItem();
Hyunyoung Songada50982015-04-10 14:35:23 -0700172 break;
173 default: // Go to some item on the current page.
174 child = itemContainer.getChildAt(newIconIndex);
175 break;
176 }
177 if (child != null) {
178 child.requestFocus();
179 playSoundEffect(keyCode, v);
180 } else {
181 handleNoopKey(keyCode, v);
182 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800183 return consume;
184 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800185
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700186 public void handleNoopKey(int keyCode, View v) {
187 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
188 mFolder.mFolderName.requestFocus();
189 playSoundEffect(keyCode, v);
190 }
191 }
Sunny Goyal290800b2015-03-05 11:33:33 -0800192 }
193
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800194 /**
Tony Wickham4fc82872015-11-10 16:52:14 -0800195 * Handles key events in the workspace hotseat (bottom of the screen).
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800196 * <p>Currently we don't special case for the phone UI in different orientations, even though
197 * the hotseat is on the side in landscape mode. This is to ensure that accessibility
198 * consistency is maintained across rotations.
199 */
200 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
201 boolean consume = FocusLogic.shouldConsume(keyCode);
202 if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
203 return consume;
204 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800205
Winsonc0b52fe2015-09-09 16:38:15 -0700206 final Launcher launcher = (Launcher) v.getContext();
207 final DeviceProfile profile = launcher.getDeviceProfile();
Adam Cohen2e6da152015-05-06 11:42:25 -0700208
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800209 if (DEBUG) {
210 Log.v(TAG, String.format(
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700211 "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
212 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800213 }
214
215 // Initialize the variables.
Winsonfa56b3f2015-09-14 12:01:13 -0700216 final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800217 final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
218 final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800219
Winsonfa56b3f2015-09-14 12:01:13 -0700220 final ItemInfo itemInfo = (ItemInfo) v.getTag();
Hyunyoung Songb76cd622015-04-16 14:34:09 -0700221 int pageIndex = workspace.getNextPage();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800222 int pageCount = workspace.getChildCount();
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700223 int iconIndex = hotseatParent.indexOfChild(v);
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700224 int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
225 .getChildAt(iconIndex).getLayoutParams()).cellX;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800226
227 final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
Hyunyoung Songb76cd622015-04-16 14:34:09 -0700228 if (iconLayout == null) {
229 // This check is to guard against cases where key strokes rushes in when workspace
230 // child creation/deletion is still in flux. (e.g., during drop or fling
231 // animation.)
232 return consume;
233 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800234 final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
235
236 ViewGroup parent = null;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800237 int[][] matrix = null;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800238
239 if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700240 !profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800241 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800242 true /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800243 iconIndex += iconParent.getChildCount();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800244 parent = iconParent;
245 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700246 profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800247 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800248 false /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800249 iconIndex += iconParent.getChildCount();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800250 parent = iconParent;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800251 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700252 profile.isVerticalBarLayout()) {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800253 keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
Winsonfa56b3f2015-09-14 12:01:13 -0700254 } else if (isUninstallKeyChord(e)) {
255 matrix = FocusLogic.createSparseMatrix(iconLayout);
256 if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
257 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
258 }
259 } else if (isDeleteKeyChord(e)) {
260 matrix = FocusLogic.createSparseMatrix(iconLayout);
Winson2949fb52015-09-24 09:56:11 -0700261 launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
Winsonc0b52fe2015-09-09 16:38:15 -0700262 } else {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800263 // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
264 // matrix extended with hotseat.
265 matrix = FocusLogic.createSparseMatrix(hotseatLayout);
Hyunyoung Song31178b82015-02-24 14:12:51 -0800266 parent = hotseatParent;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800267 }
268
269 // Process the focus.
Tony Wickham329d8bf2015-12-04 09:48:17 -0800270 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
271 pageCount, Utilities.isRtl(v.getResources()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800272
Hyunyoung Song31178b82015-02-24 14:12:51 -0800273 View newIcon = null;
Tony Wickham4fc82872015-11-10 16:52:14 -0800274 switch (newIconIndex) {
275 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
276 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
277 newIcon = parent.getChildAt(0);
278 // TODO(hyunyoungs): handle cases where the child is not an icon but
279 // a folder or a widget.
280 workspace.snapToPage(pageIndex + 1);
281 break;
282 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
283 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
284 newIcon = parent.getChildAt(0);
285 // TODO(hyunyoungs): handle cases where the child is not an icon but
286 // a folder or a widget.
287 workspace.snapToPage(pageIndex - 1);
288 break;
289 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
290 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
291 newIcon = parent.getChildAt(parent.getChildCount() - 1);
292 // TODO(hyunyoungs): handle cases where the child is not an icon but
293 // a folder or a widget.
294 workspace.snapToPage(pageIndex - 1);
295 break;
296 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
297 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
298 // Go to the previous page but keep the focus on the same hotseat icon.
299 workspace.snapToPage(pageIndex - 1);
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800300 // If the page we are going to is fullscreen, have it take the focus from hotseat.
301 CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
302 boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
303 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
304 if (isPrevPageFullscreen) {
305 workspace.getPageAt(pageIndex - 1).requestFocus();
306 }
Tony Wickham4fc82872015-11-10 16:52:14 -0800307 break;
308 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
309 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
310 // Go to the next page but keep the focus on the same hotseat icon.
311 workspace.snapToPage(pageIndex + 1);
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800312 // If the page we are going to is fullscreen, have it take the focus from hotseat.
313 CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
314 boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
315 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
316 if (isNextPageFullscreen) {
317 workspace.getPageAt(pageIndex + 1).requestFocus();
318 }
Tony Wickham4fc82872015-11-10 16:52:14 -0800319 break;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800320 }
321 if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800322 newIconIndex -= iconParent.getChildCount();
323 }
324 if (parent != null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800325 if (newIcon == null && newIconIndex >= 0) {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800326 newIcon = parent.getChildAt(newIconIndex);
327 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800328 if (newIcon != null) {
329 newIcon.requestFocus();
330 playSoundEffect(keyCode, v);
331 }
332 }
333 return consume;
334 }
335
336 /**
337 * Handles key events in a workspace containing icons.
338 */
339 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
340 boolean consume = FocusLogic.shouldConsume(keyCode);
341 if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
342 return consume;
343 }
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700344
Adam Cohen2e6da152015-05-06 11:42:25 -0700345 Launcher launcher = (Launcher) v.getContext();
346 DeviceProfile profile = launcher.getDeviceProfile();
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700347
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800348 if (DEBUG) {
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700349 Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
350 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800351 }
352
353 // Initialize the variables.
354 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Hyunyoung Song38531712015-03-03 19:25:16 -0800355 CellLayout iconLayout = (CellLayout) parent.getParent();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800356 final Workspace workspace = (Workspace) iconLayout.getParent();
Adam Cohen2e6da152015-05-06 11:42:25 -0700357 final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
358 final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.search_drop_target_bar);
359 final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700360
Winsonfa56b3f2015-09-14 12:01:13 -0700361 final ItemInfo itemInfo = (ItemInfo) v.getTag();
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700362 final int iconIndex = parent.indexOfChild(v);
363 final int pageIndex = workspace.indexOfChild(iconLayout);
364 final int pageCount = workspace.getChildCount();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800365
366 CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
367 ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
368 int[][] matrix;
369
Hyunyoung Song31178b82015-02-24 14:12:51 -0800370 // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800371 // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
372 // with the hotseat.
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700373 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800374 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
375 true /* horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Song31178b82015-02-24 14:12:51 -0800376 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700377 profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800378 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
379 false /* horizontal */, profile.inv.hotseatAllAppsRank);
Winsonfa56b3f2015-09-14 12:01:13 -0700380 } else if (isUninstallKeyChord(e)) {
381 matrix = FocusLogic.createSparseMatrix(iconLayout);
382 if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
383 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
384 }
385 } else if (isDeleteKeyChord(e)) {
386 matrix = FocusLogic.createSparseMatrix(iconLayout);
Winson2949fb52015-09-24 09:56:11 -0700387 launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800388 } else {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800389 matrix = FocusLogic.createSparseMatrix(iconLayout);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800390 }
391
392 // Process the focus.
Tony Wickham329d8bf2015-12-04 09:48:17 -0800393 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
394 pageCount, Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800395 boolean isRtl = Utilities.isRtl(v.getResources());
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800396 View newIcon = null;
Tony Wickhamaf78b592015-11-11 09:25:38 -0800397 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800398 switch (newIconIndex) {
399 case FocusLogic.NOOP:
400 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
401 newIcon = tabs;
402 }
403 break;
Hyunyoung Song38531712015-03-03 19:25:16 -0800404 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
Hyunyoung Songada50982015-04-10 14:35:23 -0700405 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
406 int newPageIndex = pageIndex - 1;
407 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
408 newPageIndex = pageIndex + 1;
409 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700410 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
Hyunyoung Songada50982015-04-10 14:35:23 -0700411 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
Hyunyoung Song38531712015-03-03 19:25:16 -0800412 if (parent != null) {
413 iconLayout = (CellLayout) parent.getParent();
Tony Wickham329d8bf2015-12-04 09:48:17 -0800414 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
Tony Wickham4fc82872015-11-10 16:52:14 -0800415 iconLayout.getCountX(), row);
Tony Wickham329d8bf2015-12-04 09:48:17 -0800416 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
417 newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800418 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
419 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
420 isRtl);
421 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
422 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
423 isRtl);
424 } else {
425 newIcon = parent.getChildAt(newIconIndex);
426 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800427 }
428 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800429 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800430 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
431 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
432 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800433 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800434 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
435 workspace.snapToPage(pageIndex - 1);
436 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800437 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800438 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800439 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800440 break;
441 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800442 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800443 break;
Hyunyoung Song38531712015-03-03 19:25:16 -0800444 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
Hyunyoung Songada50982015-04-10 14:35:23 -0700445 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
446 newPageIndex = pageIndex + 1;
447 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
448 newPageIndex = pageIndex - 1;
449 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700450 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
Hyunyoung Songada50982015-04-10 14:35:23 -0700451 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
Hyunyoung Song38531712015-03-03 19:25:16 -0800452 if (parent != null) {
453 iconLayout = (CellLayout) parent.getParent();
Tony Wickham329d8bf2015-12-04 09:48:17 -0800454 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
455 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
456 newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800457 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
458 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
459 isRtl);
460 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
461 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
462 isRtl);
463 } else {
464 newIcon = parent.getChildAt(newIconIndex);
465 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800466 }
467 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800468 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800469 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
470 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800471 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800472 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
473 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800474 break;
475 case FocusLogic.CURRENT_PAGE_LAST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800476 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
477 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800478 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800479 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
480 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800481 break;
482 default:
483 // current page, some item.
484 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
485 newIcon = parent.getChildAt(newIconIndex);
486 } else if (parent.getChildCount() <= newIconIndex &&
487 newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
488 newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
489 }
490 break;
491 }
492 if (newIcon != null) {
493 newIcon.requestFocus();
494 playSoundEffect(keyCode, v);
495 }
496 return consume;
497 }
498
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800499 //
500 // Helper methods.
501 //
502
Winson Chung97d85d22011-04-13 11:27:36 -0700503 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700504 * Private helper method to get the CellLayoutChildren given a CellLayout index.
505 */
Sunny Goyal316490e2015-06-02 09:38:28 -0700506 @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
Michael Jurkaa52570f2012-03-20 03:18:20 -0700507 ViewGroup container, int i) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700508 CellLayout parent = (CellLayout) container.getChildAt(i);
509 return parent.getShortcutsAndWidgets();
Winson Chung97d85d22011-04-13 11:27:36 -0700510 }
511
Winson Chung97d85d22011-04-13 11:27:36 -0700512 /**
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800513 * Helper method to be used for playing sound effects.
Winson Chung97d85d22011-04-13 11:27:36 -0700514 */
Adam Cohen091440a2015-03-18 14:16:05 -0700515 @Thunk static void playSoundEffect(int keyCode, View v) {
Winson Chung97d85d22011-04-13 11:27:36 -0700516 switch (keyCode) {
517 case KeyEvent.KEYCODE_DPAD_LEFT:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800518 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700519 break;
520 case KeyEvent.KEYCODE_DPAD_RIGHT:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800521 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700522 break;
523 case KeyEvent.KEYCODE_DPAD_DOWN:
Winson Chung97d85d22011-04-13 11:27:36 -0700524 case KeyEvent.KEYCODE_PAGE_DOWN:
Winson Chung97d85d22011-04-13 11:27:36 -0700525 case KeyEvent.KEYCODE_MOVE_END:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800526 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Adam Cohenac56cff2011-09-28 20:45:37 -0700527 break;
528 case KeyEvent.KEYCODE_DPAD_UP:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800529 case KeyEvent.KEYCODE_PAGE_UP:
Adam Cohenac56cff2011-09-28 20:45:37 -0700530 case KeyEvent.KEYCODE_MOVE_HOME:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800531 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Adam Cohenac56cff2011-09-28 20:45:37 -0700532 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800533 default:
Winson Chung97d85d22011-04-13 11:27:36 -0700534 break;
Winson Chung97d85d22011-04-13 11:27:36 -0700535 }
Winson Chung97d85d22011-04-13 11:27:36 -0700536 }
Winsonfa56b3f2015-09-14 12:01:13 -0700537
538 /**
539 * Returns whether the key event represents a valid uninstall key chord.
540 */
541 private static boolean isUninstallKeyChord(KeyEvent event) {
542 int keyCode = event.getKeyCode();
543 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
544 event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
545 }
546
547 /**
548 * Returns whether the key event represents a valid delete key chord.
549 */
550 private static boolean isDeleteKeyChord(KeyEvent event) {
551 int keyCode = event.getKeyCode();
552 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
553 event.hasModifiers(KeyEvent.META_CTRL_ON);
554 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800555
556 private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
557 int pageIndex, boolean isRtl) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800558 if (pageIndex - 1 < 0) {
559 return null;
560 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800561 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
562 View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
563 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800564 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800565 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
566 workspace.snapToPage(pageIndex - 1);
567 }
568 return newIcon;
569 }
570
571 private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
572 int pageIndex, boolean isRtl) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800573 if (pageIndex + 1 >= workspace.getPageCount()) {
574 return null;
575 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800576 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
577 View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
578 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800579 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800580 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
581 workspace.snapToPage(pageIndex + 1);
582 }
583 return newIcon;
584 }
585
586 private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
587 View icon;
588 int countX = cellLayout.getCountX();
589 for (int y = 0; y < cellLayout.getCountY(); y++) {
590 int increment = isRtl ? -1 : 1;
591 for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
592 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
593 return icon;
594 }
595 }
596 }
597 return null;
598 }
599
600 private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
601 boolean isRtl) {
602 View icon;
603 int countX = cellLayout.getCountX();
604 for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
605 int increment = isRtl ? 1 : -1;
606 for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
607 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
608 return icon;
609 }
610 }
611 }
612 return null;
613 }
Winson Chung97d85d22011-04-13 11:27:36 -0700614}