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.FeatureFlags;
26 import com.android.launcher3.folder.Folder;
27 import com.android.launcher3.folder.FolderPagedView;
28 import com.android.launcher3.model.data.ItemInfo;
29 import com.android.launcher3.util.FocusLogic;
30 import com.android.launcher3.util.Thunk;
31 
32 /**
33  * A keyboard listener we set on all the workspace icons.
34  */
35 class IconKeyEventListener implements View.OnKeyListener {
36     @Override
onKey(View v, int keyCode, KeyEvent event)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 hotseat buttons.
44  */
45 class HotseatIconKeyEventListener implements View.OnKeyListener {
46     @Override
onKey(View v, int keyCode, KeyEvent event)47     public boolean onKey(View v, int keyCode, KeyEvent event) {
48         return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
49     }
50 }
51 
52 /**
53  * A keyboard listener we set on full screen pages (e.g. custom content).
54  */
55 class FullscreenKeyEventListener implements View.OnKeyListener {
56     @Override
onKey(View v, int keyCode, KeyEvent event)57     public boolean onKey(View v, int keyCode, KeyEvent event) {
58         if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
59                 || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
60             // Handle the key event just like a workspace icon would in these cases. In this case,
61             // it will basically act as if there is a single icon in the top left (so you could
62             // think of the fullscreen page as a focusable fullscreen widget).
63             return FocusHelper.handleIconKeyEvent(v, keyCode, event);
64         }
65         return false;
66     }
67 }
68 
69 /**
70  * TODO: Reevaluate if this is still required
71  */
72 public class FocusHelper {
73 
74     private static final String TAG = "FocusHelper";
75     private static final boolean DEBUG = false;
76 
77     /**
78      * Handles key events in paged folder.
79      */
80     public static class PagedFolderKeyEventListener implements View.OnKeyListener {
81 
82         private final Folder mFolder;
83 
PagedFolderKeyEventListener(Folder folder)84         public PagedFolderKeyEventListener(Folder folder) {
85             mFolder = folder;
86         }
87 
88         @Override
onKey(View v, int keyCode, KeyEvent e)89         public boolean onKey(View v, int keyCode, KeyEvent e) {
90             boolean consume = FocusLogic.shouldConsume(keyCode);
91             if (e.getAction() == KeyEvent.ACTION_UP) {
92                 return consume;
93             }
94             if (DEBUG) {
95                 Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
96                         KeyEvent.keyCodeToString(keyCode)));
97             }
98 
99             if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
100                 if (FeatureFlags.IS_STUDIO_BUILD) {
101                     throw new IllegalStateException("Parent of the focused item is not supported.");
102                 } else {
103                     return false;
104                 }
105             }
106 
107             // Initialize variables.
108             final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
109             final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
110 
111             final int iconIndex = itemContainer.indexOfChild(v);
112             final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
113 
114             final int pageIndex = pagedView.indexOfChild(cellLayout);
115             final int pageCount = pagedView.getPageCount();
116             final boolean isLayoutRtl = Utilities.isRtl(v.getResources());
117 
118             int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
119             // Process focus.
120             int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
121                     pageCount, isLayoutRtl);
122             if (newIconIndex == FocusLogic.NOOP) {
123                 handleNoopKey(keyCode, v);
124                 return consume;
125             }
126             ShortcutAndWidgetContainer newParent = null;
127             View child = null;
128 
129             switch (newIconIndex) {
130                 case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
131                 case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
132                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
133                     if (newParent != null) {
134                         int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
135                         pagedView.snapToPage(pageIndex - 1);
136                         child = newParent.getChildAt(
137                                 ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
138                                     ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
139                                 row);
140                     }
141                     break;
142                 case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
143                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
144                     if (newParent != null) {
145                         pagedView.snapToPage(pageIndex - 1);
146                         child = newParent.getChildAt(0, 0);
147                     }
148                     break;
149                 case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
150                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
151                     if (newParent != null) {
152                         pagedView.snapToPage(pageIndex - 1);
153                         child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1);
154                     }
155                     break;
156                 case FocusLogic.NEXT_PAGE_FIRST_ITEM:
157                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
158                     if (newParent != null) {
159                         pagedView.snapToPage(pageIndex + 1);
160                         child = newParent.getChildAt(0, 0);
161                     }
162                     break;
163                 case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
164                 case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
165                     newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
166                     if (newParent != null) {
167                         pagedView.snapToPage(pageIndex + 1);
168                         child = FocusLogic.getAdjacentChildInNextFolderPage(
169                                 newParent, v, newIconIndex);
170                     }
171                     break;
172                 case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
173                     child = cellLayout.getChildAt(0, 0);
174                     break;
175                 case FocusLogic.CURRENT_PAGE_LAST_ITEM:
176                     child = pagedView.getLastItem();
177                     break;
178                 default: // Go to some item on the current page.
179                     child = itemContainer.getChildAt(newIconIndex);
180                     break;
181             }
182             if (child != null) {
183                 child.requestFocus();
184                 playSoundEffect(keyCode, v);
185             } else {
186                 handleNoopKey(keyCode, v);
187             }
188             return consume;
189         }
190 
handleNoopKey(int keyCode, View v)191         public void handleNoopKey(int keyCode, View v) {
192             if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
193                 mFolder.mFolderName.requestFocus();
194                 playSoundEffect(keyCode, v);
195             }
196         }
197     }
198 
199     /**
200      * Handles key events in the workspace hotseat (bottom of the screen).
201      * <p>Currently we don't special case for the phone UI in different orientations, even though
202      * the hotseat is on the side in landscape mode. This is to ensure that accessibility
203      * consistency is maintained across rotations.
204      */
handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e)205     static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
206         boolean consume = FocusLogic.shouldConsume(keyCode);
207         if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
208             return consume;
209         }
210 
211         final Launcher launcher = Launcher.getLauncher(v.getContext());
212         final DeviceProfile profile = launcher.getDeviceProfile();
213 
214         if (DEBUG) {
215             Log.v(TAG, String.format(
216                     "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
217                     KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
218         }
219 
220         // Initialize the variables.
221         final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
222         final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
223         final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
224 
225         final ItemInfo itemInfo = (ItemInfo) v.getTag();
226         int pageIndex = workspace.getNextPage();
227         int pageCount = workspace.getChildCount();
228         int iconIndex = hotseatParent.indexOfChild(v);
229         int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
230                 .getChildAt(iconIndex).getLayoutParams()).cellX;
231 
232         final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
233         if (iconLayout == null) {
234             // This check is to guard against cases where key strokes rushes in when workspace
235             // child creation/deletion is still in flux. (e.g., during drop or fling
236             // animation.)
237             return consume;
238         }
239         final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
240 
241         ViewGroup parent = null;
242         int[][] matrix = null;
243 
244         if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
245                 !profile.isVerticalBarLayout()) {
246             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
247             iconIndex += iconParent.getChildCount();
248             parent = iconParent;
249         } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
250                 profile.isVerticalBarLayout()) {
251             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
252             iconIndex += iconParent.getChildCount();
253             parent = iconParent;
254         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
255                 profile.isVerticalBarLayout()) {
256             keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
257         } else {
258             // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
259             // matrix extended with hotseat.
260             matrix = FocusLogic.createSparseMatrix(hotseatLayout);
261             parent = hotseatParent;
262         }
263 
264         // Process the focus.
265         int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
266                 pageCount, Utilities.isRtl(v.getResources()));
267 
268         View newIcon = null;
269         switch (newIconIndex) {
270             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
271                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
272                 newIcon = parent.getChildAt(0);
273                 // TODO(hyunyoungs): handle cases where the child is not an icon but
274                 // a folder or a widget.
275                 workspace.snapToPage(pageIndex + 1);
276                 break;
277             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
278                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
279                 newIcon = parent.getChildAt(0);
280                 // TODO(hyunyoungs): handle cases where the child is not an icon but
281                 // a folder or a widget.
282                 workspace.snapToPage(pageIndex - 1);
283                 break;
284             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
285                 parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
286                 newIcon = parent.getChildAt(parent.getChildCount() - 1);
287                 // TODO(hyunyoungs): handle cases where the child is not an icon but
288                 // a folder or a widget.
289                 workspace.snapToPage(pageIndex - 1);
290                 break;
291             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
292             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
293                 // Go to the previous page but keep the focus on the same hotseat icon.
294                 workspace.snapToPage(pageIndex - 1);
295                 break;
296             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
297             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
298                 // Go to the next page but keep the focus on the same hotseat icon.
299                 workspace.snapToPage(pageIndex + 1);
300                 break;
301         }
302         if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
303             newIconIndex -= iconParent.getChildCount();
304         }
305         if (parent != null) {
306             if (newIcon == null && newIconIndex >= 0) {
307                 newIcon = parent.getChildAt(newIconIndex);
308             }
309             if (newIcon != null) {
310                 newIcon.requestFocus();
311                 playSoundEffect(keyCode, v);
312             }
313         }
314         return consume;
315     }
316 
317     /**
318      * Handles key events in a workspace containing icons.
319      */
handleIconKeyEvent(View v, int keyCode, KeyEvent e)320     static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
321         boolean consume = FocusLogic.shouldConsume(keyCode);
322         if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
323             return consume;
324         }
325 
326         Launcher launcher = Launcher.getLauncher(v.getContext());
327         DeviceProfile profile = launcher.getDeviceProfile();
328 
329         if (DEBUG) {
330             Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
331                     KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
332         }
333 
334         // Initialize the variables.
335         ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
336         CellLayout iconLayout = (CellLayout) parent.getParent();
337         final Workspace workspace = (Workspace) iconLayout.getParent();
338         final ViewGroup dragLayer = (ViewGroup) workspace.getParent();
339         final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.drop_target_bar);
340         final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
341 
342         final ItemInfo itemInfo = (ItemInfo) v.getTag();
343         final int iconIndex = parent.indexOfChild(v);
344         final int pageIndex = workspace.indexOfChild(iconLayout);
345         final int pageCount = workspace.getChildCount();
346 
347         CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
348         ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
349         int[][] matrix;
350 
351         // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
352         // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
353         // with the hotseat.
354         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
355             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
356         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
357                 profile.isVerticalBarLayout()) {
358             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
359         } else {
360             matrix = FocusLogic.createSparseMatrix(iconLayout);
361         }
362 
363         // Process the focus.
364         int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
365                 pageCount, Utilities.isRtl(v.getResources()));
366         boolean isRtl = Utilities.isRtl(v.getResources());
367         View newIcon = null;
368         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
369         switch (newIconIndex) {
370             case FocusLogic.NOOP:
371                 if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
372                     newIcon = tabs;
373                 }
374                 break;
375             case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
376             case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
377                 int newPageIndex = pageIndex - 1;
378                 if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
379                     newPageIndex = pageIndex + 1;
380                 }
381                 int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
382                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
383                 if (parent != null) {
384                     iconLayout = (CellLayout) parent.getParent();
385                     matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
386                             iconLayout.getCountX(), row);
387                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
388                             newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
389                     if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
390                         newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
391                                 isRtl);
392                     } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
393                         newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
394                                 isRtl);
395                     } else {
396                         newIcon = parent.getChildAt(newIconIndex);
397                     }
398                 }
399                 break;
400             case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
401                 workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
402                 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
403                 if (newIcon == null) {
404                     // Check the hotseat if no focusable item was found on the workspace.
405                     newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
406                     workspace.snapToPage(pageIndex - 1);
407                 }
408                 break;
409             case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
410                 newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
411                 break;
412             case FocusLogic.NEXT_PAGE_FIRST_ITEM:
413                 newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
414                 break;
415             case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
416             case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
417                 newPageIndex = pageIndex + 1;
418                 if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
419                     newPageIndex = pageIndex - 1;
420                 }
421                 row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
422                 parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
423                 if (parent != null) {
424                     iconLayout = (CellLayout) parent.getParent();
425                     matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
426                     newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
427                             newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
428                     if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
429                         newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
430                                 isRtl);
431                     } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
432                         newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
433                                 isRtl);
434                     } else {
435                         newIcon = parent.getChildAt(newIconIndex);
436                     }
437                 }
438                 break;
439             case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
440                 newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
441                 if (newIcon == null) {
442                     // Check the hotseat if no focusable item was found on the workspace.
443                     newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
444                 }
445                 break;
446             case FocusLogic.CURRENT_PAGE_LAST_ITEM:
447                 newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
448                 if (newIcon == null) {
449                     // Check the hotseat if no focusable item was found on the workspace.
450                     newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
451                 }
452                 break;
453             default:
454                 // current page, some item.
455                 if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
456                     newIcon = parent.getChildAt(newIconIndex);
457                 } else if (parent.getChildCount() <= newIconIndex &&
458                         newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
459                     newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
460                 }
461                 break;
462         }
463         if (newIcon != null) {
464             newIcon.requestFocus();
465             playSoundEffect(keyCode, v);
466         }
467         return consume;
468     }
469 
470     //
471     // Helper methods.
472     //
473 
474     /**
475      * Private helper method to get the CellLayoutChildren given a CellLayout index.
476      */
getCellLayoutChildrenForIndex( ViewGroup container, int i)477     @Thunk static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
478             ViewGroup container, int i) {
479         CellLayout parent = (CellLayout) container.getChildAt(i);
480         return parent.getShortcutsAndWidgets();
481     }
482 
483     /**
484      * Helper method to be used for playing sound effects.
485      */
playSoundEffect(int keyCode, View v)486     @Thunk static void playSoundEffect(int keyCode, View v) {
487         switch (keyCode) {
488             case KeyEvent.KEYCODE_DPAD_LEFT:
489                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
490                 break;
491             case KeyEvent.KEYCODE_DPAD_RIGHT:
492                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
493                 break;
494             case KeyEvent.KEYCODE_DPAD_DOWN:
495             case KeyEvent.KEYCODE_PAGE_DOWN:
496             case KeyEvent.KEYCODE_MOVE_END:
497                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
498                 break;
499             case KeyEvent.KEYCODE_DPAD_UP:
500             case KeyEvent.KEYCODE_PAGE_UP:
501             case KeyEvent.KEYCODE_MOVE_HOME:
502                 v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
503                 break;
504             default:
505                 break;
506         }
507     }
508 
handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout, int pageIndex, boolean isRtl)509     private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
510             int pageIndex, boolean isRtl) {
511         if (pageIndex - 1 < 0) {
512             return null;
513         }
514         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
515         View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
516         if (newIcon == null) {
517             // Check the hotseat if no focusable item was found on the workspace.
518             newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
519             workspace.snapToPage(pageIndex - 1);
520         }
521         return newIcon;
522     }
523 
handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout, int pageIndex, boolean isRtl)524     private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
525             int pageIndex, boolean isRtl) {
526         if (pageIndex + 1 >= workspace.getPageCount()) {
527             return null;
528         }
529         CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
530         View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
531         if (newIcon == null) {
532             // Check the hotseat if no focusable item was found on the workspace.
533             newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
534             workspace.snapToPage(pageIndex + 1);
535         }
536         return newIcon;
537     }
538 
getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl)539     private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
540         View icon;
541         int countX = cellLayout.getCountX();
542         for (int y = 0; y < cellLayout.getCountY(); y++) {
543             int increment = isRtl ? -1 : 1;
544             for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
545                 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
546                     return icon;
547                 }
548             }
549         }
550         return null;
551     }
552 
getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout, boolean isRtl)553     private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
554             boolean isRtl) {
555         View icon;
556         int countX = cellLayout.getCountX();
557         for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
558             int increment = isRtl ? 1 : -1;
559             for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
560                 if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
561                     return icon;
562                 }
563             }
564         }
565         return null;
566     }
567 }
568