1 /*
2  * Copyright (C) 2020 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 package com.android.wallpaper.picker;
17 
18 import static com.android.wallpaper.picker.WallpaperPickerDelegate.PREVIEW_LIVE_WALLPAPER_REQUEST_CODE;
19 import static com.android.wallpaper.picker.WallpaperPickerDelegate.PREVIEW_WALLPAPER_REQUEST_CODE;
20 
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.content.Intent;
24 import android.graphics.Point;
25 import android.graphics.PorterDuff;
26 import android.graphics.Rect;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.provider.Settings;
30 import android.util.DisplayMetrics;
31 import android.util.Log;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.ImageView;
36 import android.widget.ProgressBar;
37 import android.widget.TextView;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 import androidx.cardview.widget.CardView;
42 import androidx.fragment.app.Fragment;
43 import androidx.recyclerview.widget.GridLayoutManager;
44 import androidx.recyclerview.widget.RecyclerView;
45 
46 import com.android.wallpaper.R;
47 import com.android.wallpaper.asset.Asset;
48 import com.android.wallpaper.model.Category;
49 import com.android.wallpaper.model.CategoryProvider;
50 import com.android.wallpaper.model.LiveWallpaperInfo;
51 import com.android.wallpaper.model.WallpaperInfo;
52 import com.android.wallpaper.module.InjectorProvider;
53 import com.android.wallpaper.module.UserEventLogger;
54 import com.android.wallpaper.util.DeepLinkUtils;
55 import com.android.wallpaper.util.DisplayMetricsRetriever;
56 import com.android.wallpaper.util.ResourceUtils;
57 import com.android.wallpaper.util.SizeCalculator;
58 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate;
59 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost;
60 
61 import com.bumptech.glide.Glide;
62 
63 import java.util.ArrayList;
64 import java.util.List;
65 
66 /**
67  * Displays the UI which contains the categories of the wallpaper.
68  */
69 public class CategorySelectorFragment extends AppbarFragment {
70 
71     // The number of ViewHolders that don't pertain to category tiles.
72     // Currently 2: one for the metadata section and one for the "Select wallpaper" header.
73     private static final int NUM_NON_CATEGORY_VIEW_HOLDERS = 0;
74     private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
75     private static final String TAG = "CategorySelectorFragment";
76 
77     /**
78      * Interface to be implemented by an Fragment hosting a {@link CategorySelectorFragment}
79      */
80     public interface CategorySelectorFragmentHost {
81 
82         /**
83          * Requests to show the Android custom photo picker for the sake of picking a photo
84          * to set as the device's wallpaper.
85          */
requestCustomPhotoPicker(MyPhotosStarter.PermissionChangedListener listener)86         void requestCustomPhotoPicker(MyPhotosStarter.PermissionChangedListener listener);
87 
88         /**
89          * Shows the wallpaper page of the specific category.
90          *
91          * @param category the wallpaper's {@link Category}
92          */
show(Category category)93         void show(Category category);
94 
95 
96         /**
97          * Indicates if the host has toolbar to show the title. If it does, we should set the title
98          * there.
99          */
isHostToolbarShown()100         boolean isHostToolbarShown();
101 
102         /**
103          * Sets the title in the host's toolbar.
104          */
setToolbarTitle(CharSequence title)105         void setToolbarTitle(CharSequence title);
106 
107         /**
108          * Fetches the wallpaper categories.
109          */
fetchCategories()110         void fetchCategories();
111 
112         /**
113          * Cleans up the listeners which will be notified when there's a package event.
114          */
cleanUp()115         void cleanUp();
116     }
117 
118     private final CategoryProvider mCategoryProvider;
119 
120     private RecyclerView mImageGrid;
121     private CategoryAdapter mAdapter;
122     private ArrayList<Category> mCategories = new ArrayList<>();
123     private Point mTileSizePx;
124     private boolean mAwaitingCategories;
125     private boolean mIsFeaturedCollectionAvailable;
126 
CategorySelectorFragment()127     public CategorySelectorFragment() {
128         mAdapter = new CategoryAdapter(mCategories);
129         mCategoryProvider = InjectorProvider.getInjector().getCategoryProvider(getContext());
130     }
131 
132     @Nullable
133     @Override
onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)134     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
135                              @Nullable Bundle savedInstanceState) {
136         View view = inflater.inflate(R.layout.fragment_category_selector, container,
137                 /* attachToRoot= */ false);
138         mImageGrid = view.findViewById(R.id.category_grid);
139         mImageGrid.addItemDecoration(new GridPaddingDecoration(getResources().getDimensionPixelSize(
140                 R.dimen.grid_item_category_padding_horizontal)));
141 
142         mTileSizePx = SizeCalculator.getCategoryTileSize(getActivity());
143 
144         mImageGrid.setAdapter(mAdapter);
145 
146         GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(),
147                 getNumColumns() * CategorySpanSizeLookup.DEFAULT_CATEGORY_SPAN_SIZE);
148         gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter));
149         mImageGrid.setLayoutManager(gridLayoutManager);
150         mImageGrid.setAccessibilityDelegateCompat(
151                 new WallpaperPickerRecyclerViewAccessibilityDelegate(
152                         mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns()));
153 
154         if (getCategorySelectorFragmentHost().isHostToolbarShown()) {
155             view.findViewById(R.id.header_bar).setVisibility(View.GONE);
156             getCategorySelectorFragmentHost().setToolbarTitle(getText(R.string.wallpaper_title));
157         } else {
158             setUpToolbar(view);
159             setTitle(getText(R.string.wallpaper_title));
160         }
161 
162         if (!DeepLinkUtils.isDeepLink(getActivity().getIntent())) {
163             getCategorySelectorFragmentHost().fetchCategories();
164         }
165 
166         // For nav bar edge-to-edge effect.
167         view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
168             // For status bar height.
169             v.setPadding(
170                     v.getPaddingLeft(),
171                     windowInsets.getSystemWindowInsetTop(),
172                     v.getPaddingRight(),
173                     v.getPaddingBottom());
174 
175             View gridView = v.findViewById(R.id.category_grid);
176             gridView.setPadding(
177                     gridView.getPaddingLeft(),
178                     gridView.getPaddingTop(),
179                     gridView.getPaddingRight(),
180                     windowInsets.getSystemWindowInsetBottom());
181             return windowInsets.consumeSystemWindowInsets();
182         });
183         return view;
184     }
185 
186     @Override
onDestroyView()187     public void onDestroyView() {
188         getCategorySelectorFragmentHost().cleanUp();
189         super.onDestroyView();
190     }
191 
192     /**
193      * Inserts the given category into the categories list in priority order.
194      */
addCategory(Category category, boolean loading)195     void addCategory(Category category, boolean loading) {
196         // If not previously waiting for categories, enter the waiting state by showing the loading
197         // indicator.
198         if (loading && !mAwaitingCategories) {
199             mAdapter.notifyItemChanged(getNumColumns());
200             mAdapter.notifyItemInserted(getNumColumns());
201             mAwaitingCategories = true;
202         }
203         // Not add existing category to category list
204         if (mCategories.indexOf(category) >= 0) {
205             updateCategory(category);
206             return;
207         }
208 
209         int priority = category.getPriority();
210 
211         int index = 0;
212         while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) {
213             index++;
214         }
215 
216         mCategories.add(index, category);
217         if (mAdapter != null) {
218             // Offset the index because of the static metadata element at beginning of RecyclerView.
219             mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
220         }
221     }
222 
removeCategory(Category category)223     void removeCategory(Category category) {
224         int index = mCategories.indexOf(category);
225         if (index >= 0) {
226             mCategories.remove(index);
227             mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
228         }
229     }
230 
updateCategory(Category category)231     void updateCategory(Category category) {
232         int index = mCategories.indexOf(category);
233         if (index >= 0) {
234             mCategories.remove(index);
235             mCategories.add(index, category);
236             mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
237         }
238     }
239 
clearCategories()240     void clearCategories() {
241         mCategories.clear();
242         mAdapter.notifyDataSetChanged();
243     }
244 
245     /**
246      * Notifies the CategoryFragment that no further categories are expected so it may hide
247      * the loading indicator.
248      */
doneFetchingCategories()249     void doneFetchingCategories() {
250         if (mAwaitingCategories) {
251             mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1);
252             mAwaitingCategories = false;
253         }
254 
255         mIsFeaturedCollectionAvailable = mCategoryProvider.isFeaturedCollectionAvailable();
256     }
257 
notifyDataSetChanged()258     void notifyDataSetChanged() {
259         mAdapter.notifyDataSetChanged();
260     }
261 
getNumColumns()262     private int getNumColumns() {
263         Activity activity = getActivity();
264         return activity == null ? 1 : SizeCalculator.getNumCategoryColumns(activity);
265     }
266 
267 
getCategorySelectorFragmentHost()268     private CategorySelectorFragmentHost getCategorySelectorFragmentHost() {
269         Fragment parentFragment = getParentFragment();
270         if (parentFragment != null) {
271             return (CategorySelectorFragmentHost) parentFragment;
272         } else {
273             return (CategorySelectorFragmentHost) getActivity();
274         }
275     }
276 
277     /**
278      * ViewHolder subclass for a category tile in the RecyclerView.
279      */
280     private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
281         private Category mCategory;
282         private ImageView mImageView;
283         private ImageView mOverlayIconView;
284         private TextView mTitleView;
285 
CategoryHolder(View itemView)286         CategoryHolder(View itemView) {
287             super(itemView);
288             itemView.setOnClickListener(this);
289 
290             mImageView = itemView.findViewById(R.id.image);
291             mOverlayIconView = itemView.findViewById(R.id.overlay_icon);
292             mTitleView = itemView.findViewById(R.id.category_title);
293 
294             CardView categoryView = itemView.findViewById(R.id.category);
295             categoryView.getLayoutParams().height = mTileSizePx.y;
296             categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius_small));
297         }
298 
299         @Override
onClick(View view)300         public void onClick(View view) {
301             Activity activity = getActivity();
302             final UserEventLogger eventLogger =
303                     InjectorProvider.getInjector().getUserEventLogger(activity);
304             eventLogger.logCategorySelected(mCategory.getCollectionId());
305 
306             if (mCategory.supportsCustomPhotos()) {
307                 getCategorySelectorFragmentHost().requestCustomPhotoPicker(
308                         new MyPhotosStarter.PermissionChangedListener() {
309                             @Override
310                             public void onPermissionsGranted() {
311                                 drawThumbnailAndOverlayIcon();
312                             }
313 
314                             @Override
315                             public void onPermissionsDenied(boolean dontAskAgain) {
316                                 // No-op
317                             }
318                         });
319                 return;
320             }
321 
322             if (mCategory.isSingleWallpaperCategory()) {
323                 WallpaperInfo wallpaper = mCategory.getSingleWallpaper();
324                 // Log click on individual wallpaper
325                 eventLogger.logIndividualWallpaperSelected(mCategory.getCollectionId());
326 
327                 InjectorProvider.getInjector().getWallpaperPersister(activity)
328                         .setWallpaperInfoInPreview(wallpaper);
329                 wallpaper.showPreview(activity,
330                         new PreviewActivity.PreviewActivityIntentFactory(),
331                         wallpaper instanceof LiveWallpaperInfo ? PREVIEW_LIVE_WALLPAPER_REQUEST_CODE
332                                 : PREVIEW_WALLPAPER_REQUEST_CODE);
333                 return;
334             }
335 
336             getCategorySelectorFragmentHost().show(mCategory);
337         }
338 
339         /**
340          * Binds the given category to this CategoryHolder.
341          */
bindCategory(Category category)342         private void bindCategory(Category category) {
343             mCategory = category;
344             mTitleView.setText(category.getTitle());
345             drawThumbnailAndOverlayIcon();
346         }
347 
348         /**
349          * Draws the CategoryHolder's thumbnail and overlay icon.
350          */
drawThumbnailAndOverlayIcon()351         private void drawThumbnailAndOverlayIcon() {
352             mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon(
353                     getActivity().getApplicationContext()));
354 
355             // Size the overlay icon according to the category.
356             int overlayIconDimenDp = mCategory.getOverlayIconSizeDp();
357             DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
358                     getResources(), getActivity().getWindowManager().getDefaultDisplay());
359             int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density);
360             mOverlayIconView.getLayoutParams().width = overlayIconDimenPx;
361             mOverlayIconView.getLayoutParams().height = overlayIconDimenPx;
362 
363             Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext());
364             if (thumbnail != null) {
365                 thumbnail.loadDrawable(getActivity(), mImageView,
366                         ResourceUtils.getColorAttr(
367                             getActivity(),
368                             android.R.attr.colorSecondary
369                         ));
370             } else {
371                 // TODO(orenb): Replace this workaround for b/62584914 with a proper way of
372                 //  unloading the ImageView such that no incorrect image is improperly loaded upon
373                 //  rapid scroll.
374                 Object nullObj = null;
375                 Glide.with(getActivity())
376                         .asDrawable()
377                         .load(nullObj)
378                         .into(mImageView);
379 
380             }
381         }
382     }
383 
384     private class FeaturedCategoryHolder extends CategoryHolder {
385 
FeaturedCategoryHolder(View itemView)386         FeaturedCategoryHolder(View itemView) {
387             super(itemView);
388             CardView categoryView = itemView.findViewById(R.id.category);
389             categoryView.getLayoutParams().height =
390                     SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
391             categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius));
392         }
393     }
394 
395     private class MyPhotosCategoryHolder extends CategoryHolder {
396 
MyPhotosCategoryHolder(View itemView)397         MyPhotosCategoryHolder(View itemView) {
398             super(itemView);
399             // Reuse the height of featured category since My Photos category & featured category
400             // have the same height in current UI design.
401             CardView categoryView = itemView.findViewById(R.id.category);
402             int height = SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
403             categoryView.getLayoutParams().height = height;
404             // Use the height as the card corner radius for the "My photos" category
405             // for a stadium border.
406             categoryView.setRadius(height);
407         }
408     }
409 
410     /**
411      * ViewHolder subclass for the loading indicator ("spinner") shown when categories are being
412      * fetched.
413      */
414     private class LoadingIndicatorHolder extends RecyclerView.ViewHolder {
LoadingIndicatorHolder(View view)415         private LoadingIndicatorHolder(View view) {
416             super(view);
417             ProgressBar progressBar = view.findViewById(R.id.loading_indicator);
418             progressBar.getIndeterminateDrawable().setColorFilter(
419                     ResourceUtils.getColorAttr(
420                             getActivity(),
421                             android.R.attr.colorAccent
422                     ), PorterDuff.Mode.SRC_IN);
423         }
424     }
425 
426     /**
427      * RecyclerView Adapter subclass for the category tiles in the RecyclerView.
428      */
429     private class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
430             implements MyPhotosStarter.PermissionChangedListener {
431         private static final int ITEM_VIEW_TYPE_MY_PHOTOS = 1;
432         private static final int ITEM_VIEW_TYPE_FEATURED_CATEGORY = 2;
433         private static final int ITEM_VIEW_TYPE_CATEGORY = 3;
434         private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4;
435         private List<Category> mCategories;
436 
CategoryAdapter(List<Category> categories)437         private CategoryAdapter(List<Category> categories) {
438             mCategories = categories;
439         }
440 
441         @Override
getItemViewType(int position)442         public int getItemViewType(int position) {
443             if (mAwaitingCategories && position == getItemCount() - 1) {
444                 return ITEM_VIEW_TYPE_LOADING_INDICATOR;
445             }
446 
447             if (position == 0) {
448                 return ITEM_VIEW_TYPE_MY_PHOTOS;
449             }
450 
451             if (mIsFeaturedCollectionAvailable && (position == 1 || position == 2)) {
452                 return ITEM_VIEW_TYPE_FEATURED_CATEGORY;
453             }
454 
455             return ITEM_VIEW_TYPE_CATEGORY;
456         }
457 
458         @Override
onCreateViewHolder(ViewGroup parent, int viewType)459         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
460             LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
461             View view;
462 
463             switch (viewType) {
464                 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
465                     view = layoutInflater.inflate(R.layout.grid_item_loading_indicator,
466                             parent, /* attachToRoot= */ false);
467                     return new LoadingIndicatorHolder(view);
468                 case ITEM_VIEW_TYPE_MY_PHOTOS:
469                     view = layoutInflater.inflate(R.layout.grid_item_category,
470                             parent, /* attachToRoot= */ false);
471                     return new MyPhotosCategoryHolder(view);
472                 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
473                     view = layoutInflater.inflate(R.layout.grid_item_category,
474                             parent, /* attachToRoot= */ false);
475                     return new FeaturedCategoryHolder(view);
476                 case ITEM_VIEW_TYPE_CATEGORY:
477                     view = layoutInflater.inflate(R.layout.grid_item_category,
478                             parent, /* attachToRoot= */ false);
479                     return new CategoryHolder(view);
480                 default:
481                     Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
482                     return null;
483             }
484         }
485 
486         @Override
onBindViewHolder(RecyclerView.ViewHolder holder, int position)487         public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
488             int viewType = getItemViewType(position);
489 
490             switch (viewType) {
491                 case ITEM_VIEW_TYPE_MY_PHOTOS:
492                 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
493                 case ITEM_VIEW_TYPE_CATEGORY:
494                     // Offset position to get category index to account for the non-category view
495                     // holders.
496                     Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS);
497                     ((CategoryHolder) holder).bindCategory(category);
498                     break;
499                 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
500                     // No op.
501                     break;
502                 default:
503                     Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
504             }
505         }
506 
507         @Override
getItemCount()508         public int getItemCount() {
509             // Add to size of categories to account for the metadata related views.
510             // Add 1 more for the loading indicator if not yet done loading.
511             int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS;
512             if (mAwaitingCategories) {
513                 size += 1;
514             }
515 
516             return size;
517         }
518 
519         @Override
onPermissionsGranted()520         public void onPermissionsGranted() {
521             notifyDataSetChanged();
522         }
523 
524         @Override
onPermissionsDenied(boolean dontAskAgain)525         public void onPermissionsDenied(boolean dontAskAgain) {
526             if (!dontAskAgain) {
527                 return;
528             }
529 
530             String permissionNeededMessage =
531                     getString(R.string.permission_needed_explanation_go_to_settings);
532             AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
533                     .setMessage(permissionNeededMessage)
534                     .setPositiveButton(android.R.string.ok, null /* onClickListener */)
535                     .setNegativeButton(
536                             R.string.settings_button_label,
537                             (dialogInterface, i) -> {
538                                 Intent appInfoIntent =
539                                         new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
540                                 Uri uri = Uri.fromParts("package",
541                                         getActivity().getPackageName(), /* fragment= */ null);
542                                 appInfoIntent.setData(uri);
543                                 startActivityForResult(
544                                         appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE);
545                             })
546                     .create();
547             dialog.show();
548         }
549     }
550 
551     private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
552 
553         private final int mPadding;
554 
GridPaddingDecoration(int padding)555         GridPaddingDecoration(int padding) {
556             mPadding = padding;
557         }
558 
559         @Override
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)560         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
561                                    RecyclerView.State state) {
562             int position = parent.getChildAdapterPosition(view) - NUM_NON_CATEGORY_VIEW_HOLDERS;
563             if (position >= 0) {
564                 outRect.left = mPadding;
565                 outRect.right = mPadding;
566             }
567 
568             RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
569             if (viewHolder instanceof MyPhotosCategoryHolder
570                     || viewHolder instanceof FeaturedCategoryHolder) {
571                 outRect.bottom = getResources().getDimensionPixelSize(
572                         R.dimen.grid_item_featured_category_padding_bottom);
573             } else {
574                 outRect.bottom = getResources().getDimensionPixelSize(
575                         R.dimen.grid_item_category_padding_bottom);
576             }
577         }
578     }
579 
580     /**
581      * SpanSizeLookup subclass which provides that the item in the first position spans the number
582      * of columns in the RecyclerView and all other items only take up a single span.
583      */
584     private class CategorySpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
585         private static final int DEFAULT_CATEGORY_SPAN_SIZE = 2;
586 
587         CategoryAdapter mAdapter;
588 
CategorySpanSizeLookup(CategoryAdapter adapter)589         private CategorySpanSizeLookup(CategoryAdapter adapter) {
590             mAdapter = adapter;
591         }
592 
593         @Override
getSpanSize(int position)594         public int getSpanSize(int position) {
595             if (position < NUM_NON_CATEGORY_VIEW_HOLDERS || mAdapter.getItemViewType(position)
596                     == CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR || mAdapter.getItemViewType(
597                     position) == CategoryAdapter.ITEM_VIEW_TYPE_MY_PHOTOS) {
598                 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE;
599             }
600 
601             if (mAdapter.getItemViewType(position)
602                     == CategoryAdapter.ITEM_VIEW_TYPE_FEATURED_CATEGORY) {
603                 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE / 2;
604             }
605 
606             return DEFAULT_CATEGORY_SPAN_SIZE;
607         }
608     }
609 }
610