1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3; 18 19 import android.util.Log; 20 import android.view.KeyEvent; 21 import android.view.SoundEffectConstants; 22 import android.view.View; 23 import android.view.ViewGroup; 24 25 import com.android.launcher3.config.ProviderConfig; 26 import com.android.launcher3.folder.Folder; 27 import com.android.launcher3.folder.FolderPagedView; 28 import com.android.launcher3.util.FocusLogic; 29 import com.android.launcher3.util.Thunk; 30 31 /** 32 * A keyboard listener we set on all the workspace icons. 33 */ 34 class IconKeyEventListener implements View.OnKeyListener { 35 @Override 36 public boolean onKey(View v, int keyCode, KeyEvent event) { 37 return FocusHelper.handleIconKeyEvent(v, keyCode, event); 38 } 39 } 40 41 /** 42 * A keyboard listener we set on all the hotseat buttons. 43 */ 44 class HotseatIconKeyEventListener implements View.OnKeyListener { 45 @Override 46 public boolean onKey(View v, int keyCode, KeyEvent event) { 47 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event); 48 } 49 } 50 51 /** 52 * A keyboard listener we set on full screen pages (e.g. custom content). 53 */ 54 class FullscreenKeyEventListener implements View.OnKeyListener { 55 @Override 56 public boolean onKey(View v, int keyCode, KeyEvent event) { 57 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT 58 || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) { 59 // Handle the key event just like a workspace icon would in these cases. In this case, 60 // it will basically act as if there is a single icon in the top left (so you could 61 // think of the fullscreen page as a focusable fullscreen widget). 62 return FocusHelper.handleIconKeyEvent(v, keyCode, event); 63 } 64 return false; 65 } 66 } 67 68 public class FocusHelper { 69 70 private static final String TAG = "FocusHelper"; 71 private static final boolean DEBUG = false; 72 73 /** 74 * Handles key events in paged folder. 75 */ 76 public static class PagedFolderKeyEventListener implements View.OnKeyListener { 77 78 private final Folder mFolder; 79 80 public PagedFolderKeyEventListener(Folder folder) { 81 mFolder = folder; 82 } 83 84 @Override 85 public boolean onKey(View v, int keyCode, KeyEvent e) { 86 boolean consume = FocusLogic.shouldConsume(keyCode); 87 if (e.getAction() == KeyEvent.ACTION_UP) { 88 return consume; 89 } 90 if (DEBUG) { 91 Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].", 92 KeyEvent.keyCodeToString(keyCode))); 93 } 94 95 if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) { 96 if (ProviderConfig.IS_DOGFOOD_BUILD) { 97 throw new IllegalStateException("Parent of the focused item is not supported."); 98 } else { 99 return false; 100 } 101 } 102 103 // Initialize variables. 104 final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent(); 105 final CellLayout cellLayout = (CellLayout) itemContainer.getParent(); 106 107 final int iconIndex = itemContainer.indexOfChild(v); 108 final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent(); 109 110 final int pageIndex = pagedView.indexOfChild(cellLayout); 111 final int pageCount = pagedView.getPageCount(); 112 final boolean isLayoutRtl = Utilities.isRtl(v.getResources()); 113 114 int[][] matrix = FocusLogic.createSparseMatrix(cellLayout); 115 // Process focus. 116 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex, 117 pageCount, isLayoutRtl); 118 if (newIconIndex == FocusLogic.NOOP) { 119 handleNoopKey(keyCode, v); 120 return consume; 121 } 122 ShortcutAndWidgetContainer newParent = null; 123 View child = null; 124 125 switch (newIconIndex) { 126 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: 127 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN: 128 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1); 129 if (newParent != null) { 130 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY; 131 pagedView.snapToPage(pageIndex - 1); 132 child = newParent.getChildAt( 133 ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) 134 ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1, 135 row); 136 } 137 break; 138 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM: 139 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1); 140 if (newParent != null) { 141 pagedView.snapToPage(pageIndex - 1); 142 child = newParent.getChildAt(0, 0); 143 } 144 break; 145 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM: 146 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1); 147 if (newParent != null) { 148 pagedView.snapToPage(pageIndex - 1); 149 child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1); 150 } 151 break; 152 case FocusLogic.NEXT_PAGE_FIRST_ITEM: 153 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1); 154 if (newParent != null) { 155 pagedView.snapToPage(pageIndex + 1); 156 child = newParent.getChildAt(0, 0); 157 } 158 break; 159 case FocusLogic.NEXT_PAGE_LEFT_COLUMN: 160 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN: 161 newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1); 162 if (newParent != null) { 163 pagedView.snapToPage(pageIndex + 1); 164 child = FocusLogic.getAdjacentChildInNextFolderPage( 165 newParent, v, newIconIndex); 166 } 167 break; 168 case FocusLogic.CURRENT_PAGE_FIRST_ITEM: 169 child = cellLayout.getChildAt(0, 0); 170 break; 171 case FocusLogic.CURRENT_PAGE_LAST_ITEM: 172 child = pagedView.getLastItem(); 173 break; 174 default: // Go to some item on the current page. 175 child = itemContainer.getChildAt(newIconIndex); 176 break; 177 } 178 if (child != null) { 179 child.requestFocus(); 180 playSoundEffect(keyCode, v); 181 } else { 182 handleNoopKey(keyCode, v); 183 } 184 return consume; 185 } 186 187 public void handleNoopKey(int keyCode, View v) { 188 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { 189 mFolder.mFolderName.requestFocus(); 190 playSoundEffect(keyCode, v); 191 } 192 } 193 } 194 195 /** 196 * Handles key events in the workspace hotseat (bottom of the screen). 197 * <p>Currently we don't special case for the phone UI in different orientations, even though 198 * the hotseat is on the side in landscape mode. This is to ensure that accessibility 199 * consistency is maintained across rotations. 200 */ 201 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) { 202 boolean consume = FocusLogic.shouldConsume(keyCode); 203 if (e.getAction() == KeyEvent.ACTION_UP || !consume) { 204 return consume; 205 } 206 207 final Launcher launcher = Launcher.getLauncher(v.getContext()); 208 final DeviceProfile profile = launcher.getDeviceProfile(); 209 210 if (DEBUG) { 211 Log.v(TAG, String.format( 212 "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s", 213 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout())); 214 } 215 216 // Initialize the variables. 217 final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace); 218 final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent(); 219 final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent(); 220 221 final ItemInfo itemInfo = (ItemInfo) v.getTag(); 222 int pageIndex = workspace.getNextPage(); 223 int pageCount = workspace.getChildCount(); 224 int iconIndex = hotseatParent.indexOfChild(v); 225 int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets() 226 .getChildAt(iconIndex).getLayoutParams()).cellX; 227 228 final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex); 229 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 } 235 final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets(); 236 237 ViewGroup parent = null; 238 int[][] matrix = null; 239 240 if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 241 !profile.isVerticalBarLayout()) { 242 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 243 iconIndex += iconParent.getChildCount(); 244 parent = iconParent; 245 } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && 246 profile.isVerticalBarLayout()) { 247 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 248 iconIndex += iconParent.getChildCount(); 249 parent = iconParent; 250 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && 251 profile.isVerticalBarLayout()) { 252 keyCode = KeyEvent.KEYCODE_PAGE_DOWN; 253 } 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); 260 launcher.removeItem(v, itemInfo, true /* deleteFromDb */); 261 } else { 262 // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the 263 // matrix extended with hotseat. 264 matrix = FocusLogic.createSparseMatrix(hotseatLayout); 265 parent = hotseatParent; 266 } 267 268 // Process the focus. 269 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex, 270 pageCount, Utilities.isRtl(v.getResources())); 271 272 View newIcon = null; 273 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); 299 // 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 } 306 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); 311 // 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 } 318 break; 319 } 320 if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) { 321 newIconIndex -= iconParent.getChildCount(); 322 } 323 if (parent != null) { 324 if (newIcon == null && newIconIndex >= 0) { 325 newIcon = parent.getChildAt(newIconIndex); 326 } 327 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 } 343 344 Launcher launcher = Launcher.getLauncher(v.getContext()); 345 DeviceProfile profile = launcher.getDeviceProfile(); 346 347 if (DEBUG) { 348 Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s", 349 KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout())); 350 } 351 352 // Initialize the variables. 353 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); 354 CellLayout iconLayout = (CellLayout) parent.getParent(); 355 final Workspace workspace = (Workspace) iconLayout.getParent(); 356 final ViewGroup dragLayer = (ViewGroup) workspace.getParent(); 357 final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar); 358 final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat); 359 360 final ItemInfo itemInfo = (ItemInfo) v.getTag(); 361 final int iconIndex = parent.indexOfChild(v); 362 final int pageIndex = workspace.indexOfChild(iconLayout); 363 final int pageCount = workspace.getChildCount(); 364 365 CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0); 366 ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets(); 367 int[][] matrix; 368 369 // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed 370 // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended 371 // with the hotseat. 372 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) { 373 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 374 } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && 375 profile.isVerticalBarLayout()) { 376 matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile); 377 } else if (isUninstallKeyChord(e)) { 378 matrix = FocusLogic.createSparseMatrix(iconLayout); 379 if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) { 380 UninstallDropTarget.startUninstallActivity(launcher, itemInfo); 381 } 382 } else if (isDeleteKeyChord(e)) { 383 matrix = FocusLogic.createSparseMatrix(iconLayout); 384 launcher.removeItem(v, itemInfo, true /* deleteFromDb */); 385 } else { 386 matrix = FocusLogic.createSparseMatrix(iconLayout); 387 } 388 389 // Process the focus. 390 int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex, 391 pageCount, Utilities.isRtl(v.getResources())); 392 boolean isRtl = Utilities.isRtl(v.getResources()); 393 View newIcon = null; 394 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex); 395 switch (newIconIndex) { 396 case FocusLogic.NOOP: 397 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { 398 newIcon = tabs; 399 } 400 break; 401 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN: 402 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN: 403 int newPageIndex = pageIndex - 1; 404 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) { 405 newPageIndex = pageIndex + 1; 406 } 407 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY; 408 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex); 409 if (parent != null) { 410 iconLayout = (CellLayout) parent.getParent(); 411 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, 412 iconLayout.getCountX(), row); 413 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT, 414 newPageIndex, pageCount, Utilities.isRtl(v.getResources())); 415 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) { 416 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, 417 isRtl); 418 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) { 419 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, 420 isRtl); 421 } else { 422 newIcon = parent.getChildAt(newIconIndex); 423 } 424 } 425 break; 426 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM: 427 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1); 428 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl); 429 if (newIcon == null) { 430 // Check the hotseat if no focusable item was found on the workspace. 431 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl); 432 workspace.snapToPage(pageIndex - 1); 433 } 434 break; 435 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM: 436 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl); 437 break; 438 case FocusLogic.NEXT_PAGE_FIRST_ITEM: 439 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl); 440 break; 441 case FocusLogic.NEXT_PAGE_LEFT_COLUMN: 442 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN: 443 newPageIndex = pageIndex + 1; 444 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) { 445 newPageIndex = pageIndex - 1; 446 } 447 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY; 448 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex); 449 if (parent != null) { 450 iconLayout = (CellLayout) parent.getParent(); 451 matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row); 452 newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT, 453 newPageIndex, pageCount, Utilities.isRtl(v.getResources())); 454 if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) { 455 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, 456 isRtl); 457 } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) { 458 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, 459 isRtl); 460 } else { 461 newIcon = parent.getChildAt(newIconIndex); 462 } 463 } 464 break; 465 case FocusLogic.CURRENT_PAGE_FIRST_ITEM: 466 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl); 467 if (newIcon == null) { 468 // Check the hotseat if no focusable item was found on the workspace. 469 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl); 470 } 471 break; 472 case FocusLogic.CURRENT_PAGE_LAST_ITEM: 473 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl); 474 if (newIcon == null) { 475 // Check the hotseat if no focusable item was found on the workspace. 476 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl); 477 } 478 break; 479 default: 480 // current page, some item. 481 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) { 482 newIcon = parent.getChildAt(newIconIndex); 483 } else if (parent.getChildCount() <= newIconIndex && 484 newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) { 485 newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount()); 486 } 487 break; 488 } 489 if (newIcon != null) { 490 newIcon.requestFocus(); 491 playSoundEffect(keyCode, v); 492 } 493 return consume; 494 } 495 496 // 497 // Helper methods. 498 // 499 500 /** 501 * Private helper method to get the CellLayoutChildren given a CellLayout index. 502 */ 503 @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex( 504 ViewGroup container, int i) { 505 CellLayout parent = (CellLayout) container.getChildAt(i); 506 return parent.getShortcutsAndWidgets(); 507 } 508 509 /** 510 * Helper method to be used for playing sound effects. 511 */ 512 @Thunk static void playSoundEffect(int keyCode, View v) { 513 switch (keyCode) { 514 case KeyEvent.KEYCODE_DPAD_LEFT: 515 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 516 break; 517 case KeyEvent.KEYCODE_DPAD_RIGHT: 518 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 519 break; 520 case KeyEvent.KEYCODE_DPAD_DOWN: 521 case KeyEvent.KEYCODE_PAGE_DOWN: 522 case KeyEvent.KEYCODE_MOVE_END: 523 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 524 break; 525 case KeyEvent.KEYCODE_DPAD_UP: 526 case KeyEvent.KEYCODE_PAGE_UP: 527 case KeyEvent.KEYCODE_MOVE_HOME: 528 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 529 break; 530 default: 531 break; 532 } 533 } 534 535 /** 536 * Returns whether the key event represents a valid uninstall key chord. 537 */ 538 private static boolean isUninstallKeyChord(KeyEvent event) { 539 int keyCode = event.getKeyCode(); 540 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) && 541 event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON); 542 } 543 544 /** 545 * Returns whether the key event represents a valid delete key chord. 546 */ 547 private static boolean isDeleteKeyChord(KeyEvent event) { 548 int keyCode = event.getKeyCode(); 549 return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) && 550 event.hasModifiers(KeyEvent.META_CTRL_ON); 551 } 552 553 private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout, 554 int pageIndex, boolean isRtl) { 555 if (pageIndex - 1 < 0) { 556 return null; 557 } 558 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1); 559 View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl); 560 if (newIcon == null) { 561 // Check the hotseat if no focusable item was found on the workspace. 562 newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl); 563 workspace.snapToPage(pageIndex - 1); 564 } 565 return newIcon; 566 } 567 568 private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout, 569 int pageIndex, boolean isRtl) { 570 if (pageIndex + 1 >= workspace.getPageCount()) { 571 return null; 572 } 573 CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1); 574 View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl); 575 if (newIcon == null) { 576 // Check the hotseat if no focusable item was found on the workspace. 577 newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl); 578 workspace.snapToPage(pageIndex + 1); 579 } 580 return newIcon; 581 } 582 583 private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) { 584 View icon; 585 int countX = cellLayout.getCountX(); 586 for (int y = 0; y < cellLayout.getCountY(); y++) { 587 int increment = isRtl ? -1 : 1; 588 for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) { 589 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) { 590 return icon; 591 } 592 } 593 } 594 return null; 595 } 596 597 private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout, 598 boolean isRtl) { 599 View icon; 600 int countX = cellLayout.getCountX(); 601 for (int y = cellLayout.getCountY() - 1; y >= 0; y--) { 602 int increment = isRtl ? 1 : -1; 603 for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) { 604 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) { 605 return icon; 606 } 607 } 608 } 609 return null; 610 } 611 } 612