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