• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.folder;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.graphics.Canvas;
22 import android.graphics.drawable.Drawable;
23 import android.util.ArrayMap;
24 import android.util.AttributeSet;
25 import android.util.Log;
26 import android.view.Gravity;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewDebug;
30 
31 import com.android.launcher3.BubbleTextView;
32 import com.android.launcher3.CellLayout;
33 import com.android.launcher3.DeviceProfile;
34 import com.android.launcher3.InvariantDeviceProfile;
35 import com.android.launcher3.ItemInfo;
36 import com.android.launcher3.Launcher;
37 import com.android.launcher3.LauncherAppState;
38 import com.android.launcher3.PagedView;
39 import com.android.launcher3.R;
40 import com.android.launcher3.ShortcutAndWidgetContainer;
41 import com.android.launcher3.ShortcutInfo;
42 import com.android.launcher3.Utilities;
43 import com.android.launcher3.Workspace.ItemOperator;
44 import com.android.launcher3.anim.Interpolators;
45 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
46 import com.android.launcher3.pageindicators.PageIndicatorDots;
47 import com.android.launcher3.touch.ItemClickHandler;
48 import com.android.launcher3.util.Thunk;
49 
50 import java.util.ArrayList;
51 import java.util.Iterator;
52 import java.util.Map;
53 
54 public class FolderPagedView extends PagedView<PageIndicatorDots> {
55 
56     private static final String TAG = "FolderPagedView";
57 
58     private static final int REORDER_ANIMATION_DURATION = 230;
59     private static final int START_VIEW_REORDER_DELAY = 30;
60     private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
61 
62     /**
63      * Fraction of the width to scroll when showing the next page hint.
64      */
65     private static final float SCROLL_HINT_FRACTION = 0.07f;
66 
67     private static final int[] sTmpArray = new int[2];
68 
69     public final boolean mIsRtl;
70 
71     private final LayoutInflater mInflater;
72     private final ViewGroupFocusHelper mFocusIndicatorHelper;
73 
74     @Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>();
75 
76     @ViewDebug.ExportedProperty(category = "launcher")
77     private final int mMaxCountX;
78     @ViewDebug.ExportedProperty(category = "launcher")
79     private final int mMaxCountY;
80     @ViewDebug.ExportedProperty(category = "launcher")
81     private final int mMaxItemsPerPage;
82 
83     private int mAllocatedContentSize;
84     @ViewDebug.ExportedProperty(category = "launcher")
85     private int mGridCountX;
86     @ViewDebug.ExportedProperty(category = "launcher")
87     private int mGridCountY;
88 
89     private Folder mFolder;
90 
FolderPagedView(Context context, AttributeSet attrs)91     public FolderPagedView(Context context, AttributeSet attrs) {
92         super(context, attrs);
93         InvariantDeviceProfile profile = LauncherAppState.getIDP(context);
94         mMaxCountX = profile.numFolderColumns;
95         mMaxCountY = profile.numFolderRows;
96 
97         mMaxItemsPerPage = mMaxCountX * mMaxCountY;
98 
99         mInflater = LayoutInflater.from(context);
100 
101         mIsRtl = Utilities.isRtl(getResources());
102         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
103 
104         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
105     }
106 
setFolder(Folder folder)107     public void setFolder(Folder folder) {
108         mFolder = folder;
109         mPageIndicator = folder.findViewById(R.id.folder_page_indicator);
110         initParentViews(folder);
111     }
112 
113     /**
114      * Calculates the grid size such that {@param count} items can fit in the grid.
115      * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
116      * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
117      */
calculateGridSize(int count, int countX, int countY, int maxCountX, int maxCountY, int maxItemsPerPage, int[] out)118     public static void calculateGridSize(int count, int countX, int countY, int maxCountX,
119             int maxCountY, int maxItemsPerPage, int[] out) {
120         boolean done;
121         int gridCountX = countX;
122         int gridCountY = countY;
123 
124         if (count >= maxItemsPerPage) {
125             gridCountX = maxCountX;
126             gridCountY = maxCountY;
127             done = true;
128         } else {
129             done = false;
130         }
131 
132         while (!done) {
133             int oldCountX = gridCountX;
134             int oldCountY = gridCountY;
135             if (gridCountX * gridCountY < count) {
136                 // Current grid is too small, expand it
137                 if ((gridCountX <= gridCountY || gridCountY == maxCountY)
138                         && gridCountX < maxCountX) {
139                     gridCountX++;
140                 } else if (gridCountY < maxCountY) {
141                     gridCountY++;
142                 }
143                 if (gridCountY == 0) gridCountY++;
144             } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
145                 gridCountY = Math.max(0, gridCountY - 1);
146             } else if ((gridCountX - 1) * gridCountY >= count) {
147                 gridCountX = Math.max(0, gridCountX - 1);
148             }
149             done = gridCountX == oldCountX && gridCountY == oldCountY;
150         }
151 
152         out[0] = gridCountX;
153         out[1] = gridCountY;
154     }
155 
156     /**
157      * Sets up the grid size such that {@param count} items can fit in the grid.
158      */
setupContentDimensions(int count)159     public void setupContentDimensions(int count) {
160         mAllocatedContentSize = count;
161         calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage,
162                 sTmpArray);
163         mGridCountX = sTmpArray[0];
164         mGridCountY = sTmpArray[1];
165 
166         // Update grid size
167         for (int i = getPageCount() - 1; i >= 0; i--) {
168             getPageAt(i).setGridSize(mGridCountX, mGridCountY);
169         }
170     }
171 
172     @Override
dispatchDraw(Canvas canvas)173     protected void dispatchDraw(Canvas canvas) {
174         mFocusIndicatorHelper.draw(canvas);
175         super.dispatchDraw(canvas);
176     }
177 
178     /**
179      * Binds items to the layout.
180      */
bindItems(ArrayList<ShortcutInfo> items)181     public void bindItems(ArrayList<ShortcutInfo> items) {
182         ArrayList<View> icons = new ArrayList<>();
183         for (ShortcutInfo item : items) {
184             icons.add(createNewView(item));
185         }
186         arrangeChildren(icons, icons.size(), false);
187     }
188 
allocateSpaceForRank(int rank)189     public void allocateSpaceForRank(int rank) {
190         ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder());
191         views.add(rank, null);
192         arrangeChildren(views, views.size(), false);
193     }
194 
195     /**
196      * Create space for a new item at the end, and returns the rank for that item.
197      * Also sets the current page to the last page.
198      */
allocateRankForNewItem()199     public int allocateRankForNewItem() {
200         int rank = getItemCount();
201         allocateSpaceForRank(rank);
202         setCurrentPage(rank / mMaxItemsPerPage);
203         return rank;
204     }
205 
createAndAddViewForRank(ShortcutInfo item, int rank)206     public View createAndAddViewForRank(ShortcutInfo item, int rank) {
207         View icon = createNewView(item);
208         allocateSpaceForRank(rank);
209         addViewForRank(icon, item, rank);
210         return icon;
211     }
212 
213     /**
214      * Adds the {@param view} to the layout based on {@param rank} and updated the position
215      * related attributes. It assumes that {@param item} is already attached to the view.
216      */
addViewForRank(View view, ShortcutInfo item, int rank)217     public void addViewForRank(View view, ShortcutInfo item, int rank) {
218         int pagePos = rank % mMaxItemsPerPage;
219         int pageNo = rank / mMaxItemsPerPage;
220 
221         item.rank = rank;
222         item.cellX = pagePos % mGridCountX;
223         item.cellY = pagePos / mGridCountX;
224 
225         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
226         lp.cellX = item.cellX;
227         lp.cellY = item.cellY;
228         getPageAt(pageNo).addViewToCellLayout(
229                 view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
230     }
231 
232     @SuppressLint("InflateParams")
createNewView(ShortcutInfo item)233     public View createNewView(ShortcutInfo item) {
234         final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
235                 R.layout.folder_application, null, false);
236         textView.applyFromShortcutInfo(item);
237         textView.setHapticFeedbackEnabled(false);
238         textView.setOnClickListener(ItemClickHandler.INSTANCE);
239         textView.setOnLongClickListener(mFolder);
240         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
241 
242         textView.setLayoutParams(new CellLayout.LayoutParams(
243                 item.cellX, item.cellY, item.spanX, item.spanY));
244         return textView;
245     }
246 
247     @Override
getPageAt(int index)248     public CellLayout getPageAt(int index) {
249         return (CellLayout) getChildAt(index);
250     }
251 
getCurrentCellLayout()252     public CellLayout getCurrentCellLayout() {
253         return getPageAt(getNextPage());
254     }
255 
createAndAddNewPage()256     private CellLayout createAndAddNewPage() {
257         DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
258         CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false);
259         page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
260         page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
261         page.setInvertIfRtl(true);
262         page.setGridSize(mGridCountX, mGridCountY);
263 
264         addView(page, -1, generateDefaultLayoutParams());
265         return page;
266     }
267 
268     @Override
getChildGap()269     protected int getChildGap() {
270         return getPaddingLeft() + getPaddingRight();
271     }
272 
setFixedSize(int width, int height)273     public void setFixedSize(int width, int height) {
274         width -= (getPaddingLeft() + getPaddingRight());
275         height -= (getPaddingTop() + getPaddingBottom());
276         for (int i = getChildCount() - 1; i >= 0; i --) {
277             ((CellLayout) getChildAt(i)).setFixedSize(width, height);
278         }
279     }
280 
removeItem(View v)281     public void removeItem(View v) {
282         for (int i = getChildCount() - 1; i >= 0; i --) {
283             getPageAt(i).removeView(v);
284         }
285     }
286 
287     @Override
onScrollChanged(int l, int t, int oldl, int oldt)288     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
289         super.onScrollChanged(l, t, oldl, oldt);
290         mPageIndicator.setScroll(l, mMaxScrollX);
291     }
292 
293     /**
294      * Updates position and rank of all the children in the view.
295      * It essentially removes all views from all the pages and then adds them again in appropriate
296      * page.
297      *
298      * @param list the ordered list of children.
299      * @param itemCount if greater than the total children count, empty spaces are left
300      * at the end, otherwise it is ignored.
301      *
302      */
arrangeChildren(ArrayList<View> list, int itemCount)303     public void arrangeChildren(ArrayList<View> list, int itemCount) {
304         arrangeChildren(list, itemCount, true);
305     }
306 
307     @SuppressLint("RtlHardcoded")
arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges)308     private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
309         ArrayList<CellLayout> pages = new ArrayList<>();
310         for (int i = 0; i < getChildCount(); i++) {
311             CellLayout page = (CellLayout) getChildAt(i);
312             page.removeAllViews();
313             pages.add(page);
314         }
315         setupContentDimensions(itemCount);
316 
317         Iterator<CellLayout> pageItr = pages.iterator();
318         CellLayout currentPage = null;
319 
320         int position = 0;
321         int newX, newY, rank;
322 
323         FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
324                 Launcher.getLauncher(getContext()).getDeviceProfile().inv);
325         rank = 0;
326         for (int i = 0; i < itemCount; i++) {
327             View v = list.size() > i ? list.get(i) : null;
328             if (currentPage == null || position >= mMaxItemsPerPage) {
329                 // Next page
330                 if (pageItr.hasNext()) {
331                     currentPage = pageItr.next();
332                 } else {
333                     currentPage = createAndAddNewPage();
334                 }
335                 position = 0;
336             }
337 
338             if (v != null) {
339                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
340                 newX = position % mGridCountX;
341                 newY = position / mGridCountX;
342                 ItemInfo info = (ItemInfo) v.getTag();
343                 if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
344                     info.cellX = newX;
345                     info.cellY = newY;
346                     info.rank = rank;
347                     if (saveChanges) {
348                         mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info,
349                                 mFolder.mInfo.id, 0, info.cellX, info.cellY);
350                     }
351                 }
352                 lp.cellX = info.cellX;
353                 lp.cellY = info.cellY;
354                 currentPage.addViewToCellLayout(
355                         v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
356 
357                 if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
358                     ((BubbleTextView) v).verifyHighRes();
359                 }
360             }
361 
362             rank ++;
363             position++;
364         }
365 
366         // Remove extra views.
367         boolean removed = false;
368         while (pageItr.hasNext()) {
369             removeView(pageItr.next());
370             removed = true;
371         }
372         if (removed) {
373             setCurrentPage(0);
374         }
375 
376         setEnableOverscroll(getPageCount() > 1);
377 
378         // Update footer
379         mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
380         // Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text.
381         mFolder.mFolderName.setGravity(getPageCount() > 1 ?
382                 (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL);
383     }
384 
getDesiredWidth()385     public int getDesiredWidth() {
386         return getPageCount() > 0 ?
387                 (getPageAt(0).getDesiredWidth() + getPaddingLeft() + getPaddingRight()) : 0;
388     }
389 
getDesiredHeight()390     public int getDesiredHeight()  {
391         return  getPageCount() > 0 ?
392                 (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0;
393     }
394 
getItemCount()395     public int getItemCount() {
396         int lastPageIndex = getChildCount() - 1;
397         if (lastPageIndex < 0) {
398             // If there are no pages, nothing has yet been added to the folder.
399             return 0;
400         }
401         return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
402                 + lastPageIndex * mMaxItemsPerPage;
403     }
404 
405     /**
406      * @return the rank of the cell nearest to the provided pixel position.
407      */
findNearestArea(int pixelX, int pixelY)408     public int findNearestArea(int pixelX, int pixelY) {
409         int pageIndex = getNextPage();
410         CellLayout page = getPageAt(pageIndex);
411         page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray);
412         if (mFolder.isLayoutRtl()) {
413             sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1;
414         }
415         return Math.min(mAllocatedContentSize - 1,
416                 pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]);
417     }
418 
getFirstItem()419     public View getFirstItem() {
420         if (getChildCount() < 1) {
421             return null;
422         }
423         ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
424         if (mGridCountX > 0) {
425             return currContainer.getChildAt(0, 0);
426         } else {
427             return currContainer.getChildAt(0);
428         }
429     }
430 
getLastItem()431     public View getLastItem() {
432         if (getChildCount() < 1) {
433             return null;
434         }
435         ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
436         int lastRank = currContainer.getChildCount() - 1;
437         if (mGridCountX > 0) {
438             return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
439         } else {
440             return currContainer.getChildAt(lastRank);
441         }
442     }
443 
444     /**
445      * Iterates over all its items in a reading order.
446      * @return the view for which the operator returned true.
447      */
iterateOverItems(ItemOperator op)448     public View iterateOverItems(ItemOperator op) {
449         for (int k = 0 ; k < getChildCount(); k++) {
450             CellLayout page = getPageAt(k);
451             for (int j = 0; j < page.getCountY(); j++) {
452                 for (int i = 0; i < page.getCountX(); i++) {
453                     View v = page.getChildAt(i, j);
454                     if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v)) {
455                         return v;
456                     }
457                 }
458             }
459         }
460         return null;
461     }
462 
getAccessibilityDescription()463     public String getAccessibilityDescription() {
464         return getContext().getString(R.string.folder_opened, mGridCountX, mGridCountY);
465     }
466 
467     /**
468      * Sets the focus on the first visible child.
469      */
setFocusOnFirstChild()470     public void setFocusOnFirstChild() {
471         View firstChild = getCurrentCellLayout().getChildAt(0, 0);
472         if (firstChild != null) {
473             firstChild.requestFocus();
474         }
475     }
476 
477     @Override
notifyPageSwitchListener(int prevPage)478     protected void notifyPageSwitchListener(int prevPage) {
479         super.notifyPageSwitchListener(prevPage);
480         if (mFolder != null) {
481             mFolder.updateTextViewFocus();
482         }
483     }
484 
485     /**
486      * Scrolls the current view by a fraction
487      */
showScrollHint(int direction)488     public void showScrollHint(int direction) {
489         float fraction = (direction == Folder.SCROLL_LEFT) ^ mIsRtl
490                 ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
491         int hint = (int) (fraction * getWidth());
492         int scroll = getScrollForPage(getNextPage()) + hint;
493         int delta = scroll - getScrollX();
494         if (delta != 0) {
495             mScroller.setInterpolator(Interpolators.DEACCEL);
496             mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION);
497             invalidate();
498         }
499     }
500 
clearScrollHint()501     public void clearScrollHint() {
502         if (getScrollX() != getScrollForPage(getNextPage())) {
503             snapToPage(getNextPage());
504         }
505     }
506 
507     /**
508      * Finish animation all the views which are animating across pages
509      */
completePendingPageChanges()510     public void completePendingPageChanges() {
511         if (!mPendingAnimations.isEmpty()) {
512             ArrayMap<View, Runnable> pendingViews = new ArrayMap<>(mPendingAnimations);
513             for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) {
514                 e.getKey().animate().cancel();
515                 e.getValue().run();
516             }
517         }
518     }
519 
rankOnCurrentPage(int rank)520     public boolean rankOnCurrentPage(int rank) {
521         int p = rank / mMaxItemsPerPage;
522         return p == getNextPage();
523     }
524 
525     @Override
onPageBeginTransition()526     protected void onPageBeginTransition() {
527         super.onPageBeginTransition();
528         // Ensure that adjacent pages have high resolution icons
529         verifyVisibleHighResIcons(getCurrentPage() - 1);
530         verifyVisibleHighResIcons(getCurrentPage() + 1);
531     }
532 
533     /**
534      * Ensures that all the icons on the given page are of high-res
535      */
verifyVisibleHighResIcons(int pageNo)536     public void verifyVisibleHighResIcons(int pageNo) {
537         CellLayout page = getPageAt(pageNo);
538         if (page != null) {
539             ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
540             for (int i = parent.getChildCount() - 1; i >= 0; i--) {
541                 BubbleTextView icon = ((BubbleTextView) parent.getChildAt(i));
542                 icon.verifyHighRes();
543                 // Set the callback back to the actual icon, in case
544                 // it was captured by the FolderIcon
545                 Drawable d = icon.getCompoundDrawables()[1];
546                 if (d != null) {
547                     d.setCallback(icon);
548                 }
549             }
550         }
551     }
552 
getAllocatedContentSize()553     public int getAllocatedContentSize() {
554         return mAllocatedContentSize;
555     }
556 
557     /**
558      * Reorders the items such that the {@param empty} spot moves to {@param target}
559      */
realTimeReorder(int empty, int target)560     public void realTimeReorder(int empty, int target) {
561         completePendingPageChanges();
562         int delay = 0;
563         float delayAmount = START_VIEW_REORDER_DELAY;
564 
565         // Animation only happens on the current page.
566         int pageToAnimate = getNextPage();
567 
568         int pageT = target / mMaxItemsPerPage;
569         int pagePosT = target % mMaxItemsPerPage;
570 
571         if (pageT != pageToAnimate) {
572             Log.e(TAG, "Cannot animate when the target cell is invisible");
573         }
574         int pagePosE = empty % mMaxItemsPerPage;
575         int pageE = empty / mMaxItemsPerPage;
576 
577         int startPos, endPos;
578         int moveStart, moveEnd;
579         int direction;
580 
581         if (target == empty) {
582             // No animation
583             return;
584         } else if (target > empty) {
585             // Items will move backwards to make room for the empty cell.
586             direction = 1;
587 
588             // If empty cell is in a different page, move them instantly.
589             if (pageE < pageToAnimate) {
590                 moveStart = empty;
591                 // Instantly move the first item in the current page.
592                 moveEnd = pageToAnimate * mMaxItemsPerPage;
593                 // Animate the 2nd item in the current page, as the first item was already moved to
594                 // the last page.
595                 startPos = 0;
596             } else {
597                 moveStart = moveEnd = -1;
598                 startPos = pagePosE;
599             }
600 
601             endPos = pagePosT;
602         } else {
603             // The items will move forward.
604             direction = -1;
605 
606             if (pageE > pageToAnimate) {
607                 // Move the items immediately.
608                 moveStart = empty;
609                 // Instantly move the last item in the current page.
610                 moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
611 
612                 // Animations start with the second last item in the page
613                 startPos = mMaxItemsPerPage - 1;
614             } else {
615                 moveStart = moveEnd = -1;
616                 startPos = pagePosE;
617             }
618 
619             endPos = pagePosT;
620         }
621 
622         // Instant moving views.
623         while (moveStart != moveEnd) {
624             int rankToMove = moveStart + direction;
625             int p = rankToMove / mMaxItemsPerPage;
626             int pagePos = rankToMove % mMaxItemsPerPage;
627             int x = pagePos % mGridCountX;
628             int y = pagePos / mGridCountX;
629 
630             final CellLayout page = getPageAt(p);
631             final View v = page.getChildAt(x, y);
632             if (v != null) {
633                 if (pageToAnimate != p) {
634                     page.removeView(v);
635                     addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart);
636                 } else {
637                     // Do a fake animation before removing it.
638                     final int newRank = moveStart;
639                     final float oldTranslateX = v.getTranslationX();
640 
641                     Runnable endAction = new Runnable() {
642 
643                         @Override
644                         public void run() {
645                             mPendingAnimations.remove(v);
646                             v.setTranslationX(oldTranslateX);
647                             ((CellLayout) v.getParent().getParent()).removeView(v);
648                             addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
649                         }
650                     };
651                     v.animate()
652                         .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth())
653                         .setDuration(REORDER_ANIMATION_DURATION)
654                         .setStartDelay(0)
655                         .withEndAction(endAction);
656                     mPendingAnimations.put(v, endAction);
657                 }
658             }
659             moveStart = rankToMove;
660         }
661 
662         if ((endPos - startPos) * direction <= 0) {
663             // No animation
664             return;
665         }
666 
667         CellLayout page = getPageAt(pageToAnimate);
668         for (int i = startPos; i != endPos; i += direction) {
669             int nextPos = i + direction;
670             View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
671             if (v != null) {
672                 ((ItemInfo) v.getTag()).rank -= direction;
673             }
674             if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
675                     REORDER_ANIMATION_DURATION, delay, true, true)) {
676                 delay += delayAmount;
677                 delayAmount *= VIEW_REORDER_DELAY_FACTOR;
678             }
679         }
680     }
681 
itemsPerPage()682     public int itemsPerPage() {
683         return mMaxItemsPerPage;
684     }
685 }
686