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.launcher2; 18 19 import android.content.res.Configuration; 20 import android.view.KeyEvent; 21 import android.view.View; 22 import android.view.ViewGroup; 23 import android.view.ViewParent; 24 import android.widget.TabHost; 25 import android.widget.TabWidget; 26 27 import com.android.launcher.R; 28 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.Comparator; 32 33 /** 34 * A keyboard listener we set on all the workspace icons. 35 */ 36 class IconKeyEventListener implements View.OnKeyListener { 37 public boolean onKey(View v, int keyCode, KeyEvent event) { 38 return FocusHelper.handleIconKeyEvent(v, keyCode, event); 39 } 40 } 41 42 /** 43 * A keyboard listener we set on all the workspace icons. 44 */ 45 class FolderKeyEventListener implements View.OnKeyListener { 46 public boolean onKey(View v, int keyCode, KeyEvent event) { 47 return FocusHelper.handleFolderKeyEvent(v, keyCode, event); 48 } 49 } 50 51 /** 52 * A keyboard listener we set on all the hotseat buttons. 53 */ 54 class HotseatIconKeyEventListener implements View.OnKeyListener { 55 public boolean onKey(View v, int keyCode, KeyEvent event) { 56 final Configuration configuration = v.getResources().getConfiguration(); 57 return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation); 58 } 59 } 60 61 /** 62 * A keyboard listener we set on the last tab button in AppsCustomize to jump to then 63 * market icon and vice versa. 64 */ 65 class AppsCustomizeTabKeyEventListener implements View.OnKeyListener { 66 public boolean onKey(View v, int keyCode, KeyEvent event) { 67 return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event); 68 } 69 } 70 71 public class FocusHelper { 72 /** 73 * Private helper to get the parent TabHost in the view hiearchy. 74 */ 75 private static TabHost findTabHostParent(View v) { 76 ViewParent p = v.getParent(); 77 while (p != null && !(p instanceof TabHost)) { 78 p = p.getParent(); 79 } 80 return (TabHost) p; 81 } 82 83 /** 84 * Handles key events in a AppsCustomize tab between the last tab view and the shop button. 85 */ 86 static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) { 87 final TabHost tabHost = findTabHostParent(v); 88 final ViewGroup contents = tabHost.getTabContentView(); 89 final View shop = tabHost.findViewById(R.id.market_button); 90 91 final int action = e.getAction(); 92 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 93 boolean wasHandled = false; 94 switch (keyCode) { 95 case KeyEvent.KEYCODE_DPAD_RIGHT: 96 if (handleKeyEvent) { 97 // Select the shop button if we aren't on it 98 if (v != shop) { 99 shop.requestFocus(); 100 } 101 } 102 wasHandled = true; 103 break; 104 case KeyEvent.KEYCODE_DPAD_DOWN: 105 if (handleKeyEvent) { 106 // Select the content view (down is handled by the tab key handler otherwise) 107 if (v == shop) { 108 contents.requestFocus(); 109 wasHandled = true; 110 } 111 } 112 break; 113 default: break; 114 } 115 return wasHandled; 116 } 117 118 /** 119 * Returns the Viewgroup containing page contents for the page at the index specified. 120 */ 121 private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) { 122 ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index); 123 if (page instanceof PagedViewCellLayout) { 124 // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren 125 page = (ViewGroup) page.getChildAt(0); 126 } 127 return page; 128 } 129 130 /** 131 * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets. 132 */ 133 static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode, 134 KeyEvent e) { 135 136 final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent(); 137 final PagedView container = (PagedView) parent.getParent(); 138 final TabHost tabHost = findTabHostParent(container); 139 final TabWidget tabs = tabHost.getTabWidget(); 140 final int widgetIndex = parent.indexOfChild(w); 141 final int widgetCount = parent.getChildCount(); 142 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent)); 143 final int pageCount = container.getChildCount(); 144 final int cellCountX = parent.getCellCountX(); 145 final int cellCountY = parent.getCellCountY(); 146 final int x = widgetIndex % cellCountX; 147 final int y = widgetIndex / cellCountX; 148 149 final int action = e.getAction(); 150 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 151 ViewGroup newParent = null; 152 // Now that we load items in the bg asynchronously, we can't just focus 153 // child siblings willy-nilly 154 View child = null; 155 boolean wasHandled = false; 156 switch (keyCode) { 157 case KeyEvent.KEYCODE_DPAD_LEFT: 158 if (handleKeyEvent) { 159 // Select the previous widget or the last widget on the previous page 160 if (widgetIndex > 0) { 161 parent.getChildAt(widgetIndex - 1).requestFocus(); 162 } else { 163 if (pageIndex > 0) { 164 newParent = getAppsCustomizePage(container, pageIndex - 1); 165 if (newParent != null) { 166 child = newParent.getChildAt(newParent.getChildCount() - 1); 167 if (child != null) child.requestFocus(); 168 } 169 } 170 } 171 } 172 wasHandled = true; 173 break; 174 case KeyEvent.KEYCODE_DPAD_RIGHT: 175 if (handleKeyEvent) { 176 // Select the next widget or the first widget on the next page 177 if (widgetIndex < (widgetCount - 1)) { 178 parent.getChildAt(widgetIndex + 1).requestFocus(); 179 } else { 180 if (pageIndex < (pageCount - 1)) { 181 newParent = getAppsCustomizePage(container, pageIndex + 1); 182 if (newParent != null) { 183 child = newParent.getChildAt(0); 184 if (child != null) child.requestFocus(); 185 } 186 } 187 } 188 } 189 wasHandled = true; 190 break; 191 case KeyEvent.KEYCODE_DPAD_UP: 192 if (handleKeyEvent) { 193 // Select the closest icon in the previous row, otherwise select the tab bar 194 if (y > 0) { 195 int newWidgetIndex = ((y - 1) * cellCountX) + x; 196 child = parent.getChildAt(newWidgetIndex); 197 if (child != null) child.requestFocus(); 198 } else { 199 tabs.requestFocus(); 200 } 201 } 202 wasHandled = true; 203 break; 204 case KeyEvent.KEYCODE_DPAD_DOWN: 205 if (handleKeyEvent) { 206 // Select the closest icon in the previous row, otherwise do nothing 207 if (y < (cellCountY - 1)) { 208 int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x); 209 child = parent.getChildAt(newWidgetIndex); 210 if (child != null) child.requestFocus(); 211 } 212 } 213 wasHandled = true; 214 break; 215 case KeyEvent.KEYCODE_ENTER: 216 case KeyEvent.KEYCODE_DPAD_CENTER: 217 if (handleKeyEvent) { 218 // Simulate a click on the widget 219 View.OnClickListener clickListener = (View.OnClickListener) container; 220 clickListener.onClick(w); 221 } 222 wasHandled = true; 223 break; 224 case KeyEvent.KEYCODE_PAGE_UP: 225 if (handleKeyEvent) { 226 // Select the first item on the previous page, or the first item on this page 227 // if there is no previous page 228 if (pageIndex > 0) { 229 newParent = getAppsCustomizePage(container, pageIndex - 1); 230 if (newParent != null) { 231 child = newParent.getChildAt(0); 232 } 233 } else { 234 child = parent.getChildAt(0); 235 } 236 if (child != null) child.requestFocus(); 237 } 238 wasHandled = true; 239 break; 240 case KeyEvent.KEYCODE_PAGE_DOWN: 241 if (handleKeyEvent) { 242 // Select the first item on the next page, or the last item on this page 243 // if there is no next page 244 if (pageIndex < (pageCount - 1)) { 245 newParent = getAppsCustomizePage(container, pageIndex + 1); 246 if (newParent != null) { 247 child = newParent.getChildAt(0); 248 } 249 } else { 250 child = parent.getChildAt(widgetCount - 1); 251 } 252 if (child != null) child.requestFocus(); 253 } 254 wasHandled = true; 255 break; 256 case KeyEvent.KEYCODE_MOVE_HOME: 257 if (handleKeyEvent) { 258 // Select the first item on this page 259 child = parent.getChildAt(0); 260 if (child != null) child.requestFocus(); 261 } 262 wasHandled = true; 263 break; 264 case KeyEvent.KEYCODE_MOVE_END: 265 if (handleKeyEvent) { 266 // Select the last item on this page 267 parent.getChildAt(widgetCount - 1).requestFocus(); 268 } 269 wasHandled = true; 270 break; 271 default: break; 272 } 273 return wasHandled; 274 } 275 276 /** 277 * Handles key events in a PageViewCellLayout containing PagedViewIcons. 278 */ 279 static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) { 280 ViewGroup parentLayout; 281 ViewGroup itemContainer; 282 int countX; 283 int countY; 284 if (v.getParent() instanceof PagedViewCellLayoutChildren) { 285 itemContainer = (ViewGroup) v.getParent(); 286 parentLayout = (ViewGroup) itemContainer.getParent(); 287 countX = ((PagedViewCellLayout) parentLayout).getCellCountX(); 288 countY = ((PagedViewCellLayout) parentLayout).getCellCountY(); 289 } else { 290 itemContainer = parentLayout = (ViewGroup) v.getParent(); 291 countX = ((PagedViewGridLayout) parentLayout).getCellCountX(); 292 countY = ((PagedViewGridLayout) parentLayout).getCellCountY(); 293 } 294 295 // Note we have an extra parent because of the 296 // PagedViewCellLayout/PagedViewCellLayoutChildren relationship 297 final PagedView container = (PagedView) parentLayout.getParent(); 298 final TabHost tabHost = findTabHostParent(container); 299 final TabWidget tabs = tabHost.getTabWidget(); 300 final int iconIndex = itemContainer.indexOfChild(v); 301 final int itemCount = itemContainer.getChildCount(); 302 final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout)); 303 final int pageCount = container.getChildCount(); 304 305 final int x = iconIndex % countX; 306 final int y = iconIndex / countX; 307 308 final int action = e.getAction(); 309 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 310 ViewGroup newParent = null; 311 // Side pages do not always load synchronously, so check before focusing child siblings 312 // willy-nilly 313 View child = null; 314 boolean wasHandled = false; 315 switch (keyCode) { 316 case KeyEvent.KEYCODE_DPAD_LEFT: 317 if (handleKeyEvent) { 318 // Select the previous icon or the last icon on the previous page 319 if (iconIndex > 0) { 320 itemContainer.getChildAt(iconIndex - 1).requestFocus(); 321 } else { 322 if (pageIndex > 0) { 323 newParent = getAppsCustomizePage(container, pageIndex - 1); 324 if (newParent != null) { 325 container.snapToPage(pageIndex - 1); 326 child = newParent.getChildAt(newParent.getChildCount() - 1); 327 if (child != null) child.requestFocus(); 328 } 329 } 330 } 331 } 332 wasHandled = true; 333 break; 334 case KeyEvent.KEYCODE_DPAD_RIGHT: 335 if (handleKeyEvent) { 336 // Select the next icon or the first icon on the next page 337 if (iconIndex < (itemCount - 1)) { 338 itemContainer.getChildAt(iconIndex + 1).requestFocus(); 339 } else { 340 if (pageIndex < (pageCount - 1)) { 341 newParent = getAppsCustomizePage(container, pageIndex + 1); 342 if (newParent != null) { 343 container.snapToPage(pageIndex + 1); 344 child = newParent.getChildAt(0); 345 if (child != null) child.requestFocus(); 346 } 347 } 348 } 349 } 350 wasHandled = true; 351 break; 352 case KeyEvent.KEYCODE_DPAD_UP: 353 if (handleKeyEvent) { 354 // Select the closest icon in the previous row, otherwise select the tab bar 355 if (y > 0) { 356 int newiconIndex = ((y - 1) * countX) + x; 357 itemContainer.getChildAt(newiconIndex).requestFocus(); 358 } else { 359 tabs.requestFocus(); 360 } 361 } 362 wasHandled = true; 363 break; 364 case KeyEvent.KEYCODE_DPAD_DOWN: 365 if (handleKeyEvent) { 366 // Select the closest icon in the previous row, otherwise do nothing 367 if (y < (countY - 1)) { 368 int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x); 369 itemContainer.getChildAt(newiconIndex).requestFocus(); 370 } 371 } 372 wasHandled = true; 373 break; 374 case KeyEvent.KEYCODE_ENTER: 375 case KeyEvent.KEYCODE_DPAD_CENTER: 376 if (handleKeyEvent) { 377 // Simulate a click on the icon 378 View.OnClickListener clickListener = (View.OnClickListener) container; 379 clickListener.onClick(v); 380 } 381 wasHandled = true; 382 break; 383 case KeyEvent.KEYCODE_PAGE_UP: 384 if (handleKeyEvent) { 385 // Select the first icon on the previous page, or the first icon on this page 386 // if there is no previous page 387 if (pageIndex > 0) { 388 newParent = getAppsCustomizePage(container, pageIndex - 1); 389 if (newParent != null) { 390 container.snapToPage(pageIndex - 1); 391 child = newParent.getChildAt(0); 392 if (child != null) child.requestFocus(); 393 } 394 } else { 395 itemContainer.getChildAt(0).requestFocus(); 396 } 397 } 398 wasHandled = true; 399 break; 400 case KeyEvent.KEYCODE_PAGE_DOWN: 401 if (handleKeyEvent) { 402 // Select the first icon on the next page, or the last icon on this page 403 // if there is no next page 404 if (pageIndex < (pageCount - 1)) { 405 newParent = getAppsCustomizePage(container, pageIndex + 1); 406 if (newParent != null) { 407 container.snapToPage(pageIndex + 1); 408 child = newParent.getChildAt(0); 409 if (child != null) child.requestFocus(); 410 } 411 } else { 412 itemContainer.getChildAt(itemCount - 1).requestFocus(); 413 } 414 } 415 wasHandled = true; 416 break; 417 case KeyEvent.KEYCODE_MOVE_HOME: 418 if (handleKeyEvent) { 419 // Select the first icon on this page 420 itemContainer.getChildAt(0).requestFocus(); 421 } 422 wasHandled = true; 423 break; 424 case KeyEvent.KEYCODE_MOVE_END: 425 if (handleKeyEvent) { 426 // Select the last icon on this page 427 itemContainer.getChildAt(itemCount - 1).requestFocus(); 428 } 429 wasHandled = true; 430 break; 431 default: break; 432 } 433 return wasHandled; 434 } 435 436 /** 437 * Handles key events in the tab widget. 438 */ 439 static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) { 440 if (!LauncherApplication.isScreenLarge()) return false; 441 442 final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent(); 443 final TabHost tabHost = findTabHostParent(parent); 444 final ViewGroup contents = tabHost.getTabContentView(); 445 final int tabCount = parent.getTabCount(); 446 final int tabIndex = parent.getChildTabIndex(v); 447 448 final int action = e.getAction(); 449 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 450 boolean wasHandled = false; 451 switch (keyCode) { 452 case KeyEvent.KEYCODE_DPAD_LEFT: 453 if (handleKeyEvent) { 454 // Select the previous tab 455 if (tabIndex > 0) { 456 parent.getChildTabViewAt(tabIndex - 1).requestFocus(); 457 } 458 } 459 wasHandled = true; 460 break; 461 case KeyEvent.KEYCODE_DPAD_RIGHT: 462 if (handleKeyEvent) { 463 // Select the next tab, or if the last tab has a focus right id, select that 464 if (tabIndex < (tabCount - 1)) { 465 parent.getChildTabViewAt(tabIndex + 1).requestFocus(); 466 } else { 467 if (v.getNextFocusRightId() != View.NO_ID) { 468 tabHost.findViewById(v.getNextFocusRightId()).requestFocus(); 469 } 470 } 471 } 472 wasHandled = true; 473 break; 474 case KeyEvent.KEYCODE_DPAD_UP: 475 // Do nothing 476 wasHandled = true; 477 break; 478 case KeyEvent.KEYCODE_DPAD_DOWN: 479 if (handleKeyEvent) { 480 // Select the content view 481 contents.requestFocus(); 482 } 483 wasHandled = true; 484 break; 485 default: break; 486 } 487 return wasHandled; 488 } 489 490 /** 491 * Handles key events in the workspace hotseat (bottom of the screen). 492 */ 493 static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) { 494 final ViewGroup parent = (ViewGroup) v.getParent(); 495 final ViewGroup launcher = (ViewGroup) parent.getParent(); 496 final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace); 497 final int buttonIndex = parent.indexOfChild(v); 498 final int buttonCount = parent.getChildCount(); 499 final int pageIndex = workspace.getCurrentPage(); 500 501 // NOTE: currently we don't special case for the phone UI in different 502 // orientations, even though the hotseat is on the side in landscape mode. This 503 // is to ensure that accessibility consistency is maintained across rotations. 504 505 final int action = e.getAction(); 506 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 507 boolean wasHandled = false; 508 switch (keyCode) { 509 case KeyEvent.KEYCODE_DPAD_LEFT: 510 if (handleKeyEvent) { 511 // Select the previous button, otherwise snap to the previous page 512 if (buttonIndex > 0) { 513 parent.getChildAt(buttonIndex - 1).requestFocus(); 514 } else { 515 workspace.snapToPage(pageIndex - 1); 516 } 517 } 518 wasHandled = true; 519 break; 520 case KeyEvent.KEYCODE_DPAD_RIGHT: 521 if (handleKeyEvent) { 522 // Select the next button, otherwise snap to the next page 523 if (buttonIndex < (buttonCount - 1)) { 524 parent.getChildAt(buttonIndex + 1).requestFocus(); 525 } else { 526 workspace.snapToPage(pageIndex + 1); 527 } 528 } 529 wasHandled = true; 530 break; 531 case KeyEvent.KEYCODE_DPAD_UP: 532 if (handleKeyEvent) { 533 // Select the first bubble text view in the current page of the workspace 534 final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex); 535 final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets(); 536 final View newIcon = getIconInDirection(layout, children, -1, 1); 537 if (newIcon != null) { 538 newIcon.requestFocus(); 539 } else { 540 workspace.requestFocus(); 541 } 542 } 543 wasHandled = true; 544 break; 545 case KeyEvent.KEYCODE_DPAD_DOWN: 546 // Do nothing 547 wasHandled = true; 548 break; 549 default: break; 550 } 551 return wasHandled; 552 } 553 554 /** 555 * Private helper method to get the CellLayoutChildren given a CellLayout index. 556 */ 557 private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex( 558 ViewGroup container, int i) { 559 ViewGroup parent = (ViewGroup) container.getChildAt(i); 560 return (ShortcutAndWidgetContainer) parent.getChildAt(0); 561 } 562 563 /** 564 * Private helper method to sort all the CellLayout children in order of their (x,y) spatially 565 * from top left to bottom right. 566 */ 567 private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout, 568 ViewGroup parent) { 569 // First we order each the CellLayout children by their x,y coordinates 570 final int cellCountX = layout.getCountX(); 571 final int count = parent.getChildCount(); 572 ArrayList<View> views = new ArrayList<View>(); 573 for (int j = 0; j < count; ++j) { 574 views.add(parent.getChildAt(j)); 575 } 576 Collections.sort(views, new Comparator<View>() { 577 @Override 578 public int compare(View lhs, View rhs) { 579 CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams(); 580 CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams(); 581 int lvIndex = (llp.cellY * cellCountX) + llp.cellX; 582 int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX; 583 return lvIndex - rvIndex; 584 } 585 }); 586 return views; 587 } 588 /** 589 * Private helper method to find the index of the next BubbleTextView or FolderIcon in the 590 * direction delta. 591 * 592 * @param delta either -1 or 1 depending on the direction we want to search 593 */ 594 private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) { 595 // Then we find the next BubbleTextView offset by delta from i 596 final int count = views.size(); 597 int newI = i + delta; 598 while (0 <= newI && newI < count) { 599 View newV = views.get(newI); 600 if (newV instanceof BubbleTextView || newV instanceof FolderIcon) { 601 return newV; 602 } 603 newI += delta; 604 } 605 return null; 606 } 607 private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i, 608 int delta) { 609 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 610 return findIndexOfIcon(views, i, delta); 611 } 612 private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v, 613 int delta) { 614 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 615 return findIndexOfIcon(views, views.indexOf(v), delta); 616 } 617 /** 618 * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction 619 * delta on the next line. 620 * 621 * @param delta either -1 or 1 depending on the line and direction we want to search 622 */ 623 private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v, 624 int lineDelta) { 625 final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent); 626 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 627 final int cellCountY = layout.getCountY(); 628 final int row = lp.cellY; 629 final int newRow = row + lineDelta; 630 if (0 <= newRow && newRow < cellCountY) { 631 float closestDistance = Float.MAX_VALUE; 632 int closestIndex = -1; 633 int index = views.indexOf(v); 634 int endIndex = (lineDelta < 0) ? -1 : views.size(); 635 while (index != endIndex) { 636 View newV = views.get(index); 637 CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams(); 638 boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row); 639 if (satisfiesRow && 640 (newV instanceof BubbleTextView || newV instanceof FolderIcon)) { 641 float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) + 642 Math.pow(tmpLp.cellY - lp.cellY, 2)); 643 if (tmpDistance < closestDistance) { 644 closestIndex = index; 645 closestDistance = tmpDistance; 646 } 647 } 648 if (index <= endIndex) { 649 ++index; 650 } else { 651 --index; 652 } 653 } 654 if (closestIndex > -1) { 655 return views.get(closestIndex); 656 } 657 } 658 return null; 659 } 660 661 /** 662 * Handles key events in a Workspace containing. 663 */ 664 static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) { 665 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); 666 final CellLayout layout = (CellLayout) parent.getParent(); 667 final Workspace workspace = (Workspace) layout.getParent(); 668 final ViewGroup launcher = (ViewGroup) workspace.getParent(); 669 final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar); 670 final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat); 671 int pageIndex = workspace.indexOfChild(layout); 672 int pageCount = workspace.getChildCount(); 673 674 final int action = e.getAction(); 675 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 676 boolean wasHandled = false; 677 switch (keyCode) { 678 case KeyEvent.KEYCODE_DPAD_LEFT: 679 if (handleKeyEvent) { 680 // Select the previous icon or the last icon on the previous page if possible 681 View newIcon = getIconInDirection(layout, parent, v, -1); 682 if (newIcon != null) { 683 newIcon.requestFocus(); 684 } else { 685 if (pageIndex > 0) { 686 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 687 newIcon = getIconInDirection(layout, parent, 688 parent.getChildCount(), -1); 689 if (newIcon != null) { 690 newIcon.requestFocus(); 691 } else { 692 // Snap to the previous page 693 workspace.snapToPage(pageIndex - 1); 694 } 695 } 696 } 697 } 698 wasHandled = true; 699 break; 700 case KeyEvent.KEYCODE_DPAD_RIGHT: 701 if (handleKeyEvent) { 702 // Select the next icon or the first icon on the next page if possible 703 View newIcon = getIconInDirection(layout, parent, v, 1); 704 if (newIcon != null) { 705 newIcon.requestFocus(); 706 } else { 707 if (pageIndex < (pageCount - 1)) { 708 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); 709 newIcon = getIconInDirection(layout, parent, -1, 1); 710 if (newIcon != null) { 711 newIcon.requestFocus(); 712 } else { 713 // Snap to the next page 714 workspace.snapToPage(pageIndex + 1); 715 } 716 } 717 } 718 } 719 wasHandled = true; 720 break; 721 case KeyEvent.KEYCODE_DPAD_UP: 722 if (handleKeyEvent) { 723 // Select the closest icon in the previous line, otherwise select the tab bar 724 View newIcon = getClosestIconOnLine(layout, parent, v, -1); 725 if (newIcon != null) { 726 newIcon.requestFocus(); 727 wasHandled = true; 728 } else { 729 tabs.requestFocus(); 730 } 731 } 732 break; 733 case KeyEvent.KEYCODE_DPAD_DOWN: 734 if (handleKeyEvent) { 735 // Select the closest icon in the next line, otherwise select the button bar 736 View newIcon = getClosestIconOnLine(layout, parent, v, 1); 737 if (newIcon != null) { 738 newIcon.requestFocus(); 739 wasHandled = true; 740 } else if (hotseat != null) { 741 hotseat.requestFocus(); 742 } 743 } 744 break; 745 case KeyEvent.KEYCODE_PAGE_UP: 746 if (handleKeyEvent) { 747 // Select the first icon on the previous page or the first icon on this page 748 // if there is no previous page 749 if (pageIndex > 0) { 750 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); 751 View newIcon = getIconInDirection(layout, parent, -1, 1); 752 if (newIcon != null) { 753 newIcon.requestFocus(); 754 } else { 755 // Snap to the previous page 756 workspace.snapToPage(pageIndex - 1); 757 } 758 } else { 759 View newIcon = getIconInDirection(layout, parent, -1, 1); 760 if (newIcon != null) { 761 newIcon.requestFocus(); 762 } 763 } 764 } 765 wasHandled = true; 766 break; 767 case KeyEvent.KEYCODE_PAGE_DOWN: 768 if (handleKeyEvent) { 769 // Select the first icon on the next page or the last icon on this page 770 // if there is no previous page 771 if (pageIndex < (pageCount - 1)) { 772 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); 773 View newIcon = getIconInDirection(layout, parent, -1, 1); 774 if (newIcon != null) { 775 newIcon.requestFocus(); 776 } else { 777 // Snap to the next page 778 workspace.snapToPage(pageIndex + 1); 779 } 780 } else { 781 View newIcon = getIconInDirection(layout, parent, 782 parent.getChildCount(), -1); 783 if (newIcon != null) { 784 newIcon.requestFocus(); 785 } 786 } 787 } 788 wasHandled = true; 789 break; 790 case KeyEvent.KEYCODE_MOVE_HOME: 791 if (handleKeyEvent) { 792 // Select the first icon on this page 793 View newIcon = getIconInDirection(layout, parent, -1, 1); 794 if (newIcon != null) { 795 newIcon.requestFocus(); 796 } 797 } 798 wasHandled = true; 799 break; 800 case KeyEvent.KEYCODE_MOVE_END: 801 if (handleKeyEvent) { 802 // Select the last icon on this page 803 View newIcon = getIconInDirection(layout, parent, 804 parent.getChildCount(), -1); 805 if (newIcon != null) { 806 newIcon.requestFocus(); 807 } 808 } 809 wasHandled = true; 810 break; 811 default: break; 812 } 813 return wasHandled; 814 } 815 816 /** 817 * Handles key events for items in a Folder. 818 */ 819 static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) { 820 ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); 821 final CellLayout layout = (CellLayout) parent.getParent(); 822 final Folder folder = (Folder) layout.getParent(); 823 View title = folder.mFolderName; 824 825 final int action = e.getAction(); 826 final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); 827 boolean wasHandled = false; 828 switch (keyCode) { 829 case KeyEvent.KEYCODE_DPAD_LEFT: 830 if (handleKeyEvent) { 831 // Select the previous icon 832 View newIcon = getIconInDirection(layout, parent, v, -1); 833 if (newIcon != null) { 834 newIcon.requestFocus(); 835 } 836 } 837 wasHandled = true; 838 break; 839 case KeyEvent.KEYCODE_DPAD_RIGHT: 840 if (handleKeyEvent) { 841 // Select the next icon 842 View newIcon = getIconInDirection(layout, parent, v, 1); 843 if (newIcon != null) { 844 newIcon.requestFocus(); 845 } else { 846 title.requestFocus(); 847 } 848 } 849 wasHandled = true; 850 break; 851 case KeyEvent.KEYCODE_DPAD_UP: 852 if (handleKeyEvent) { 853 // Select the closest icon in the previous line 854 View newIcon = getClosestIconOnLine(layout, parent, v, -1); 855 if (newIcon != null) { 856 newIcon.requestFocus(); 857 } 858 } 859 wasHandled = true; 860 break; 861 case KeyEvent.KEYCODE_DPAD_DOWN: 862 if (handleKeyEvent) { 863 // Select the closest icon in the next line 864 View newIcon = getClosestIconOnLine(layout, parent, v, 1); 865 if (newIcon != null) { 866 newIcon.requestFocus(); 867 } else { 868 title.requestFocus(); 869 } 870 } 871 wasHandled = true; 872 break; 873 case KeyEvent.KEYCODE_MOVE_HOME: 874 if (handleKeyEvent) { 875 // Select the first icon on this page 876 View newIcon = getIconInDirection(layout, parent, -1, 1); 877 if (newIcon != null) { 878 newIcon.requestFocus(); 879 } 880 } 881 wasHandled = true; 882 break; 883 case KeyEvent.KEYCODE_MOVE_END: 884 if (handleKeyEvent) { 885 // Select the last icon on this page 886 View newIcon = getIconInDirection(layout, parent, 887 parent.getChildCount(), -1); 888 if (newIcon != null) { 889 newIcon.requestFocus(); 890 } 891 } 892 wasHandled = true; 893 break; 894 default: break; 895 } 896 return wasHandled; 897 } 898 } 899