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