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