1 /* 2 * Copyright (C) 2017 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.individual; 17 18 import android.annotation.MenuRes; 19 import android.app.Activity; 20 import android.app.ProgressDialog; 21 import android.app.WallpaperManager; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.res.Configuration; 25 import android.content.res.Resources.NotFoundException; 26 import android.graphics.Point; 27 import android.os.Build.VERSION; 28 import android.os.Build.VERSION_CODES; 29 import android.os.Bundle; 30 import android.service.wallpaper.WallpaperService; 31 import android.text.TextUtils; 32 import android.util.ArraySet; 33 import android.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.MenuItem; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.widget.ImageView; 39 import android.widget.RelativeLayout; 40 import android.widget.Toast; 41 42 import androidx.annotation.DrawableRes; 43 import androidx.annotation.NonNull; 44 import androidx.cardview.widget.CardView; 45 import androidx.core.widget.ContentLoadingProgressBar; 46 import androidx.fragment.app.DialogFragment; 47 import androidx.fragment.app.Fragment; 48 import androidx.recyclerview.widget.GridLayoutManager; 49 import androidx.recyclerview.widget.RecyclerView; 50 import androidx.recyclerview.widget.RecyclerView.ViewHolder; 51 52 import com.android.wallpaper.R; 53 import com.android.wallpaper.model.Category; 54 import com.android.wallpaper.model.CategoryProvider; 55 import com.android.wallpaper.model.CategoryReceiver; 56 import com.android.wallpaper.model.WallpaperCategory; 57 import com.android.wallpaper.model.WallpaperInfo; 58 import com.android.wallpaper.model.WallpaperReceiver; 59 import com.android.wallpaper.model.WallpaperRotationInitializer; 60 import com.android.wallpaper.model.WallpaperRotationInitializer.Listener; 61 import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference; 62 import com.android.wallpaper.module.Injector; 63 import com.android.wallpaper.module.InjectorProvider; 64 import com.android.wallpaper.module.PackageStatusNotifier; 65 import com.android.wallpaper.module.WallpaperPreferences; 66 import com.android.wallpaper.picker.AppbarFragment; 67 import com.android.wallpaper.picker.FragmentTransactionChecker; 68 import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider; 69 import com.android.wallpaper.picker.RotationStarter; 70 import com.android.wallpaper.picker.StartRotationDialogFragment; 71 import com.android.wallpaper.picker.StartRotationErrorDialogFragment; 72 import com.android.wallpaper.util.ActivityUtils; 73 import com.android.wallpaper.util.LaunchUtils; 74 import com.android.wallpaper.util.SizeCalculator; 75 import com.android.wallpaper.widget.GridPaddingDecoration; 76 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate; 77 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost; 78 79 import com.bumptech.glide.Glide; 80 import com.bumptech.glide.MemoryCategory; 81 82 import java.util.ArrayList; 83 import java.util.Date; 84 import java.util.List; 85 import java.util.Set; 86 87 /** 88 * Displays the Main UI for picking an individual wallpaper image. 89 */ 90 public class IndividualPickerFragment extends AppbarFragment 91 implements RotationStarter, StartRotationErrorDialogFragment.Listener, 92 StartRotationDialogFragment.Listener { 93 94 /** 95 * Position of a special tile that doesn't belong to an individual wallpaper of the category, 96 * such as "my photos" or "daily rotation". 97 */ 98 static final int SPECIAL_FIXED_TILE_ADAPTER_POSITION = 0; 99 static final String ARG_CATEGORY_COLLECTION_ID = "category_collection_id"; 100 101 protected static final int MAX_CAPACITY_IN_FEWER_COLUMN_LAYOUT = 8; 102 103 private static final String TAG = "IndividualPickerFrgmnt"; 104 private static final int UNUSED_REQUEST_CODE = 1; 105 private static final String TAG_START_ROTATION_DIALOG = "start_rotation_dialog"; 106 private static final String TAG_START_ROTATION_ERROR_DIALOG = "start_rotation_error_dialog"; 107 private static final String PROGRESS_DIALOG_NO_TITLE = null; 108 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true; 109 private static final String KEY_NIGHT_MODE = "IndividualPickerFragment.NIGHT_MODE"; 110 111 /** 112 * Interface to be implemented by a Fragment(or an Activity) hosting 113 * a {@link IndividualPickerFragment}. 114 */ 115 public interface IndividualPickerFragmentHost { 116 /** 117 * Indicates if the host has toolbar to show the title. If it does, we should set the title 118 * there. 119 */ isHostToolbarShown()120 boolean isHostToolbarShown(); 121 122 /** 123 * Sets the title in the host's toolbar. 124 */ setToolbarTitle(CharSequence title)125 void setToolbarTitle(CharSequence title); 126 127 /** 128 * Configures the menu in the toolbar. 129 * 130 * @param menuResId the resource id of the menu 131 */ setToolbarMenu(@enuRes int menuResId)132 void setToolbarMenu(@MenuRes int menuResId); 133 134 /** 135 * Removes the menu in the toolbar. 136 */ removeToolbarMenu()137 void removeToolbarMenu(); 138 139 /** 140 * Moves to the previous fragment. 141 */ moveToPreviousFragment()142 void moveToPreviousFragment(); 143 } 144 145 RecyclerView mImageGrid; 146 IndividualAdapter mAdapter; 147 WallpaperCategory mCategory; 148 WallpaperRotationInitializer mWallpaperRotationInitializer; 149 List<WallpaperInfo> mWallpapers; 150 Point mTileSizePx; 151 PackageStatusNotifier mPackageStatusNotifier; 152 153 boolean mIsWallpapersReceived; 154 PackageStatusNotifier.Listener mAppStatusListener; 155 156 private ProgressDialog mProgressDialog; 157 private ContentLoadingProgressBar mLoading; 158 private CategoryProvider mCategoryProvider; 159 160 /** 161 * Staged error dialog fragments that were unable to be shown when the activity didn't allow 162 * committing fragment transactions. 163 */ 164 private StartRotationErrorDialogFragment mStagedStartRotationErrorDialogFragment; 165 166 private WallpaperManager mWallpaperManager; 167 private Set<String> mAppliedWallpaperIds; 168 newInstance(String collectionId)169 public static IndividualPickerFragment newInstance(String collectionId) { 170 Bundle args = new Bundle(); 171 args.putString(ARG_CATEGORY_COLLECTION_ID, collectionId); 172 173 IndividualPickerFragment fragment = new IndividualPickerFragment(); 174 fragment.setArguments(args); 175 return fragment; 176 } 177 178 @Override onCreate(Bundle savedInstanceState)179 public void onCreate(Bundle savedInstanceState) { 180 super.onCreate(savedInstanceState); 181 182 Injector injector = InjectorProvider.getInjector(); 183 Context appContext = getContext().getApplicationContext(); 184 185 mWallpaperManager = WallpaperManager.getInstance(appContext); 186 187 mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext); 188 189 mWallpapers = new ArrayList<>(); 190 191 // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded 192 if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE) 193 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) { 194 Glide.get(getContext()).clearMemory(); 195 } 196 197 mCategoryProvider = injector.getCategoryProvider(appContext); 198 mCategoryProvider.fetchCategories(new CategoryReceiver() { 199 @Override 200 public void onCategoryReceived(Category category) { 201 // Do nothing. 202 } 203 204 @Override 205 public void doneFetchingCategories() { 206 Category category = mCategoryProvider.getCategory( 207 getArguments().getString(ARG_CATEGORY_COLLECTION_ID)); 208 if (category != null && !(category instanceof WallpaperCategory)) { 209 return; 210 } 211 mCategory = (WallpaperCategory) category; 212 if (mCategory == null) { 213 // The absence of this category in the CategoryProvider indicates a broken 214 // state, see b/38030129. Hence, finish the activity and return. 215 getIndividualPickerFragmentHost().moveToPreviousFragment(); 216 Toast.makeText(getContext(), R.string.collection_not_exist_msg, 217 Toast.LENGTH_SHORT).show(); 218 return; 219 } 220 onCategoryLoaded(); 221 } 222 }, false); 223 } 224 225 onCategoryLoaded()226 protected void onCategoryLoaded() { 227 if (getIndividualPickerFragmentHost() == null) { 228 return; 229 } 230 if (getIndividualPickerFragmentHost().isHostToolbarShown()) { 231 getIndividualPickerFragmentHost().setToolbarTitle(mCategory.getTitle()); 232 } else { 233 setTitle(mCategory.getTitle()); 234 } 235 mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer(); 236 if (mToolbar != null && isRotationEnabled()) { 237 setUpToolbarMenu(R.menu.individual_picker_menu); 238 } 239 fetchWallpapers(false); 240 241 if (mCategory.supportsThirdParty()) { 242 mAppStatusListener = (packageName, status) -> { 243 if (status != PackageStatusNotifier.PackageStatus.REMOVED || 244 mCategory.containsThirdParty(packageName)) { 245 fetchWallpapers(true); 246 } 247 }; 248 mPackageStatusNotifier.addListener(mAppStatusListener, 249 WallpaperService.SERVICE_INTERFACE); 250 } 251 } 252 fetchWallpapers(boolean forceReload)253 void fetchWallpapers(boolean forceReload) { 254 mWallpapers.clear(); 255 mIsWallpapersReceived = false; 256 updateLoading(); 257 mCategory.fetchWallpapers(getActivity().getApplicationContext(), new WallpaperReceiver() { 258 @Override 259 public void onWallpapersReceived(List<WallpaperInfo> wallpapers) { 260 mIsWallpapersReceived = true; 261 updateLoading(); 262 for (WallpaperInfo wallpaper : wallpapers) { 263 mWallpapers.add(wallpaper); 264 } 265 maybeSetUpImageGrid(); 266 267 // Wallpapers may load after the adapter is initialized, in which case we have 268 // to explicitly notify that the data set has changed. 269 if (mAdapter != null) { 270 mAdapter.notifyDataSetChanged(); 271 } 272 273 if (wallpapers.isEmpty()) { 274 // If there are no more wallpapers and we're on phone, just finish the 275 // Activity. 276 Activity activity = getActivity(); 277 if (activity != null) { 278 activity.finish(); 279 } 280 } 281 } 282 }, forceReload); 283 } 284 updateLoading()285 void updateLoading() { 286 if (mLoading == null) { 287 return; 288 } 289 290 if (mIsWallpapersReceived) { 291 mLoading.hide(); 292 } else { 293 mLoading.show(); 294 } 295 } 296 297 @Override onSaveInstanceState(@onNull Bundle outState)298 public void onSaveInstanceState(@NonNull Bundle outState) { 299 super.onSaveInstanceState(outState); 300 outState.putInt(KEY_NIGHT_MODE, 301 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK); 302 } 303 304 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)305 public View onCreateView(LayoutInflater inflater, ViewGroup container, 306 Bundle savedInstanceState) { 307 View view = inflater.inflate(R.layout.fragment_individual_picker, container, false); 308 if (getIndividualPickerFragmentHost().isHostToolbarShown()) { 309 view.findViewById(R.id.header_bar).setVisibility(View.GONE); 310 setUpArrowEnabled(/* upArrow= */ true); 311 if (isRotationEnabled()) { 312 getIndividualPickerFragmentHost().setToolbarMenu(R.menu.individual_picker_menu); 313 } 314 } else { 315 setUpToolbar(view); 316 if (isRotationEnabled()) { 317 setUpToolbarMenu(R.menu.individual_picker_menu); 318 } 319 if (mCategory != null) { 320 setTitle(mCategory.getTitle()); 321 } 322 } 323 324 mAppliedWallpaperIds = getAppliedWallpaperIds(); 325 326 mImageGrid = (RecyclerView) view.findViewById(R.id.wallpaper_grid); 327 mLoading = view.findViewById(R.id.loading_indicator); 328 updateLoading(); 329 maybeSetUpImageGrid(); 330 // For nav bar edge-to-edge effect. 331 mImageGrid.setOnApplyWindowInsetsListener((v, windowInsets) -> { 332 v.setPadding( 333 v.getPaddingLeft(), 334 v.getPaddingTop(), 335 v.getPaddingRight(), 336 windowInsets.getSystemWindowInsetBottom()); 337 return windowInsets.consumeSystemWindowInsets(); 338 }); 339 return view; 340 } 341 getIndividualPickerFragmentHost()342 private IndividualPickerFragmentHost getIndividualPickerFragmentHost() { 343 Fragment parentFragment = getParentFragment(); 344 if (parentFragment != null) { 345 return (IndividualPickerFragmentHost) parentFragment; 346 } else { 347 return (IndividualPickerFragmentHost) getActivity(); 348 } 349 } 350 maybeSetUpImageGrid()351 protected void maybeSetUpImageGrid() { 352 // Skip if mImageGrid been initialized yet 353 if (mImageGrid == null) { 354 return; 355 } 356 // Skip if category hasn't loaded yet 357 if (mCategory == null) { 358 return; 359 } 360 if (getContext() == null) { 361 return; 362 } 363 364 // Wallpaper count could change, so we may need to change the layout(2 or 3 columns layout) 365 GridLayoutManager gridLayoutManager = (GridLayoutManager) mImageGrid.getLayoutManager(); 366 boolean needUpdateLayout = 367 gridLayoutManager != null && gridLayoutManager.getSpanCount() != getNumColumns(); 368 369 // Skip if the adapter was already created and don't need to change the layout 370 if (mAdapter != null && !needUpdateLayout) { 371 return; 372 } 373 374 // Clear the old decoration 375 int decorationCount = mImageGrid.getItemDecorationCount(); 376 for (int i = 0; i < decorationCount; i++) { 377 mImageGrid.removeItemDecorationAt(i); 378 } 379 380 mImageGrid.addItemDecoration(new GridPaddingDecoration(getGridItemPaddingHorizontal(), 381 getGridItemPaddingBottom())); 382 int edgePadding = getEdgePadding(); 383 mImageGrid.setPadding(edgePadding, mImageGrid.getPaddingTop(), edgePadding, 384 mImageGrid.getPaddingBottom()); 385 mTileSizePx = isFewerColumnLayout() 386 ? SizeCalculator.getFeaturedIndividualTileSize(getActivity()) 387 : SizeCalculator.getIndividualTileSize(getActivity()); 388 setUpImageGrid(); 389 mImageGrid.setAccessibilityDelegateCompat( 390 new WallpaperPickerRecyclerViewAccessibilityDelegate( 391 mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns())); 392 } 393 isFewerColumnLayout()394 boolean isFewerColumnLayout() { 395 return mWallpapers != null && mWallpapers.size() <= MAX_CAPACITY_IN_FEWER_COLUMN_LAYOUT; 396 } 397 getGridItemPaddingHorizontal()398 private int getGridItemPaddingHorizontal() { 399 return isFewerColumnLayout() 400 ? getResources().getDimensionPixelSize( 401 R.dimen.grid_item_featured_individual_padding_horizontal) 402 : getResources().getDimensionPixelSize( 403 R.dimen.grid_item_individual_padding_horizontal); 404 } 405 getGridItemPaddingBottom()406 private int getGridItemPaddingBottom() { 407 return isFewerColumnLayout() 408 ? getResources().getDimensionPixelSize( 409 R.dimen.grid_item_featured_individual_padding_bottom) 410 : getResources().getDimensionPixelSize(R.dimen.grid_item_individual_padding_bottom); 411 } 412 getEdgePadding()413 private int getEdgePadding() { 414 return isFewerColumnLayout() 415 ? getResources().getDimensionPixelSize(R.dimen.featured_wallpaper_grid_edge_space) 416 : getResources().getDimensionPixelSize(R.dimen.wallpaper_grid_edge_space); 417 } 418 419 /** 420 * Create the adapter and assign it to mImageGrid. 421 * Both mImageGrid and mCategory are guaranteed to not be null when this method is called. 422 */ setUpImageGrid()423 void setUpImageGrid() { 424 mAdapter = new IndividualAdapter(mWallpapers); 425 mImageGrid.setAdapter(mAdapter); 426 mImageGrid.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns())); 427 } 428 429 @Override onResume()430 public void onResume() { 431 super.onResume(); 432 433 WallpaperPreferences preferences = InjectorProvider.getInjector() 434 .getPreferences(getActivity()); 435 preferences.setLastAppActiveTimestamp(new Date().getTime()); 436 437 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in 438 // PreviewFragment. 439 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL); 440 441 // Show the staged 'start rotation' error dialog fragment if there is one that was unable to be 442 // shown earlier when this fragment's hosting activity didn't allow committing fragment 443 // transactions. 444 if (mStagedStartRotationErrorDialogFragment != null) { 445 mStagedStartRotationErrorDialogFragment.show( 446 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG); 447 mStagedStartRotationErrorDialogFragment = null; 448 } 449 } 450 451 @Override onDestroyView()452 public void onDestroyView() { 453 super.onDestroyView(); 454 getIndividualPickerFragmentHost().removeToolbarMenu(); 455 } 456 457 @Override onDestroy()458 public void onDestroy() { 459 super.onDestroy(); 460 if (mProgressDialog != null) { 461 mProgressDialog.dismiss(); 462 } 463 if (mAppStatusListener != null) { 464 mPackageStatusNotifier.removeListener(mAppStatusListener); 465 } 466 } 467 468 @Override onStartRotationDialogDismiss(@onNull DialogInterface dialog)469 public void onStartRotationDialogDismiss(@NonNull DialogInterface dialog) { 470 // TODO(b/159310028): Refactor fragment layer to make it able to restore from config change. 471 // This is to handle config change with StartRotationDialog popup, the StartRotationDialog 472 // still holds a reference to the destroyed Fragment and is calling 473 // onStartRotationDialogDismissed on that destroyed Fragment. 474 } 475 476 @Override retryStartRotation(@etworkPreference int networkPreference)477 public void retryStartRotation(@NetworkPreference int networkPreference) { 478 startRotation(networkPreference); 479 } 480 481 @Override startRotation(@etworkPreference final int networkPreference)482 public void startRotation(@NetworkPreference final int networkPreference) { 483 if (!isRotationEnabled()) { 484 Log.e(TAG, "Rotation is not enabled for this category " + mCategory.getTitle()); 485 return; 486 } 487 488 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore 489 // causes Espresso to hang once the dialog is shown. 490 int themeResId; 491 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) { 492 themeResId = R.style.ProgressDialogThemePreL; 493 } else { 494 themeResId = R.style.LightDialogTheme; 495 } 496 mProgressDialog = new ProgressDialog(getActivity(), themeResId); 497 498 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE); 499 mProgressDialog.setMessage( 500 getResources().getString(R.string.start_rotation_progress_message)); 501 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE); 502 mProgressDialog.show(); 503 504 final Context appContext = getActivity().getApplicationContext(); 505 506 mWallpaperRotationInitializer.setFirstWallpaperInRotation( 507 appContext, 508 networkPreference, 509 new Listener() { 510 @Override 511 public void onFirstWallpaperInRotationSet() { 512 if (mProgressDialog != null) { 513 mProgressDialog.dismiss(); 514 } 515 516 // The fragment may be detached from its containing activity if the user exits the 517 // app before the first wallpaper image in rotation finishes downloading. 518 Activity activity = getActivity(); 519 520 if (mWallpaperRotationInitializer.startRotation(appContext)) { 521 if (activity != null) { 522 try { 523 Toast.makeText(activity, 524 R.string.wallpaper_set_successfully_message, 525 Toast.LENGTH_SHORT).show(); 526 } catch (NotFoundException e) { 527 Log.e(TAG, "Could not show toast " + e); 528 } 529 530 activity.setResult(Activity.RESULT_OK); 531 activity.finish(); 532 if (!ActivityUtils.isSUWMode(appContext)) { 533 // Go back to launcher home. 534 LaunchUtils.launchHome(appContext); 535 } 536 } 537 } else { // Failed to start rotation. 538 showStartRotationErrorDialog(networkPreference); 539 } 540 } 541 542 @Override 543 public void onError() { 544 if (mProgressDialog != null) { 545 mProgressDialog.dismiss(); 546 } 547 548 showStartRotationErrorDialog(networkPreference); 549 } 550 }); 551 } 552 showStartRotationErrorDialog(@etworkPreference int networkPreference)553 private void showStartRotationErrorDialog(@NetworkPreference int networkPreference) { 554 FragmentTransactionChecker activity = (FragmentTransactionChecker) getActivity(); 555 if (activity != null) { 556 StartRotationErrorDialogFragment startRotationErrorDialogFragment = 557 StartRotationErrorDialogFragment.newInstance(networkPreference); 558 startRotationErrorDialogFragment.setTargetFragment( 559 IndividualPickerFragment.this, UNUSED_REQUEST_CODE); 560 561 if (activity.isSafeToCommitFragmentTransaction()) { 562 startRotationErrorDialogFragment.show( 563 getFragmentManager(), TAG_START_ROTATION_ERROR_DIALOG); 564 } else { 565 mStagedStartRotationErrorDialogFragment = startRotationErrorDialogFragment; 566 } 567 } 568 } 569 getNumColumns()570 int getNumColumns() { 571 Activity activity = getActivity(); 572 if (activity == null) { 573 return 1; 574 } 575 return isFewerColumnLayout() 576 ? SizeCalculator.getNumFeaturedIndividualColumns(activity) 577 : SizeCalculator.getNumIndividualColumns(activity); 578 } 579 580 /** 581 * Returns whether rotation is enabled for this category. 582 */ isRotationEnabled()583 boolean isRotationEnabled() { 584 return mWallpaperRotationInitializer != null; 585 } 586 587 @Override onMenuItemClick(MenuItem item)588 public boolean onMenuItemClick(MenuItem item) { 589 if (item.getItemId() == R.id.daily_rotation) { 590 showRotationDialog(); 591 return true; 592 } 593 return super.onMenuItemClick(item); 594 } 595 596 /** 597 * Popups a daily rotation dialog for the uses to confirm. 598 */ showRotationDialog()599 public void showRotationDialog() { 600 DialogFragment startRotationDialogFragment = new StartRotationDialogFragment(); 601 startRotationDialogFragment.setTargetFragment( 602 IndividualPickerFragment.this, UNUSED_REQUEST_CODE); 603 startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG); 604 } 605 getAppliedWallpaperIds()606 private Set<String> getAppliedWallpaperIds() { 607 WallpaperPreferences prefs = 608 InjectorProvider.getInjector().getPreferences(getContext()); 609 android.app.WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo(); 610 Set<String> appliedWallpaperIds = new ArraySet<>(); 611 612 String homeWallpaperId = wallpaperInfo != null ? wallpaperInfo.getServiceName() 613 : prefs.getHomeWallpaperRemoteId(); 614 if (!TextUtils.isEmpty(homeWallpaperId)) { 615 appliedWallpaperIds.add(homeWallpaperId); 616 } 617 618 boolean isLockWallpaperApplied = 619 mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) >= 0; 620 String lockWallpaperId = prefs.getLockWallpaperRemoteId(); 621 if (isLockWallpaperApplied && !TextUtils.isEmpty(lockWallpaperId)) { 622 appliedWallpaperIds.add(lockWallpaperId); 623 } 624 625 return appliedWallpaperIds; 626 } 627 628 /** 629 * RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView. 630 */ 631 class IndividualAdapter extends RecyclerView.Adapter<ViewHolder> { 632 static final int ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER = 2; 633 static final int ITEM_VIEW_TYPE_MY_PHOTOS = 3; 634 635 private final List<WallpaperInfo> mWallpapers; 636 IndividualAdapter(List<WallpaperInfo> wallpapers)637 IndividualAdapter(List<WallpaperInfo> wallpapers) { 638 mWallpapers = wallpapers; 639 } 640 641 @Override onCreateViewHolder(ViewGroup parent, int viewType)642 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 643 switch (viewType) { 644 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER: 645 return createIndividualHolder(parent); 646 case ITEM_VIEW_TYPE_MY_PHOTOS: 647 return createMyPhotosHolder(parent); 648 default: 649 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter"); 650 return null; 651 } 652 } 653 654 @Override getItemViewType(int position)655 public int getItemViewType(int position) { 656 // A category cannot have both a "start rotation" tile and a "my photos" tile. 657 if (mCategory.supportsCustomPhotos() 658 && !isRotationEnabled() 659 && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) { 660 return ITEM_VIEW_TYPE_MY_PHOTOS; 661 } 662 663 return ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER; 664 } 665 666 @Override onBindViewHolder(ViewHolder holder, int position)667 public void onBindViewHolder(ViewHolder holder, int position) { 668 int viewType = getItemViewType(position); 669 670 switch (viewType) { 671 case ITEM_VIEW_TYPE_INDIVIDUAL_WALLPAPER: 672 onBindIndividualHolder(holder, position); 673 break; 674 case ITEM_VIEW_TYPE_MY_PHOTOS: 675 ((MyPhotosViewHolder) holder).bind(); 676 break; 677 default: 678 Log.e(TAG, "Unsupported viewType " + viewType + " in IndividualAdapter"); 679 } 680 } 681 682 @Override getItemCount()683 public int getItemCount() { 684 return mCategory.supportsCustomPhotos() ? mWallpapers.size() + 1 : mWallpapers.size(); 685 } 686 createIndividualHolder(ViewGroup parent)687 private ViewHolder createIndividualHolder(ViewGroup parent) { 688 LayoutInflater layoutInflater = LayoutInflater.from(getActivity()); 689 View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false); 690 691 return new PreviewIndividualHolder(getActivity(), mTileSizePx.y, view); 692 } 693 createMyPhotosHolder(ViewGroup parent)694 private ViewHolder createMyPhotosHolder(ViewGroup parent) { 695 LayoutInflater layoutInflater = LayoutInflater.from(getActivity()); 696 View view = layoutInflater.inflate(R.layout.grid_item_my_photos, parent, false); 697 698 return new MyPhotosViewHolder(getActivity(), 699 ((MyPhotosStarterProvider) getActivity()).getMyPhotosStarter(), 700 mTileSizePx.y, view); 701 } 702 onBindIndividualHolder(ViewHolder holder, int position)703 void onBindIndividualHolder(ViewHolder holder, int position) { 704 int wallpaperIndex = mCategory.supportsCustomPhotos() ? position - 1 : position; 705 WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex); 706 wallpaper.computeColorInfo(holder.itemView.getContext()); 707 ((IndividualHolder) holder).bindWallpaper(wallpaper); 708 boolean isWallpaperApplied = isWallpaperApplied(wallpaper); 709 710 CardView container = holder.itemView.findViewById(R.id.wallpaper_container); 711 int radiusId = isFewerColumnLayout() ? R.dimen.grid_item_all_radius 712 : R.dimen.grid_item_all_radius_small; 713 container.setRadius(getResources().getDimension(radiusId)); 714 showBadge(holder, R.drawable.wallpaper_check_circle_24dp, isWallpaperApplied); 715 } 716 isWallpaperApplied(WallpaperInfo wallpaper)717 protected boolean isWallpaperApplied(WallpaperInfo wallpaper) { 718 return mAppliedWallpaperIds.contains(wallpaper.getWallpaperId()); 719 } 720 showBadge(ViewHolder holder, @DrawableRes int icon, boolean show)721 protected void showBadge(ViewHolder holder, @DrawableRes int icon, boolean show) { 722 ImageView badge = holder.itemView.findViewById(R.id.indicator_icon); 723 if (show) { 724 final float margin = isFewerColumnLayout() ? getResources().getDimension( 725 R.dimen.grid_item_badge_margin) : getResources().getDimension( 726 R.dimen.grid_item_badge_margin_small); 727 final RelativeLayout.LayoutParams layoutParams = 728 (RelativeLayout.LayoutParams) badge.getLayoutParams(); 729 layoutParams.setMargins(/* left= */ (int) margin, /* top= */ (int) margin, 730 /* right= */ (int) margin, /* bottom= */ (int) margin); 731 badge.setLayoutParams(layoutParams); 732 badge.setBackgroundResource(icon); 733 badge.setVisibility(View.VISIBLE); 734 } else { 735 badge.setVisibility(View.GONE); 736 } 737 } 738 } 739 } 740