blob: 2af6a73b90544169c2f8ed8548024822b2351837 [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;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080026import com.android.launcher3.util.FocusLogic;
Adam Cohen091440a2015-03-18 14:16:05 -070027import com.android.launcher3.util.Thunk;
Winson Chungfaa13252011-06-13 18:15:54 -070028
Winson Chung97d85d22011-04-13 11:27:36 -070029/**
Winson Chung4d279d92011-07-21 11:46:32 -070030 * A keyboard listener we set on all the workspace icons.
31 */
Adam Cohenac56cff2011-09-28 20:45:37 -070032class IconKeyEventListener implements View.OnKeyListener {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080033 @Override
Winson Chung4d279d92011-07-21 11:46:32 -070034 public boolean onKey(View v, int keyCode, KeyEvent event) {
Adam Cohenac56cff2011-09-28 20:45:37 -070035 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
36 }
37}
38
39/**
Winson Chung3d503fb2011-07-13 17:25:49 -070040 * A keyboard listener we set on all the hotseat buttons.
Winson Chung4e6a9762011-05-09 11:56:34 -070041 */
Adam Cohenac56cff2011-09-28 20:45:37 -070042class HotseatIconKeyEventListener implements View.OnKeyListener {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080043 @Override
Winson Chung4e6a9762011-05-09 11:56:34 -070044 public boolean onKey(View v, int keyCode, KeyEvent event) {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080045 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
Winson Chung4e6a9762011-05-09 11:56:34 -070046 }
47}
48
Tony Wickham0fa5ada2015-11-13 17:32:20 -080049/**
50 * A keyboard listener we set on full screen pages (e.g. custom content).
51 */
52class FullscreenKeyEventListener implements View.OnKeyListener {
53 @Override
54 public boolean onKey(View v, int keyCode, KeyEvent event) {
55 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
56 || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
57 // Handle the key event just like a workspace icon would in these cases. In this case,
58 // it will basically act as if there is a single icon in the top left (so you could
59 // think of the fullscreen page as a focusable fullscreen widget).
60 return FocusHelper.handleIconKeyEvent(v, keyCode, event);
61 }
62 return false;
63 }
64}
65
Winson Chung97d85d22011-04-13 11:27:36 -070066public class FocusHelper {
Winson Chung97d85d22011-04-13 11:27:36 -070067
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080068 private static final String TAG = "FocusHelper";
69 private static final boolean DEBUG = false;
70
Hyunyoung Songee3e6a72015-02-20 14:25:27 -080071 /**
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070072 * Handles key events in paged folder.
Sunny Goyal290800b2015-03-05 11:33:33 -080073 */
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070074 public static class PagedFolderKeyEventListener implements View.OnKeyListener {
Sunny Goyal290800b2015-03-05 11:33:33 -080075
76 private final Folder mFolder;
77
78 public PagedFolderKeyEventListener(Folder folder) {
79 mFolder = folder;
80 }
81
82 @Override
Hyunyoung Songada50982015-04-10 14:35:23 -070083 public boolean onKey(View v, int keyCode, KeyEvent e) {
84 boolean consume = FocusLogic.shouldConsume(keyCode);
85 if (e.getAction() == KeyEvent.ACTION_UP) {
86 return consume;
Sunny Goyal290800b2015-03-05 11:33:33 -080087 }
Hyunyoung Songada50982015-04-10 14:35:23 -070088 if (DEBUG) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070089 Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
Hyunyoung Songada50982015-04-10 14:35:23 -070090 KeyEvent.keyCodeToString(keyCode)));
91 }
Sunny Goyal290800b2015-03-05 11:33:33 -080092
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -070093 if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
Sunny Goyal6c56c682015-07-16 14:09:05 -070094 if (ProviderConfig.IS_DOGFOOD_BUILD) {
Hyunyoung Songada50982015-04-10 14:35:23 -070095 throw new IllegalStateException("Parent of the focused item is not supported.");
96 } else {
97 return false;
98 }
99 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800100
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700101 // Initialize variables.
102 final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
103 final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
Hyunyoung Songada50982015-04-10 14:35:23 -0700104
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700105 final int iconIndex = itemContainer.indexOfChild(v);
106 final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
107
108 final int pageIndex = pagedView.indexOfChild(cellLayout);
109 final int pageCount = pagedView.getPageCount();
Sunny Goyalc6205602015-05-21 20:46:33 -0700110 final boolean isLayoutRtl = Utilities.isRtl(v.getResources());
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700111
112 int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
Hyunyoung Songada50982015-04-10 14:35:23 -0700113 // Process focus.
Tony Wickham329d8bf2015-12-04 09:48:17 -0800114 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
115 pageCount, isLayoutRtl);
Hyunyoung Songada50982015-04-10 14:35:23 -0700116 if (newIconIndex == FocusLogic.NOOP) {
117 handleNoopKey(keyCode, v);
118 return consume;
119 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700120 ShortcutAndWidgetContainer newParent = null;
121 View child = null;
122
Hyunyoung Songada50982015-04-10 14:35:23 -0700123 switch (newIconIndex) {
124 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700125 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
126 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
Hyunyoung Songada50982015-04-10 14:35:23 -0700127 if (newParent != null) {
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700128 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
129 pagedView.snapToPage(pageIndex - 1);
130 child = newParent.getChildAt(
131 ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
Tony Wickham329d8bf2015-12-04 09:48:17 -0800132 ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
133 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);
Tony Wickham329d8bf2015-12-04 09:48:17 -0800147 child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 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();
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700222 int iconIndex = hotseatParent.indexOfChild(v);
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700223 int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
224 .getChildAt(iconIndex).getLayoutParams()).cellX;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800225
226 final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
Hyunyoung Songb76cd622015-04-16 14:34:09 -0700227 if (iconLayout == null) {
228 // This check is to guard against cases where key strokes rushes in when workspace
229 // child creation/deletion is still in flux. (e.g., during drop or fling
230 // animation.)
231 return consume;
232 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800233 final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
234
235 ViewGroup parent = null;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800236 int[][] matrix = null;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800237
238 if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700239 !profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800240 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800241 true /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800242 iconIndex += iconParent.getChildCount();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800243 parent = iconParent;
244 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700245 profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800246 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
Tony Wickham6cbd2222015-11-09 17:51:08 -0800247 false /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800248 iconIndex += iconParent.getChildCount();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800249 parent = iconParent;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800250 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700251 profile.isVerticalBarLayout()) {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800252 keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
Winsonfa56b3f2015-09-14 12:01:13 -0700253 } else if (isUninstallKeyChord(e)) {
254 matrix = FocusLogic.createSparseMatrix(iconLayout);
255 if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
256 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
257 }
258 } else if (isDeleteKeyChord(e)) {
259 matrix = FocusLogic.createSparseMatrix(iconLayout);
Winson2949fb52015-09-24 09:56:11 -0700260 launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
Winsonc0b52fe2015-09-09 16:38:15 -0700261 } else {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800262 // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
263 // matrix extended with hotseat.
264 matrix = FocusLogic.createSparseMatrix(hotseatLayout);
Hyunyoung Song31178b82015-02-24 14:12:51 -0800265 parent = hotseatParent;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800266 }
267
268 // Process the focus.
Tony Wickham329d8bf2015-12-04 09:48:17 -0800269 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
270 pageCount, Utilities.isRtl(v.getResources()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800271
Hyunyoung Song31178b82015-02-24 14:12:51 -0800272 View newIcon = null;
Tony Wickham4fc82872015-11-10 16:52:14 -0800273 switch (newIconIndex) {
274 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
275 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
276 newIcon = parent.getChildAt(0);
277 // TODO(hyunyoungs): handle cases where the child is not an icon but
278 // a folder or a widget.
279 workspace.snapToPage(pageIndex + 1);
280 break;
281 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
282 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
283 newIcon = parent.getChildAt(0);
284 // TODO(hyunyoungs): handle cases where the child is not an icon but
285 // a folder or a widget.
286 workspace.snapToPage(pageIndex - 1);
287 break;
288 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
289 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
290 newIcon = parent.getChildAt(parent.getChildCount() - 1);
291 // TODO(hyunyoungs): handle cases where the child is not an icon but
292 // a folder or a widget.
293 workspace.snapToPage(pageIndex - 1);
294 break;
295 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
296 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
297 // Go to the previous page but keep the focus on the same hotseat icon.
298 workspace.snapToPage(pageIndex - 1);
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800299 // If the page we are going to is fullscreen, have it take the focus from hotseat.
300 CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
301 boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
302 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
303 if (isPrevPageFullscreen) {
304 workspace.getPageAt(pageIndex - 1).requestFocus();
305 }
Tony Wickham4fc82872015-11-10 16:52:14 -0800306 break;
307 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
308 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
309 // Go to the next page but keep the focus on the same hotseat icon.
310 workspace.snapToPage(pageIndex + 1);
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800311 // If the page we are going to is fullscreen, have it take the focus from hotseat.
312 CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
313 boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
314 .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
315 if (isNextPageFullscreen) {
316 workspace.getPageAt(pageIndex + 1).requestFocus();
317 }
Tony Wickham4fc82872015-11-10 16:52:14 -0800318 break;
Hyunyoung Song31178b82015-02-24 14:12:51 -0800319 }
320 if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800321 newIconIndex -= iconParent.getChildCount();
322 }
323 if (parent != null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800324 if (newIcon == null && newIconIndex >= 0) {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800325 newIcon = parent.getChildAt(newIconIndex);
326 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800327 if (newIcon != null) {
328 newIcon.requestFocus();
329 playSoundEffect(keyCode, v);
330 }
331 }
332 return consume;
333 }
334
335 /**
336 * Handles key events in a workspace containing icons.
337 */
338 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
339 boolean consume = FocusLogic.shouldConsume(keyCode);
340 if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
341 return consume;
342 }
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700343
Adam Cohen2e6da152015-05-06 11:42:25 -0700344 Launcher launcher = (Launcher) v.getContext();
345 DeviceProfile profile = launcher.getDeviceProfile();
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700346
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800347 if (DEBUG) {
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700348 Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
349 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800350 }
351
352 // Initialize the variables.
353 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
Hyunyoung Song38531712015-03-03 19:25:16 -0800354 CellLayout iconLayout = (CellLayout) parent.getParent();
Hyunyoung Song31178b82015-02-24 14:12:51 -0800355 final Workspace workspace = (Workspace) iconLayout.getParent();
Adam Cohen2e6da152015-05-06 11:42:25 -0700356 final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
357 final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.search_drop_target_bar);
358 final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700359
Winsonfa56b3f2015-09-14 12:01:13 -0700360 final ItemInfo itemInfo = (ItemInfo) v.getTag();
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700361 final int iconIndex = parent.indexOfChild(v);
362 final int pageIndex = workspace.indexOfChild(iconLayout);
363 final int pageCount = workspace.getChildCount();
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800364
365 CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
366 ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
367 int[][] matrix;
368
Hyunyoung Song31178b82015-02-24 14:12:51 -0800369 // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800370 // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
371 // with the hotseat.
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700372 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800373 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
374 true /* horizontal */, profile.inv.hotseatAllAppsRank);
Hyunyoung Song31178b82015-02-24 14:12:51 -0800375 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700376 profile.isVerticalBarLayout()) {
Tony Wickham329d8bf2015-12-04 09:48:17 -0800377 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
378 false /* horizontal */, profile.inv.hotseatAllAppsRank);
Winsonfa56b3f2015-09-14 12:01:13 -0700379 } else if (isUninstallKeyChord(e)) {
380 matrix = FocusLogic.createSparseMatrix(iconLayout);
381 if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
382 UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
383 }
384 } else if (isDeleteKeyChord(e)) {
385 matrix = FocusLogic.createSparseMatrix(iconLayout);
Winson2949fb52015-09-24 09:56:11 -0700386 launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800387 } else {
Hyunyoung Song31178b82015-02-24 14:12:51 -0800388 matrix = FocusLogic.createSparseMatrix(iconLayout);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800389 }
390
391 // Process the focus.
Tony Wickham329d8bf2015-12-04 09:48:17 -0800392 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
393 pageCount, Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800394 boolean isRtl = Utilities.isRtl(v.getResources());
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800395 View newIcon = null;
Tony Wickhamaf78b592015-11-11 09:25:38 -0800396 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800397 switch (newIconIndex) {
398 case FocusLogic.NOOP:
399 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
400 newIcon = tabs;
401 }
402 break;
Hyunyoung Song38531712015-03-03 19:25:16 -0800403 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
Hyunyoung Songada50982015-04-10 14:35:23 -0700404 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
405 int newPageIndex = pageIndex - 1;
406 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
407 newPageIndex = pageIndex + 1;
408 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700409 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
Hyunyoung Songada50982015-04-10 14:35:23 -0700410 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
Hyunyoung Song38531712015-03-03 19:25:16 -0800411 if (parent != null) {
412 iconLayout = (CellLayout) parent.getParent();
Tony Wickham329d8bf2015-12-04 09:48:17 -0800413 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
Tony Wickham4fc82872015-11-10 16:52:14 -0800414 iconLayout.getCountX(), row);
Tony Wickham329d8bf2015-12-04 09:48:17 -0800415 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
416 newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800417 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
418 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
419 isRtl);
420 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
421 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
422 isRtl);
423 } else {
424 newIcon = parent.getChildAt(newIconIndex);
425 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800426 }
427 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800428 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800429 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
430 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
431 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800432 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800433 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
434 workspace.snapToPage(pageIndex - 1);
435 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800436 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800437 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800438 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800439 break;
440 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800441 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800442 break;
Hyunyoung Song38531712015-03-03 19:25:16 -0800443 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
Hyunyoung Songada50982015-04-10 14:35:23 -0700444 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
445 newPageIndex = pageIndex + 1;
446 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
447 newPageIndex = pageIndex - 1;
448 }
Sunny Goyalfc3c1ed2015-04-09 18:48:21 -0700449 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
Hyunyoung Songada50982015-04-10 14:35:23 -0700450 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
Hyunyoung Song38531712015-03-03 19:25:16 -0800451 if (parent != null) {
452 iconLayout = (CellLayout) parent.getParent();
Tony Wickham329d8bf2015-12-04 09:48:17 -0800453 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
454 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
455 newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
Tony Wickhamaf78b592015-11-11 09:25:38 -0800456 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
457 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
458 isRtl);
459 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
460 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
461 isRtl);
462 } else {
463 newIcon = parent.getChildAt(newIconIndex);
464 }
Hyunyoung Song38531712015-03-03 19:25:16 -0800465 }
466 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800467 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800468 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
469 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800470 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800471 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
472 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800473 break;
474 case FocusLogic.CURRENT_PAGE_LAST_ITEM:
Tony Wickhamaf78b592015-11-11 09:25:38 -0800475 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
476 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800477 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800478 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
479 }
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800480 break;
481 default:
482 // current page, some item.
483 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
484 newIcon = parent.getChildAt(newIconIndex);
485 } else if (parent.getChildCount() <= newIconIndex &&
486 newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
487 newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
488 }
489 break;
490 }
491 if (newIcon != null) {
492 newIcon.requestFocus();
493 playSoundEffect(keyCode, v);
494 }
495 return consume;
496 }
497
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800498 //
499 // Helper methods.
500 //
501
Winson Chung97d85d22011-04-13 11:27:36 -0700502 /**
Winson Chung97d85d22011-04-13 11:27:36 -0700503 * Private helper method to get the CellLayoutChildren given a CellLayout index.
504 */
Sunny Goyal316490e2015-06-02 09:38:28 -0700505 @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
Michael Jurkaa52570f2012-03-20 03:18:20 -0700506 ViewGroup container, int i) {
Sunny Goyalb3726d92014-08-20 16:58:17 -0700507 CellLayout parent = (CellLayout) container.getChildAt(i);
508 return parent.getShortcutsAndWidgets();
Winson Chung97d85d22011-04-13 11:27:36 -0700509 }
510
Winson Chung97d85d22011-04-13 11:27:36 -0700511 /**
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800512 * Helper method to be used for playing sound effects.
Winson Chung97d85d22011-04-13 11:27:36 -0700513 */
Adam Cohen091440a2015-03-18 14:16:05 -0700514 @Thunk static void playSoundEffect(int keyCode, View v) {
Winson Chung97d85d22011-04-13 11:27:36 -0700515 switch (keyCode) {
516 case KeyEvent.KEYCODE_DPAD_LEFT:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800517 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
Winson Chung97d85d22011-04-13 11:27:36 -0700518 break;
519 case KeyEvent.KEYCODE_DPAD_RIGHT:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800520 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
Winson Chung97d85d22011-04-13 11:27:36 -0700521 break;
522 case KeyEvent.KEYCODE_DPAD_DOWN:
Winson Chung97d85d22011-04-13 11:27:36 -0700523 case KeyEvent.KEYCODE_PAGE_DOWN:
Winson Chung97d85d22011-04-13 11:27:36 -0700524 case KeyEvent.KEYCODE_MOVE_END:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800525 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
Adam Cohenac56cff2011-09-28 20:45:37 -0700526 break;
527 case KeyEvent.KEYCODE_DPAD_UP:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800528 case KeyEvent.KEYCODE_PAGE_UP:
Adam Cohenac56cff2011-09-28 20:45:37 -0700529 case KeyEvent.KEYCODE_MOVE_HOME:
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800530 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
Adam Cohenac56cff2011-09-28 20:45:37 -0700531 break;
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800532 default:
Winson Chung97d85d22011-04-13 11:27:36 -0700533 break;
Winson Chung97d85d22011-04-13 11:27:36 -0700534 }
Winson Chung97d85d22011-04-13 11:27:36 -0700535 }
Winsonfa56b3f2015-09-14 12:01:13 -0700536
537 /**
538 * Returns whether the key event represents a valid uninstall key chord.
539 */
540 private static boolean isUninstallKeyChord(KeyEvent event) {
541 int keyCode = event.getKeyCode();
542 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
543 event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
544 }
545
546 /**
547 * Returns whether the key event represents a valid delete key chord.
548 */
549 private static boolean isDeleteKeyChord(KeyEvent event) {
550 int keyCode = event.getKeyCode();
551 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
552 event.hasModifiers(KeyEvent.META_CTRL_ON);
553 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800554
555 private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
556 int pageIndex, boolean isRtl) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800557 if (pageIndex - 1 < 0) {
558 return null;
559 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800560 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
561 View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
562 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800563 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800564 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
565 workspace.snapToPage(pageIndex - 1);
566 }
567 return newIcon;
568 }
569
570 private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
571 int pageIndex, boolean isRtl) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800572 if (pageIndex + 1 >= workspace.getPageCount()) {
573 return null;
574 }
Tony Wickhamaf78b592015-11-11 09:25:38 -0800575 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
576 View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
577 if (newIcon == null) {
Tony Wickham0fa5ada2015-11-13 17:32:20 -0800578 // Check the hotseat if no focusable item was found on the workspace.
Tony Wickhamaf78b592015-11-11 09:25:38 -0800579 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
580 workspace.snapToPage(pageIndex + 1);
581 }
582 return newIcon;
583 }
584
585 private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
586 View icon;
587 int countX = cellLayout.getCountX();
588 for (int y = 0; y < cellLayout.getCountY(); y++) {
589 int increment = isRtl ? -1 : 1;
590 for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
591 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
592 return icon;
593 }
594 }
595 }
596 return null;
597 }
598
599 private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
600 boolean isRtl) {
601 View icon;
602 int countX = cellLayout.getCountX();
603 for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
604 int increment = isRtl ? 1 : -1;
605 for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
606 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
607 return icon;
608 }
609 }
610 }
611 return null;
612 }
Winson Chung97d85d22011-04-13 11:27:36 -0700613}