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