1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3; 18 19 import android.content.res.Configuration; 20 import android.view.KeyEvent; 21 import android.view.SoundEffectConstants; 22 import android.view.View; 23 import android.view.ViewGroup; 24 import android.widget.ScrollView; 25 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.Comparator; 29 30 /** 31 * A keyboard listener we set on all the workspace icons. 32 */ 33 class IconKeyEventListener implements View.OnKeyListener { 34 public boolean onKey(View v, int keyCode, KeyEvent event) { 35 return FocusHelper.handleIconKeyEvent(v, keyCode, event); 36 } 37 } 38 39 /** 40 * A keyboard listener we set on all the workspace icons. 41 */ 42 class FolderKeyEventListener implements View.OnKeyListener { 43 public boolean onKey(View v, int keyCode, KeyEvent event) { 44 return FocusHelper.handleFolderKeyEvent(v, keyCode, event); 45 } 46 } 47 48 /** 49 * A keyboard listener we set on all the hotseat buttons. 50 */ 51 class HotseatIconKeyEventListener implements View.OnKeyListener { 52 public boolean onKey(View v, int keyCode, KeyEvent event) { 53 final Configuration configuration = v.getResources().getConfiguration(); 54 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation); 55 } 56 } 57 58 public class FocusHelper { 59 60 /** 61 * Returns the Viewgroup containing page contents for the page at the index specified. 62 */ 63 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) { 64 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index); 65 if (page instanceof CellLayout) { 66 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren 67 page = ((CellLayout) page).getShortcutsAndWidgets(); 68 } 69 return page; 70 } 71 72 /** 73 * Handles key events in a PageViewCellLayout containing PagedViewIcons. 74 */ 75 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) { 76 ViewGroup parentLayout; 77 ViewGroup itemContainer; 78 int countX; 79 int countY; 80 if (v.getParent() instanceof ShortcutAndWidgetContainer) { 81 itemContainer = (ViewGroup) v.getParent(); 82 parentLayout = (ViewGroup) itemContainer.getParent(); 83 countX = ((CellLayout) parentLayout).getCountX(); 84 countY = ((CellLayout) parentLayout).getCountY(); 85 } else { 86 itemContainer = parentLayout = (ViewGroup) v.getParent(); 87 countX = ((PagedViewGridLayout) parentLayout).getCellCountX(); 88 countY = ((PagedViewGridLayout) parentLayout).getCellCountY(); 89 } 90 91 // Note we have an extra parent because of the 92 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship 93 final PagedView container = (PagedView) parentLayout.getParent(); 94 final int iconIndex = itemContainer.indexOfChild(v); 95 final int itemCount = itemContainer.getChildCount(); 96 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout)); 97 final int pageCount = container.getChildCount(); 98 99 final int x = iconIndex % countX; 100 final int y = iconIndex / countX; 101 102 final int action = e.getAction(); 103 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 104 ViewGroup newParent = null; 105 // Side pages do not always load synchronously, so check before focusing child siblings 106 // willy-nilly 107 View child = null; 108 boolean wasHandled = false; 109 switch (keyCode) { 110 case KeyEvent.KEYCODE_DPAD_LEFT: 111 if (handleKeyEvent) { 112 // Select the previous icon or the last icon on the previous page 113 if (iconIndex > 0) { 114 itemContainer.getChildAt(iconIndex - 1).requestFocus(); 115 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 116 } else { 117 if (pageIndex > 0) { 118 newParent = getAppsCustomizePage(container, pageIndex - 1); 119 if (newParent != null) { 120 container.snapToPage(pageIndex - 1); 121 child = newParent.getChildAt(newParent.getChildCount() - 1); 122 if (child != null) { 123 child.requestFocus(); 124 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 125 } 126 } 127 } 128 } 129 } 130 wasHandled = true; 131 break; 132 case KeyEvent.KEYCODE_DPAD_RIGHT: 133 if (handleKeyEvent) { 134 // Select the next icon or the first icon on the next page 135 if (iconIndex < (itemCount - 1)) { 136 itemContainer.getChildAt(iconIndex + 1).requestFocus(); 137 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 138 } else { 139 if (pageIndex < (pageCount - 1)) { 140 newParent = getAppsCustomizePage(container, pageIndex + 1); 141 if (newParent != null) { 142 container.snapToPage(pageIndex + 1); 143 child = newParent.getChildAt(0); 144 if (child != null) { 145 child.requestFocus(); 146 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 147 } 148 } 149 } 150 } 151 } 152 wasHandled = true; 153 break; 154 case KeyEvent.KEYCODE_DPAD_UP: 155 if (handleKeyEvent) { 156 // Select the closest icon in the previous row, otherwise select the tab bar 157 if (y > 0) { 158 int newiconIndex = ((y - 1) * countX) + x; 159 itemContainer.getChildAt(newiconIndex).requestFocus(); 160 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 161 } 162 } 163 wasHandled = true; 164 break; 165 case KeyEvent.KEYCODE_DPAD_DOWN: 166 if (handleKeyEvent) { 167 // Select the closest icon in the next row, otherwise do nothing 168 if (y < (countY - 1)) { 169 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x); 170 int newIconY = newiconIndex / countX; 171 if (newIconY != y) { 172 itemContainer.getChildAt(newiconIndex).requestFocus(); 173 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 174 } 175 } 176 } 177 wasHandled = true; 178 break; 179 case KeyEvent.KEYCODE_PAGE_UP: 180 if (handleKeyEvent) { 181 // Select the first icon on the previous page, or the first icon on this page 182 // if there is no previous page 183 if (pageIndex > 0) { 184 newParent = getAppsCustomizePage(container, pageIndex - 1); 185 if (newParent != null) { 186 container.snapToPage(pageIndex - 1); 187 child = newParent.getChildAt(0); 188 if (child != null) { 189 child.requestFocus(); 190 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 191 } 192 } 193 } else { 194 itemContainer.getChildAt(0).requestFocus(); 195 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 196 } 197 } 198 wasHandled = true; 199 break; 200 case KeyEvent.KEYCODE_PAGE_DOWN: 201 if (handleKeyEvent) { 202 // Select the first icon on the next page, or the last icon on this page 203 // if there is no next page 204 if (pageIndex < (pageCount - 1)) { 205 newParent = getAppsCustomizePage(container, pageIndex + 1); 206 if (newParent != null) { 207 container.snapToPage(pageIndex + 1); 208 child = newParent.getChildAt(0); 209 if (child != null) { 210 child.requestFocus(); 211 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 212 } 213 } 214 } else { 215 itemContainer.getChildAt(itemCount - 1).requestFocus(); 216 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 217 } 218 } 219 wasHandled = true; 220 break; 221 case KeyEvent.KEYCODE_MOVE_HOME: 222 if (handleKeyEvent) { 223 // Select the first icon on this page 224 itemContainer.getChildAt(0).requestFocus(); 225 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 226 } 227 wasHandled = true; 228 break; 229 case KeyEvent.KEYCODE_MOVE_END: 230 if (handleKeyEvent) { 231 // Select the last icon on this page 232 itemContainer.getChildAt(itemCount - 1).requestFocus(); 233 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 234 } 235 wasHandled = true; 236 break; 237 default: break; 238 } 239 return wasHandled; 240 } 241 242 /** 243 * Handles key events in the workspace hotseat (bottom of the screen). 244 */ 245 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) { 246 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); 247 final CellLayout layout = (CellLayout) parent.getParent(); 248 249 // NOTE: currently we don't special case for the phone UI in different 250 // orientations, even though the hotseat is on the side in landscape mode. This 251 // is to ensure that accessibility consistency is maintained across rotations. 252 final int action = e.getAction(); 253 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 254 boolean wasHandled = false; 255 switch (keyCode) { 256 case KeyEvent.KEYCODE_DPAD_LEFT: 257 if (handleKeyEvent) { 258 ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 259 int myIndex = views.indexOf(v); 260 // Select the previous button, otherwise do nothing 261 if (myIndex > 0) { 262 views.get(myIndex - 1).requestFocus(); 263 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 264 } 265 } 266 wasHandled = true; 267 break; 268 case KeyEvent.KEYCODE_DPAD_RIGHT: 269 if (handleKeyEvent) { 270 ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 271 int myIndex = views.indexOf(v); 272 // Select the next button, otherwise do nothing 273 if (myIndex < views.size() - 1) { 274 views.get(myIndex + 1).requestFocus(); 275 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 276 } 277 } 278 wasHandled = true; 279 break; 280 case KeyEvent.KEYCODE_DPAD_UP: 281 if (handleKeyEvent) { 282 final Workspace workspace = (Workspace) 283 v.getRootView().findViewById(R.id.workspace); 284 if (workspace != null) { 285 int pageIndex = workspace.getCurrentPage(); 286 CellLayout topLayout = (CellLayout) workspace.getChildAt(pageIndex); 287 ShortcutAndWidgetContainer children = topLayout.getShortcutsAndWidgets(); 288 final View newIcon = getIconInDirection(layout, children, -1, 1); 289 // Select the first bubble text view in the current page of the workspace 290 if (newIcon != null) { 291 newIcon.requestFocus(); 292 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 293 } else { 294 workspace.requestFocus(); 295 } 296 } 297 } 298 wasHandled = true; 299 break; 300 case KeyEvent.KEYCODE_DPAD_DOWN: 301 // Do nothing 302 wasHandled = true; 303 break; 304 default: break; 305 } 306 return wasHandled; 307 } 308 309 /** 310 * Private helper method to get the CellLayoutChildren given a CellLayout index. 311 */ 312 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex( 313 ViewGroup container, int i) { 314 CellLayout parent = (CellLayout) container.getChildAt(i); 315 return parent.getShortcutsAndWidgets(); 316 } 317 318 /** 319 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially 320 * from top left to bottom right. 321 */ 322 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout, 323 ViewGroup parent) { 324 // First we order each the CellLayout children by their x,y coordinates 325 final int cellCountX = layout.getCountX(); 326 final int count = parent.getChildCount(); 327 ArrayList<View> views = new ArrayList<View>(); 328 for (int j = 0; j < count; ++j) { 329 views.add(parent.getChildAt(j)); 330 } 331 Collections.sort(views, new Comparator<View>() { 332 @Override 333 public int compare(View lhs, View rhs) { 334 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams(); 335 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams(); 336 int lvIndex = (llp.cellY * cellCountX) + llp.cellX; 337 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX; 338 return lvIndex - rvIndex; 339 } 340 }); 341 return views; 342 } 343 /** 344 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the 345 * direction delta. 346 * 347 * @param delta either -1 or 1 depending on the direction we want to search 348 */ 349 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) { 350 // Then we find the next BubbleTextView offset by delta from i 351 final int count = views.size(); 352 int newI = i + delta; 353 while (0 <= newI && newI < count) { 354 View newV = views.get(newI); 355 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) { 356 return newV; 357 } 358 newI += delta; 359 } 360 return null; 361 } 362 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i, 363 int delta) { 364 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 365 return findIndexOfIcon(views, i, delta); 366 } 367 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v, 368 int delta) { 369 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 370 return findIndexOfIcon(views, views.indexOf(v), delta); 371 } 372 /** 373 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction 374 * delta on the next line. 375 * 376 * @param delta either -1 or 1 depending on the line and direction we want to search 377 */ 378 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v, 379 int lineDelta) { 380 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 381 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 382 final int cellCountY = layout.getCountY(); 383 final int row = lp.cellY; 384 final int newRow = row + lineDelta; 385 if (0 <= newRow && newRow < cellCountY) { 386 float closestDistance = Float.MAX_VALUE; 387 int closestIndex = -1; 388 int index = views.indexOf(v); 389 int endIndex = (lineDelta < 0) ? -1 : views.size(); 390 while (index != endIndex) { 391 View newV = views.get(index); 392 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams(); 393 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row); 394 if (satisfiesRow && 395 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) { 396 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) + 397 Math.pow(tmpLp.cellY - lp.cellY, 2)); 398 if (tmpDistance < closestDistance) { 399 closestIndex = index; 400 closestDistance = tmpDistance; 401 } 402 } 403 if (index <= endIndex) { 404 ++index; 405 } else { 406 --index; 407 } 408 } 409 if (closestIndex > -1) { 410 return views.get(closestIndex); 411 } 412 } 413 return null; 414 } 415 416 /** 417 * Handles key events in a Workspace containing. 418 */ 419 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) { 420 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); 421 final CellLayout layout = (CellLayout) parent.getParent(); 422 final Workspace workspace = (Workspace) layout.getParent(); 423 final ViewGroup launcher = (ViewGroup) workspace.getParent(); 424 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar); 425 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat); 426 int pageIndex = workspace.indexOfChild(layout); 427 int pageCount = workspace.getChildCount(); 428 429 final int action = e.getAction(); 430 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 431 boolean wasHandled = false; 432 switch (keyCode) { 433 case KeyEvent.KEYCODE_DPAD_LEFT: 434 if (handleKeyEvent) { 435 // Select the previous icon or the last icon on the previous page if possible 436 View newIcon = getIconInDirection(layout, parent, v, -1); 437 if (newIcon != null) { 438 newIcon.requestFocus(); 439 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 440 } else { 441 if (pageIndex > 0) { 442 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 443 newIcon = getIconInDirection(layout, parent, 444 parent.getChildCount(), -1); 445 if (newIcon != null) { 446 newIcon.requestFocus(); 447 } else { 448 // Snap to the previous page 449 workspace.snapToPage(pageIndex - 1); 450 } 451 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 452 } 453 } 454 } 455 wasHandled = true; 456 break; 457 case KeyEvent.KEYCODE_DPAD_RIGHT: 458 if (handleKeyEvent) { 459 // Select the next icon or the first icon on the next page if possible 460 View newIcon = getIconInDirection(layout, parent, v, 1); 461 if (newIcon != null) { 462 newIcon.requestFocus(); 463 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 464 } else { 465 if (pageIndex < (pageCount - 1)) { 466 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); 467 newIcon = getIconInDirection(layout, parent, -1, 1); 468 if (newIcon != null) { 469 newIcon.requestFocus(); 470 } else { 471 // Snap to the next page 472 workspace.snapToPage(pageIndex + 1); 473 } 474 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 475 } 476 } 477 } 478 wasHandled = true; 479 break; 480 case KeyEvent.KEYCODE_DPAD_UP: 481 if (handleKeyEvent) { 482 // Select the closest icon in the previous line, otherwise select the tab bar 483 View newIcon = getClosestIconOnLine(layout, parent, v, -1); 484 if (newIcon != null) { 485 newIcon.requestFocus(); 486 wasHandled = true; 487 } else { 488 tabs.requestFocus(); 489 } 490 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 491 } 492 break; 493 case KeyEvent.KEYCODE_DPAD_DOWN: 494 if (handleKeyEvent) { 495 // Select the closest icon in the next line, otherwise select the button bar 496 View newIcon = getClosestIconOnLine(layout, parent, v, 1); 497 if (newIcon != null) { 498 newIcon.requestFocus(); 499 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 500 wasHandled = true; 501 } else if (hotseat != null) { 502 hotseat.requestFocus(); 503 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 504 } 505 } 506 break; 507 case KeyEvent.KEYCODE_PAGE_UP: 508 if (handleKeyEvent) { 509 // Select the first icon on the previous page or the first icon on this page 510 // if there is no previous page 511 if (pageIndex > 0) { 512 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 513 View newIcon = getIconInDirection(layout, parent, -1, 1); 514 if (newIcon != null) { 515 newIcon.requestFocus(); 516 } else { 517 // Snap to the previous page 518 workspace.snapToPage(pageIndex - 1); 519 } 520 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 521 } else { 522 View newIcon = getIconInDirection(layout, parent, -1, 1); 523 if (newIcon != null) { 524 newIcon.requestFocus(); 525 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 526 } 527 } 528 } 529 wasHandled = true; 530 break; 531 case KeyEvent.KEYCODE_PAGE_DOWN: 532 if (handleKeyEvent) { 533 // Select the first icon on the next page or the last icon on this page 534 // if there is no previous page 535 if (pageIndex < (pageCount - 1)) { 536 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); 537 View newIcon = getIconInDirection(layout, parent, -1, 1); 538 if (newIcon != null) { 539 newIcon.requestFocus(); 540 } else { 541 // Snap to the next page 542 workspace.snapToPage(pageIndex + 1); 543 } 544 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 545 } else { 546 View newIcon = getIconInDirection(layout, parent, 547 parent.getChildCount(), -1); 548 if (newIcon != null) { 549 newIcon.requestFocus(); 550 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 551 } 552 } 553 } 554 wasHandled = true; 555 break; 556 case KeyEvent.KEYCODE_MOVE_HOME: 557 if (handleKeyEvent) { 558 // Select the first icon on this page 559 View newIcon = getIconInDirection(layout, parent, -1, 1); 560 if (newIcon != null) { 561 newIcon.requestFocus(); 562 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 563 } 564 } 565 wasHandled = true; 566 break; 567 case KeyEvent.KEYCODE_MOVE_END: 568 if (handleKeyEvent) { 569 // Select the last icon on this page 570 View newIcon = getIconInDirection(layout, parent, 571 parent.getChildCount(), -1); 572 if (newIcon != null) { 573 newIcon.requestFocus(); 574 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 575 } 576 } 577 wasHandled = true; 578 break; 579 default: break; 580 } 581 return wasHandled; 582 } 583 584 /** 585 * Handles key events for items in a Folder. 586 */ 587 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) { 588 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); 589 final CellLayout layout = (CellLayout) parent.getParent(); 590 final ScrollView scrollView = (ScrollView) layout.getParent(); 591 final Folder folder = (Folder) scrollView.getParent(); 592 View title = folder.mFolderName; 593 594 final int action = e.getAction(); 595 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 596 boolean wasHandled = false; 597 switch (keyCode) { 598 case KeyEvent.KEYCODE_DPAD_LEFT: 599 if (handleKeyEvent) { 600 // Select the previous icon 601 View newIcon = getIconInDirection(layout, parent, v, -1); 602 if (newIcon != null) { 603 newIcon.requestFocus(); 604 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); 605 } 606 } 607 wasHandled = true; 608 break; 609 case KeyEvent.KEYCODE_DPAD_RIGHT: 610 if (handleKeyEvent) { 611 // Select the next icon 612 View newIcon = getIconInDirection(layout, parent, v, 1); 613 if (newIcon != null) { 614 newIcon.requestFocus(); 615 } else { 616 title.requestFocus(); 617 } 618 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); 619 } 620 wasHandled = true; 621 break; 622 case KeyEvent.KEYCODE_DPAD_UP: 623 if (handleKeyEvent) { 624 // Select the closest icon in the previous line 625 View newIcon = getClosestIconOnLine(layout, parent, v, -1); 626 if (newIcon != null) { 627 newIcon.requestFocus(); 628 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 629 } 630 } 631 wasHandled = true; 632 break; 633 case KeyEvent.KEYCODE_DPAD_DOWN: 634 if (handleKeyEvent) { 635 // Select the closest icon in the next line 636 View newIcon = getClosestIconOnLine(layout, parent, v, 1); 637 if (newIcon != null) { 638 newIcon.requestFocus(); 639 } else { 640 title.requestFocus(); 641 } 642 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 643 } 644 wasHandled = true; 645 break; 646 case KeyEvent.KEYCODE_MOVE_HOME: 647 if (handleKeyEvent) { 648 // Select the first icon on this page 649 View newIcon = getIconInDirection(layout, parent, -1, 1); 650 if (newIcon != null) { 651 newIcon.requestFocus(); 652 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP); 653 } 654 } 655 wasHandled = true; 656 break; 657 case KeyEvent.KEYCODE_MOVE_END: 658 if (handleKeyEvent) { 659 // Select the last icon on this page 660 View newIcon = getIconInDirection(layout, parent, 661 parent.getChildCount(), -1); 662 if (newIcon != null) { 663 newIcon.requestFocus(); 664 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN); 665 } 666 } 667 wasHandled = true; 668 break; 669 default: break; 670 } 671 return wasHandled; 672 } 673 } 674