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