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