blob: 44a8ec507d22f594655e1e08fc126d7fcccaf8bf [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
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080025import com.android.launcher3.util.FocusLogic;
Adam Cohen091440a2015-03-18 14:16:05 -070026import com.android.launcher3.util.Thunk;
Winson Chungfaa13252011-06-13 18:15:54 -070027
Winson Chung97d85d22011-04-13 11:27:36 -070028/**
Winson Chung4d279d92011-07-21 11:46:32 -070029 * A keyboard listener we set on all the workspace icons.
30 */
Adam Cohenac56cff2011-09-28 20:45:37 -070031class IconKeyEventListener implements View.OnKeyListener {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080032 @Override
Winson Chung4d279d92011-07-21 11:46:32 -070033 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070034 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
35 }
36}
37
38/**
Winson Chung3d503fb2011-07-13 17:25:49 -070039 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070040 */
Adam Cohenac56cff2011-09-28 20:45:37 -070041class HotseatIconKeyEventListener implements View.OnKeyListener {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080042 @Override
Winson Chung4e6a9762011-05-09 11:56:34 -070043 public boolean onKey(View v, int keyCode, KeyEvent event) {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080044 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
Winson Chung4e6a9762011-05-09 11:56:34 -070045 }
46}
47
Tony Wickham0fa5ada2015-11-13 17:32:20 -080048/**
49 * A keyboard listener we set on full screen pages (e.g. custom content).
50 */
51class FullscreenKeyEventListener implements View.OnKeyListener {
52 @Override
53 public boolean onKey(View v, int keyCode, KeyEvent event) {
54 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
55 || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
56 // Handle the key event just like a workspace icon would in these cases. In this case,
57 // it will basically act as if there is a single icon in the top left (so you could
58 // think of the fullscreen page as a focusable fullscreen widget).
59 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
60 }
61 return false;
62 }
63}
64
Winson Chung97d85d22011-04-13 11:27:36 -070065public class FocusHelper {
Winson Chung97d85d22011-04-13 11:27:36 -070066
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080067 private static final String TAG = "FocusHelper";
68 private static final boolean DEBUG = false;
69
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080070 /**
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070071 * Handles key events in paged folder.
Sunny Goyal290800b2015-03-05 11:33:33 -080072 */
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070073 public static class PagedFolderKeyEventListener implements View.OnKeyListener {
Sunny Goyal290800b2015-03-05 11:33:33 -080074
75 private final Folder mFolder;
76
77 public PagedFolderKeyEventListener(Folder folder) {
78 mFolder = folder;
79 }
80
81 @Override
Hyunyoung Songada50982015-04-10 14:35:23 -070082 public boolean onKey(View v, int keyCode, KeyEvent e) {
83 boolean consume = FocusLogic.shouldConsume(keyCode);
84 if (e.getAction() == KeyEvent.ACTION_UP) {
85 return consume;
Sunny Goyal290800b2015-03-05 11:33:33 -080086 }
Hyunyoung Songada50982015-04-10 14:35:23 -070087 if (DEBUG) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070088 Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
Hyunyoung Songada50982015-04-10 14:35:23 -070089 KeyEvent.keyCodeToString(keyCode)));
90 }
Sunny Goyal290800b2015-03-05 11:33:33 -080091
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070092 if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
Hyunyoung Songada50982015-04-10 14:35:23 -070093 if (LauncherAppState.isDogfoodBuild()) {
94 throw new IllegalStateException("Parent of the focused item is not supported.");
95 } else {
96 return false;
97 }
98 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080099
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700100 // Initialize variables.
101 final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
102 final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
103 final int countX = cellLayout.getCountX();
104 final int countY = cellLayout.getCountY();
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.
Adam Cohen2e6da152015-05-06 11:42:25 -0700115 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
Sunny Goyalc6205602015-05-21 20:46:33 -0700116 countY, matrix, iconIndex, pageIndex, 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)
133 ^ newParent.invertLayoutHorizontally()) ? 0 : countX - 1, row);
Hyunyoung Songada50982015-04-10 14:35:23 -0700134 }
135 break;
136 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700137 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700138 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700139 pagedView.snapToPage(pageIndex - 1);
140 child = newParent.getChildAt(0, 0);
Hyunyoung Songada50982015-04-10 14:35:23 -0700141 }
142 break;
143 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700144 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700145 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700146 pagedView.snapToPage(pageIndex - 1);
147 child = newParent.getChildAt(countX - 1, countY - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700148 }
149 break;
150 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700151 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700152 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700153 pagedView.snapToPage(pageIndex + 1);
154 child = newParent.getChildAt(0, 0);
Hyunyoung Songada50982015-04-10 14:35:23 -0700155 }
156 break;
157 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700158 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
159 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700160 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700161 pagedView.snapToPage(pageIndex + 1);
Tony Wickham25189852015-11-04 17:44:32 -0800162 child = FocusLogic.getAdjacentChildInNextFolderPage(
163 newParent, v, newIconIndex);
Hyunyoung Songada50982015-04-10 14:35:23 -0700164 }
165 break;
166 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700167 child = cellLayout.getChildAt(0, 0);
Hyunyoung Songada50982015-04-10 14:35:23 -0700168 break;
169 case FocusLogic.CURRENT_PAGE_LAST_ITEM:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700170 child = pagedView.getLastItem();
Hyunyoung Songada50982015-04-10 14:35:23 -0700171 break;
172 default: // Go to some item on the current page.
173 child = itemContainer.getChildAt(newIconIndex);
174 break;
175 }
176 if (child != null) {
177 child.requestFocus();
178 playSoundEffect(keyCode, v);
179 } else {
180 handleNoopKey(keyCode, v);
181 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800182 return consume;
183 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800184
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700185 public void handleNoopKey(int keyCode, View v) {
186 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
187 mFolder.mFolderName.requestFocus();
188 playSoundEffect(keyCode, v);
189 }
190 }
Sunny Goyal290800b2015-03-05 11:33:33 -0800191 }
192
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800193 /**
Tony Wickham4fc82872015-11-10 16:52:14 -0800194 * Handles key events in the workspace hotseat (bottom of the screen).
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800195 * <p>Currently we don't special case for the phone UI in different orientations, even though
196 * the hotseat is on the side in landscape mode. This is to ensure that accessibility
197 * consistency is maintained across rotations.
198 */
199 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
200 boolean consume = FocusLogic.shouldConsume(keyCode);
201 if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
202 return consume;
203 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800204
Winsonc0b52fe2015-09-09 16:38:15 -0700205 final Launcher launcher = (Launcher) v.getContext();
206 final DeviceProfile profile = launcher.getDeviceProfile();
Adam Cohen2e6da152015-05-06 11:42:25 -0700207
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800208 if (DEBUG) {
209 Log.v(TAG, String.format(
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700210 "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
211 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800212 }
213
214 // Initialize the variables.
Winsonfa56b3f2015-09-14 12:01:13 -0700215 final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800216 final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
217 final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800218
Winsonfa56b3f2015-09-14 12:01:13 -0700219 final ItemInfo itemInfo = (ItemInfo) v.getTag();
Hyunyoung Songb76cd622015-04-16 14:34:09 -0700220 int pageIndex = workspace.getNextPage();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800221 int pageCount = workspace.getChildCount();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800222 int countX = -1;
223 int countY = -1;
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700224 int iconIndex = hotseatParent.indexOfChild(v);
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700225 int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
226 .getChildAt(iconIndex).getLayoutParams()).cellX;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800227
228 final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
Hyunyoung Songb76cd622015-04-16 14:34:09 -0700229 if (iconLayout == null) {
230 // This check is to guard against cases where key strokes rushes in when workspace
231 // child creation/deletion is still in flux. (e.g., during drop or fling
232 // animation.)
233 return consume;
234 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800235 final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
236
237 ViewGroup parent = null;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800238 int[][] matrix = null;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800239
240 if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700241 !profile.isVerticalBarLayout()) {
242 matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800243 true /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800244 iconIndex += iconParent.getChildCount();
Tony Wickham6cbd2222015-11-09 17:51:08 -0800245 countX = hotseatLayout.getCountX();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800246 countY = iconLayout.getCountY() + hotseatLayout.getCountY();
247 parent = iconParent;
248 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700249 profile.isVerticalBarLayout()) {
250 matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800251 false /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800252 iconIndex += iconParent.getChildCount();
253 countX = iconLayout.getCountX() + hotseatLayout.getCountX();
Tony Wickham6cbd2222015-11-09 17:51:08 -0800254 countY = hotseatLayout.getCountY();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800255 parent = iconParent;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800256 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700257 profile.isVerticalBarLayout()) {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800258 keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
Winsonfa56b3f2015-09-14 12:01:13 -0700259 } else if (isUninstallKeyChord(e)) {
260 matrix = FocusLogic.createSparseMatrix(iconLayout);
261 if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
262 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
263 }
264 } else if (isDeleteKeyChord(e)) {
265 matrix = FocusLogic.createSparseMatrix(iconLayout);
Winson2949fb52015-09-24 09:56:11 -0700266 launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
Winsonc0b52fe2015-09-09 16:38:15 -0700267 } else {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800268 // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
269 // matrix extended with hotseat.
270 matrix = FocusLogic.createSparseMatrix(hotseatLayout);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800271 countX = hotseatLayout.getCountX();
272 countY = hotseatLayout.getCountY();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800273 parent = hotseatParent;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800274 }
275
276 // Process the focus.
Adam Cohen2e6da152015-05-06 11:42:25 -0700277 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
Sunny Goyalc6205602015-05-21 20:46:33 -0700278 countY, matrix, iconIndex, pageIndex, pageCount, Utilities.isRtl(v.getResources()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800279
Hyunyoung Song31178b82015-02-24 14:12:51 -0800280 View newIcon = null;
Tony Wickham4fc82872015-11-10 16:52:14 -0800281 switch (newIconIndex) {
282 case FocusLogic.NEXT_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_FIRST_ITEM:
290 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
291 newIcon = parent.getChildAt(0);
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_LAST_ITEM:
297 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
298 newIcon = parent.getChildAt(parent.getChildCount() - 1);
299 // TODO(hyunyoungs): handle cases where the child is not an icon but
300 // a folder or a widget.
301 workspace.snapToPage(pageIndex - 1);
302 break;
303 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
304 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
305 // Go to the previous page but keep the focus on the same hotseat icon.
306 workspace.snapToPage(pageIndex - 1);
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800307 // If the page we are going to is fullscreen, have it take the focus from hotseat.
308 CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
309 boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
310 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
311 if (isPrevPageFullscreen) {
312 workspace.getPageAt(pageIndex - 1).requestFocus();
313 }
Tony Wickham4fc82872015-11-10 16:52:14 -0800314 break;
315 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
316 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
317 // Go to the next page but keep the focus on the same hotseat icon.
318 workspace.snapToPage(pageIndex + 1);
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800319 // If the page we are going to is fullscreen, have it take the focus from hotseat.
320 CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
321 boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
322 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
323 if (isNextPageFullscreen) {
324 workspace.getPageAt(pageIndex + 1).requestFocus();
325 }
Tony Wickham4fc82872015-11-10 16:52:14 -0800326 break;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800327 }
328 if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800329 newIconIndex -= iconParent.getChildCount();
330 }
331 if (parent != null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800332 if (newIcon == null && newIconIndex >= 0) {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800333 newIcon = parent.getChildAt(newIconIndex);
334 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800335 if (newIcon != null) {
336 newIcon.requestFocus();
337 playSoundEffect(keyCode, v);
338 }
339 }
340 return consume;
341 }
342
343 /**
344 * Handles key events in a workspace containing icons.
345 */
346 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
347 boolean consume = FocusLogic.shouldConsume(keyCode);
348 if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
349 return consume;
350 }
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700351
Adam Cohen2e6da152015-05-06 11:42:25 -0700352 Launcher launcher = (Launcher) v.getContext();
353 DeviceProfile profile = launcher.getDeviceProfile();
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700354
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800355 if (DEBUG) {
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700356 Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
357 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800358 }
359
360 // Initialize the variables.
361 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Hyunyoung Song38531712015-03-03 19:25:16 -0800362 CellLayout iconLayout = (CellLayout) parent.getParent();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800363 final Workspace workspace = (Workspace) iconLayout.getParent();
Adam Cohen2e6da152015-05-06 11:42:25 -0700364 final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
365 final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.search_drop_target_bar);
366 final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700367
Winsonfa56b3f2015-09-14 12:01:13 -0700368 final ItemInfo itemInfo = (ItemInfo) v.getTag();
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700369 final int iconIndex = parent.indexOfChild(v);
370 final int pageIndex = workspace.indexOfChild(iconLayout);
371 final int pageCount = workspace.getChildCount();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800372 int countX = iconLayout.getCountX();
373 int countY = iconLayout.getCountY();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800374
375 CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
376 ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
377 int[][] matrix;
378
Hyunyoung Song31178b82015-02-24 14:12:51 -0800379 // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800380 // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
381 // with the hotseat.
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700382 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
383 matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, true /* horizontal */,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800384 profile.inv.hotseatAllAppsRank);
385 countX = hotseatLayout.getCountX();
386 countY = countY + hotseatLayout.getCountY();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800387 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700388 profile.isVerticalBarLayout()) {
389 matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, false /* horizontal */,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800390 profile.inv.hotseatAllAppsRank);
391 countX = countX + hotseatLayout.getCountX();
392 countY = hotseatLayout.getCountY();
Winsonfa56b3f2015-09-14 12:01:13 -0700393 } else if (isUninstallKeyChord(e)) {
394 matrix = FocusLogic.createSparseMatrix(iconLayout);
395 if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
396 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
397 }
398 } else if (isDeleteKeyChord(e)) {
399 matrix = FocusLogic.createSparseMatrix(iconLayout);
Winson2949fb52015-09-24 09:56:11 -0700400 launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800401 } else {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800402 matrix = FocusLogic.createSparseMatrix(iconLayout);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800403 }
404
405 // Process the focus.
Adam Cohen2e6da152015-05-06 11:42:25 -0700406 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
Sunny Goyalc6205602015-05-21 20:46:33 -0700407 countY, matrix, iconIndex, pageIndex, pageCount, Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800408 boolean isRtl = Utilities.isRtl(v.getResources());
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800409 View newIcon = null;
Tony Wickhamaf78b592015-11-11 09:25:38 -0800410 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800411 switch (newIconIndex) {
412 case FocusLogic.NOOP:
413 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
414 newIcon = tabs;
415 }
416 break;
Hyunyoung Song38531712015-03-03 19:25:16 -0800417 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
Hyunyoung Songada50982015-04-10 14:35:23 -0700418 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
419 int newPageIndex = pageIndex - 1;
420 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
421 newPageIndex = pageIndex + 1;
422 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700423 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
Hyunyoung Songada50982015-04-10 14:35:23 -0700424 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
Hyunyoung Song38531712015-03-03 19:25:16 -0800425 if (parent != null) {
426 iconLayout = (CellLayout) parent.getParent();
Hyunyoung Songac721f82015-03-04 16:33:56 -0800427 matrix = FocusLogic.createSparseMatrix(iconLayout,
Tony Wickham4fc82872015-11-10 16:52:14 -0800428 iconLayout.getCountX(), row);
Adam Cohen2e6da152015-05-06 11:42:25 -0700429 newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
Sunny Goyalc6205602015-05-21 20:46:33 -0700430 matrix, FocusLogic.PIVOT, newPageIndex, pageCount,
431 Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800432 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
433 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
434 isRtl);
435 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
436 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
437 isRtl);
438 } else {
439 newIcon = parent.getChildAt(newIconIndex);
440 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800441 }
442 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800443 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800444 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
445 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
446 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800447 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800448 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
449 workspace.snapToPage(pageIndex - 1);
450 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800451 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800452 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800453 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800454 break;
455 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800456 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800457 break;
Hyunyoung Song38531712015-03-03 19:25:16 -0800458 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
Hyunyoung Songada50982015-04-10 14:35:23 -0700459 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
460 newPageIndex = pageIndex + 1;
461 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
462 newPageIndex = pageIndex - 1;
463 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700464 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
Hyunyoung Songada50982015-04-10 14:35:23 -0700465 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
Hyunyoung Song38531712015-03-03 19:25:16 -0800466 if (parent != null) {
467 iconLayout = (CellLayout) parent.getParent();
Hyunyoung Songac721f82015-03-04 16:33:56 -0800468 matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
Adam Cohen2e6da152015-05-06 11:42:25 -0700469 newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
Sunny Goyalc6205602015-05-21 20:46:33 -0700470 matrix, FocusLogic.PIVOT, newPageIndex, pageCount,
471 Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800472 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
473 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
474 isRtl);
475 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
476 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
477 isRtl);
478 } else {
479 newIcon = parent.getChildAt(newIconIndex);
480 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800481 }
482 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800483 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800484 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
485 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800486 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800487 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
488 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800489 break;
490 case FocusLogic.CURRENT_PAGE_LAST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800491 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
492 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800493 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800494 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
495 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800496 break;
497 default:
498 // current page, some item.
499 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
500 newIcon = parent.getChildAt(newIconIndex);
501 } else if (parent.getChildCount() <= newIconIndex &&
502 newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
503 newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
504 }
505 break;
506 }
507 if (newIcon != null) {
508 newIcon.requestFocus();
509 playSoundEffect(keyCode, v);
510 }
511 return consume;
512 }
513
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800514 //
515 // Helper methods.
516 //
517
Winson Chung97d85d22011-04-13 11:27:36 -0700518 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700519 * Private helper method to get the CellLayoutChildren given a CellLayout index.
520 */
Sunny Goyal316490e2015-06-02 09:38:28 -0700521 @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
Michael Jurkaa52570f2012-03-20 03:18:20 -0700522 ViewGroup container, int i) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700523 CellLayout parent = (CellLayout) container.getChildAt(i);
524 return parent.getShortcutsAndWidgets();
Winson Chung97d85d22011-04-13 11:27:36 -0700525 }
526
Winson Chung97d85d22011-04-13 11:27:36 -0700527 /**
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800528 * Helper method to be used for playing sound effects.
Winson Chung97d85d22011-04-13 11:27:36 -0700529 */
Adam Cohen091440a2015-03-18 14:16:05 -0700530 @Thunk static void playSoundEffect(int keyCode, View v) {
Winson Chung97d85d22011-04-13 11:27:36 -0700531 switch (keyCode) {
532 case KeyEvent.KEYCODE_DPAD_LEFT:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800533 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700534 break;
535 case KeyEvent.KEYCODE_DPAD_RIGHT:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800536 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700537 break;
538 case KeyEvent.KEYCODE_DPAD_DOWN:
Winson Chung97d85d22011-04-13 11:27:36 -0700539 case KeyEvent.KEYCODE_PAGE_DOWN:
Winson Chung97d85d22011-04-13 11:27:36 -0700540 case KeyEvent.KEYCODE_MOVE_END:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800541 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Adam Cohenac56cff2011-09-28 20:45:37 -0700542 break;
543 case KeyEvent.KEYCODE_DPAD_UP:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800544 case KeyEvent.KEYCODE_PAGE_UP:
Adam Cohenac56cff2011-09-28 20:45:37 -0700545 case KeyEvent.KEYCODE_MOVE_HOME:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800546 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Adam Cohenac56cff2011-09-28 20:45:37 -0700547 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800548 default:
Winson Chung97d85d22011-04-13 11:27:36 -0700549 break;
Winson Chung97d85d22011-04-13 11:27:36 -0700550 }
Winson Chung97d85d22011-04-13 11:27:36 -0700551 }
Winsonfa56b3f2015-09-14 12:01:13 -0700552
553 /**
554 * Returns whether the key event represents a valid uninstall key chord.
555 */
556 private static boolean isUninstallKeyChord(KeyEvent event) {
557 int keyCode = event.getKeyCode();
558 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
559 event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
560 }
561
562 /**
563 * Returns whether the key event represents a valid delete key chord.
564 */
565 private static boolean isDeleteKeyChord(KeyEvent event) {
566 int keyCode = event.getKeyCode();
567 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
568 event.hasModifiers(KeyEvent.META_CTRL_ON);
569 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800570
571 private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
572 int pageIndex, boolean isRtl) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800573 if (pageIndex - 1 < 0) {
574 return null;
575 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800576 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
577 View newIcon = getFirstFocusableIconInReverseReadingOrder(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 = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
581 workspace.snapToPage(pageIndex - 1);
582 }
583 return newIcon;
584 }
585
586 private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
587 int pageIndex, boolean isRtl) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800588 if (pageIndex + 1 >= workspace.getPageCount()) {
589 return null;
590 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800591 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
592 View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
593 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800594 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800595 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
596 workspace.snapToPage(pageIndex + 1);
597 }
598 return newIcon;
599 }
600
601 private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
602 View icon;
603 int countX = cellLayout.getCountX();
604 for (int y = 0; y < cellLayout.getCountY(); y++) {
605 int increment = isRtl ? -1 : 1;
606 for (int x = isRtl ? countX - 1 : 0; 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 }
614
615 private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
616 boolean isRtl) {
617 View icon;
618 int countX = cellLayout.getCountX();
619 for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
620 int increment = isRtl ? 1 : -1;
621 for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
622 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
623 return icon;
624 }
625 }
626 }
627 return null;
628 }
Winson Chung97d85d22011-04-13 11:27:36 -0700629}