1 /* 2 * Copyright (C) 2018 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.customization.picker.theme; 17 18 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 19 20 import android.app.Activity; 21 import android.app.WallpaperColors; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.res.ColorStateList; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.graphics.Bitmap.Config; 28 import android.graphics.drawable.BitmapDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.LayerDrawable; 31 import android.os.Bundle; 32 import android.util.Log; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.View.OnClickListener; 36 import android.view.View.OnLayoutChangeListener; 37 import android.view.ViewGroup; 38 import android.widget.CheckBox; 39 import android.widget.CompoundButton; 40 import android.widget.ImageView; 41 import android.widget.SeekBar; 42 import android.widget.Switch; 43 import android.widget.TextView; 44 import android.widget.Toast; 45 46 import androidx.annotation.NonNull; 47 import androidx.annotation.Nullable; 48 import androidx.core.widget.ContentLoadingProgressBar; 49 import androidx.recyclerview.widget.RecyclerView; 50 51 import com.android.customization.model.CustomizationManager.Callback; 52 import com.android.customization.model.CustomizationManager.OptionsFetchedListener; 53 import com.android.customization.model.theme.ThemeBundle; 54 import com.android.customization.model.theme.ThemeBundle.PreviewInfo; 55 import com.android.customization.model.theme.ThemeManager; 56 import com.android.customization.model.theme.custom.CustomTheme; 57 import com.android.customization.module.ThemesUserEventLogger; 58 import com.android.customization.picker.BasePreviewAdapter; 59 import com.android.customization.picker.TimeTicker; 60 import com.android.customization.picker.theme.ThemePreviewPage.ThemeCoverPage; 61 import com.android.customization.picker.theme.ThemePreviewPage.TimeContainer; 62 import com.android.customization.widget.OptionSelectorController; 63 import com.android.wallpaper.R; 64 import com.android.wallpaper.asset.Asset; 65 import com.android.wallpaper.asset.Asset.CenterCropBitmapTask; 66 import com.android.wallpaper.model.WallpaperInfo; 67 import com.android.wallpaper.module.CurrentWallpaperInfoFactory; 68 import com.android.wallpaper.module.InjectorProvider; 69 import com.android.wallpaper.picker.ToolbarFragment; 70 import com.android.wallpaper.widget.PreviewPager; 71 72 import java.util.List; 73 74 /** 75 * Fragment that contains the main UI for selecting and applying a ThemeBundle. 76 */ 77 public class ThemeFragment extends ToolbarFragment { 78 79 private static final String TAG = "ThemeFragment"; 80 private static final String KEY_SELECTED_THEME = "ThemeFragment.SelectedThemeBundle"; 81 82 /** 83 * Interface to be implemented by an Activity hosting a {@link ThemeFragment} 84 */ 85 public interface ThemeFragmentHost { getThemeManager()86 ThemeManager getThemeManager(); 87 } newInstance(CharSequence title)88 public static ThemeFragment newInstance(CharSequence title) { 89 ThemeFragment fragment = new ThemeFragment(); 90 fragment.setArguments(ToolbarFragment.createArguments(title)); 91 return fragment; 92 } 93 94 private RecyclerView mOptionsContainer; 95 private CheckBox mUseMyWallpaperButton; 96 private OptionSelectorController<ThemeBundle> mOptionsController; 97 private ThemeManager mThemeManager; 98 private ThemesUserEventLogger mEventLogger; 99 private ThemeBundle mSelectedTheme; 100 private ThemePreviewAdapter mAdapter; 101 private PreviewPager mPreviewPager; 102 private ContentLoadingProgressBar mLoading; 103 private View mContent; 104 private View mError; 105 private boolean mUseMyWallpaper; 106 private WallpaperInfo mCurrentHomeWallpaper; 107 private CurrentWallpaperInfoFactory mCurrentWallpaperFactory; 108 private TimeTicker mTicker; 109 110 @Override onAttach(Context context)111 public void onAttach(Context context) { 112 super.onAttach(context); 113 mThemeManager = ((ThemeFragmentHost) context).getThemeManager(); 114 mEventLogger = (ThemesUserEventLogger) 115 InjectorProvider.getInjector().getUserEventLogger(context); 116 } 117 118 @Nullable 119 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)120 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 121 @Nullable Bundle savedInstanceState) { 122 View view; 123 if (ADD_SCALABLE_HEADER) { 124 // TODO(b/147780560): Once the temporary flag (ADD_SCALABLE_HEADER) is removed, 125 // we should have a layout with the same name for portrait and landscape. 126 int orientation = getResources().getConfiguration().orientation; 127 view = inflater.inflate( 128 orientation == ORIENTATION_LANDSCAPE 129 ? R.layout.fragment_theme_picker 130 : R.layout.fragment_theme_scalable_picker, 131 container, /* attachToRoot */ false); 132 } else { 133 view = inflater.inflate( 134 R.layout.fragment_theme_picker, container, /* attachToRoot */ false); 135 } 136 setUpToolbar(view); 137 138 mContent = view.findViewById(R.id.content_section); 139 mLoading = view.findViewById(R.id.loading_indicator); 140 mError = view.findViewById(R.id.error_section); 141 mCurrentWallpaperFactory = InjectorProvider.getInjector() 142 .getCurrentWallpaperFactory(getActivity().getApplicationContext()); 143 mPreviewPager = view.findViewById(R.id.theme_preview_pager); 144 mOptionsContainer = view.findViewById(R.id.options_container); 145 view.findViewById(R.id.apply_button).setOnClickListener(v -> { 146 applyTheme(); 147 }); 148 mUseMyWallpaperButton = view.findViewById(R.id.use_my_wallpaper); 149 mUseMyWallpaperButton.setOnCheckedChangeListener(this::onUseMyWallpaperCheckChanged); 150 setUpOptions(savedInstanceState); 151 152 return view; 153 } 154 applyTheme()155 private void applyTheme() { 156 mThemeManager.apply(mSelectedTheme, new Callback() { 157 @Override 158 public void onSuccess() { 159 Toast.makeText(getContext(), R.string.applied_theme_msg, 160 Toast.LENGTH_LONG).show(); 161 getActivity().finish(); 162 getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out); 163 } 164 165 @Override 166 public void onError(@Nullable Throwable throwable) { 167 Log.w(TAG, "Error applying theme", throwable); 168 Toast.makeText(getContext(), R.string.apply_theme_error_msg, 169 Toast.LENGTH_LONG).show(); 170 } 171 }); 172 } 173 174 @Override onResume()175 public void onResume() { 176 super.onResume(); 177 mTicker = TimeTicker.registerNewReceiver(getContext(), this::updateTime); 178 reloadWallpaper(); 179 updateTime(); 180 } 181 updateTime()182 private void updateTime() { 183 if (mAdapter != null) { 184 mAdapter.updateTime(); 185 } 186 } 187 188 @Override onPause()189 public void onPause() { 190 super.onPause(); 191 if (getContext() != null) { 192 getContext().unregisterReceiver(mTicker); 193 } 194 } 195 196 @Override onSaveInstanceState(@onNull Bundle outState)197 public void onSaveInstanceState(@NonNull Bundle outState) { 198 super.onSaveInstanceState(outState); 199 if (mSelectedTheme != null && !mSelectedTheme.isActive(mThemeManager)) { 200 outState.putString(KEY_SELECTED_THEME, mSelectedTheme.getSerializedPackages()); 201 } 202 } 203 204 @Override onActivityResult(int requestCode, int resultCode, Intent data)205 public void onActivityResult(int requestCode, int resultCode, Intent data) { 206 if (requestCode == CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME) { 207 if (resultCode == CustomThemeActivity.RESULT_THEME_DELETED) { 208 mSelectedTheme = null; 209 reloadOptions(); 210 } else if (resultCode == CustomThemeActivity.RESULT_THEME_APPLIED) { 211 getActivity().finish(); 212 } else { 213 if (mSelectedTheme != null) { 214 mOptionsController.setSelectedOption(mSelectedTheme); 215 } else { 216 reloadOptions(); 217 } 218 } 219 } 220 super.onActivityResult(requestCode, resultCode, data); 221 } 222 onUseMyWallpaperCheckChanged(CompoundButton checkbox, boolean checked)223 private void onUseMyWallpaperCheckChanged(CompoundButton checkbox, boolean checked) { 224 mUseMyWallpaper = checked; 225 reloadWallpaper(); 226 } 227 reloadWallpaper()228 private void reloadWallpaper() { 229 mCurrentWallpaperFactory.createCurrentWallpaperInfos( 230 (homeWallpaper, lockWallpaper, presentationMode) -> { 231 mCurrentHomeWallpaper = homeWallpaper; 232 if (mSelectedTheme != null) { 233 if (mUseMyWallpaper || (mSelectedTheme instanceof CustomTheme)) { 234 mSelectedTheme.setOverrideThemeWallpaper(homeWallpaper); 235 } else { 236 mSelectedTheme.setOverrideThemeWallpaper(null); 237 } 238 if (mAdapter != null) { 239 mAdapter.rebindWallpaperIfAvailable(); 240 } 241 } 242 }, false); 243 } 244 createAdapter(List<ThemeBundle> options)245 private void createAdapter(List<ThemeBundle> options) { 246 mAdapter = new ThemePreviewAdapter(getActivity(), mSelectedTheme, 247 mSelectedTheme instanceof CustomTheme ? this::onEditClicked : null, 248 new PreloadWallpapersLayoutListener(options)); 249 mPreviewPager.setAdapter(mAdapter); 250 } 251 onEditClicked(View view)252 private void onEditClicked(View view) { 253 if (mSelectedTheme instanceof CustomTheme) { 254 navigateToCustomTheme((CustomTheme) mSelectedTheme); 255 } 256 } 257 updateButtonsVisibility()258 private void updateButtonsVisibility() { 259 mUseMyWallpaperButton.setVisibility(mSelectedTheme instanceof CustomTheme 260 ? View.INVISIBLE : View.VISIBLE); 261 } 262 hideError()263 private void hideError() { 264 mContent.setVisibility(View.VISIBLE); 265 mError.setVisibility(View.GONE); 266 } 267 showError()268 private void showError() { 269 mLoading.hide(); 270 mContent.setVisibility(View.GONE); 271 mError.setVisibility(View.VISIBLE); 272 } 273 setUpOptions(@ullable Bundle savedInstanceState)274 private void setUpOptions(@Nullable Bundle savedInstanceState) { 275 hideError(); 276 mLoading.show(); 277 mThemeManager.fetchOptions(new OptionsFetchedListener<ThemeBundle>() { 278 @Override 279 public void onOptionsLoaded(List<ThemeBundle> options) { 280 mOptionsController = new OptionSelectorController<>(mOptionsContainer, options); 281 mOptionsController.addListener(selected -> { 282 mLoading.hide(); 283 if (selected instanceof CustomTheme && !((CustomTheme) selected).isDefined()) { 284 navigateToCustomTheme((CustomTheme) selected); 285 } else { 286 mSelectedTheme = (ThemeBundle) selected; 287 if (mUseMyWallpaper || mSelectedTheme instanceof CustomTheme) { 288 mSelectedTheme.setOverrideThemeWallpaper(mCurrentHomeWallpaper); 289 } else { 290 mSelectedTheme.setOverrideThemeWallpaper(null); 291 } 292 mEventLogger.logThemeSelected(mSelectedTheme, 293 selected instanceof CustomTheme); 294 createAdapter(options); 295 updateButtonsVisibility(); 296 } 297 }); 298 mOptionsController.initOptions(mThemeManager); 299 String previouslySelected = savedInstanceState != null 300 ? savedInstanceState.getString(KEY_SELECTED_THEME) : null; 301 for (ThemeBundle theme : options) { 302 if (previouslySelected != null 303 && previouslySelected.equals(theme.getSerializedPackages())) { 304 mSelectedTheme = theme; 305 } else if (theme.isActive(mThemeManager)) { 306 mSelectedTheme = theme; 307 break; 308 } 309 } 310 if (mSelectedTheme == null) { 311 // Select the default theme if there is no matching custom enabled theme 312 // TODO(b/124796742): default to custom if there is no matching theme bundle 313 mSelectedTheme = options.get(0); 314 } else { 315 // Only show show checkmark if we found a matching theme 316 mOptionsController.setAppliedOption(mSelectedTheme); 317 } 318 mOptionsController.setSelectedOption(mSelectedTheme); 319 } 320 @Override 321 public void onError(@Nullable Throwable throwable) { 322 if (throwable != null) { 323 Log.e(TAG, "Error loading theme bundles", throwable); 324 } 325 showError(); 326 } 327 }, false); 328 } 329 reloadOptions()330 private void reloadOptions() { 331 mThemeManager.fetchOptions(options -> { 332 mOptionsController.resetOptions(options); 333 for (ThemeBundle theme : options) { 334 if (theme.isActive(mThemeManager)) { 335 mSelectedTheme = theme; 336 break; 337 } 338 } 339 if (mSelectedTheme == null) { 340 // Select the default theme if there is no matching custom enabled theme 341 // TODO(b/124796742): default to custom if there is no matching theme bundle 342 mSelectedTheme = options.get(0); 343 } else { 344 // Only show show checkmark if we found a matching theme 345 mOptionsController.setAppliedOption(mSelectedTheme); 346 } 347 mOptionsController.setSelectedOption(mSelectedTheme); 348 }, true); 349 } 350 navigateToCustomTheme(CustomTheme themeToEdit)351 private void navigateToCustomTheme(CustomTheme themeToEdit) { 352 Intent intent = new Intent(getActivity(), CustomThemeActivity.class); 353 intent.putExtra(CustomThemeActivity.EXTRA_THEME_TITLE, themeToEdit.getTitle()); 354 intent.putExtra(CustomThemeActivity.EXTRA_THEME_ID, themeToEdit.getId()); 355 intent.putExtra(CustomThemeActivity.EXTRA_THEME_PACKAGES, 356 themeToEdit.getSerializedPackages()); 357 startActivityForResult(intent, CustomThemeActivity.REQUEST_CODE_CUSTOM_THEME); 358 } 359 360 /** 361 * Adapter class for mPreviewPager. 362 * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe, 363 * we don't want to just scroll) 364 */ 365 private static class ThemePreviewAdapter extends BasePreviewAdapter<ThemePreviewPage> { 366 367 private int[] mIconIds = { 368 R.id.preview_icon_0, R.id.preview_icon_1, R.id.preview_icon_2, R.id.preview_icon_3, 369 R.id.preview_icon_4, R.id.preview_icon_5 370 }; 371 private int[] mColorButtonIds = { 372 R.id.preview_check_selected, R.id.preview_radio_selected, R.id.preview_toggle_selected 373 }; 374 private int[] mColorTileIds = { 375 R.id.preview_color_qs_0_bg, R.id.preview_color_qs_1_bg, R.id.preview_color_qs_2_bg 376 }; 377 private int[][] mColorTileIconIds = { 378 new int[]{ R.id.preview_color_qs_0_icon, 0}, 379 new int[]{ R.id.preview_color_qs_1_icon, 1}, 380 new int[] { R.id.preview_color_qs_2_icon, 3} 381 }; 382 383 private int[] mShapeIconIds = { 384 R.id.shape_preview_icon_0, R.id.shape_preview_icon_1, R.id.shape_preview_icon_2, 385 R.id.shape_preview_icon_3, R.id.shape_preview_icon_4, R.id.shape_preview_icon_5 386 }; 387 ThemePreviewAdapter(Activity activity, ThemeBundle theme, @Nullable OnClickListener editClickListener, @Nullable OnLayoutChangeListener coverCardLayoutListener)388 ThemePreviewAdapter(Activity activity, ThemeBundle theme, 389 @Nullable OnClickListener editClickListener, 390 @Nullable OnLayoutChangeListener coverCardLayoutListener) { 391 super(activity, R.layout.theme_preview_card); 392 final Resources res = activity.getResources(); 393 final PreviewInfo previewInfo = theme.getPreviewInfo(); 394 395 Drawable coverScrim = theme instanceof CustomTheme 396 ? res.getDrawable(R.drawable.theme_cover_scrim, activity.getTheme()) 397 : null; 398 399 WallpaperPreviewLayoutListener wallpaperListener = new WallpaperPreviewLayoutListener( 400 theme, previewInfo, coverScrim, true); 401 402 addPage(new ThemeCoverPage(activity, theme.getTitle(), 403 previewInfo.resolveAccentColor(res), previewInfo.icons, 404 previewInfo.headlineFontFamily, previewInfo.bottomSheeetCornerRadius, 405 previewInfo.shapeDrawable, previewInfo.shapeAppIcons, editClickListener, 406 mColorButtonIds, mColorTileIds, mColorTileIconIds, mShapeIconIds, 407 wallpaperListener, coverCardLayoutListener)); 408 addPage(new ThemePreviewPage(activity, R.string.preview_name_font, R.drawable.ic_font, 409 R.layout.preview_card_font_content, 410 previewInfo.resolveAccentColor(res)) { 411 @Override 412 protected void bindBody(boolean forceRebind) { 413 TextView title = card.findViewById(R.id.font_card_title); 414 title.setTypeface(previewInfo.headlineFontFamily); 415 TextView body = card.findViewById(R.id.font_card_body); 416 body.setTypeface(previewInfo.bodyFontFamily); 417 card.findViewById(R.id.font_card_divider).setBackgroundColor(accentColor); 418 } 419 }); 420 if (previewInfo.icons.size() >= mIconIds.length) { 421 addPage(new ThemePreviewPage(activity, R.string.preview_name_icon, 422 R.drawable.ic_wifi_24px, R.layout.preview_card_icon_content, 423 previewInfo.resolveAccentColor(res)) { 424 @Override 425 protected void bindBody(boolean forceRebind) { 426 for (int i = 0; i < mIconIds.length && i < previewInfo.icons.size(); i++) { 427 ((ImageView) card.findViewById(mIconIds[i])) 428 .setImageDrawable(previewInfo.icons.get(i) 429 .getConstantState().newDrawable().mutate()); 430 } 431 } 432 }); 433 } 434 if (previewInfo.colorAccentDark != -1 && previewInfo.colorAccentLight != -1) { 435 addPage(new ThemePreviewPage(activity, R.string.preview_name_color, 436 R.drawable.ic_colorize_24px, R.layout.preview_card_color_content, 437 previewInfo.resolveAccentColor(res)) { 438 @Override 439 protected void bindBody(boolean forceRebind) { 440 int controlGreyColor = res.getColor(R.color.control_grey); 441 ColorStateList tintList = new ColorStateList( 442 new int[][]{ 443 new int[]{android.R.attr.state_selected}, 444 new int[]{android.R.attr.state_checked}, 445 new int[]{-android.R.attr.state_enabled}, 446 }, 447 new int[] { 448 accentColor, 449 accentColor, 450 controlGreyColor 451 } 452 ); 453 454 for (int i = 0; i < mColorButtonIds.length; i++) { 455 CompoundButton button = card.findViewById(mColorButtonIds[i]); 456 button.setButtonTintList(tintList); 457 } 458 459 Switch enabledSwitch = card.findViewById(R.id.preview_toggle_selected); 460 enabledSwitch.setThumbTintList(tintList); 461 enabledSwitch.setTrackTintList(tintList); 462 463 ColorStateList seekbarTintList = ColorStateList.valueOf(accentColor); 464 SeekBar seekbar = card.findViewById(R.id.preview_seekbar); 465 seekbar.setThumbTintList(seekbarTintList); 466 seekbar.setProgressTintList(seekbarTintList); 467 seekbar.setProgressBackgroundTintList(seekbarTintList); 468 // Disable seekbar 469 seekbar.setOnTouchListener((view, motionEvent) -> true); 470 471 int iconFgColor = res.getColor(R.color.tile_enabled_icon_color, null); 472 for (int i = 0; i < mColorTileIds.length && i < previewInfo.icons.size(); 473 i++) { 474 Drawable icon = previewInfo.icons.get(mColorTileIconIds[i][1]) 475 .getConstantState().newDrawable().mutate(); 476 icon.setTint(iconFgColor); 477 Drawable bgShape = 478 previewInfo.shapeDrawable.getConstantState().newDrawable(); 479 bgShape.setTint(accentColor); 480 481 ImageView bg = card.findViewById(mColorTileIds[i]); 482 bg.setImageDrawable(bgShape); 483 ImageView fg = card.findViewById(mColorTileIconIds[i][0]); 484 fg.setImageDrawable(icon); 485 } 486 } 487 }); 488 } 489 if (!previewInfo.shapeAppIcons.isEmpty()) { 490 addPage(new ThemePreviewPage(activity, R.string.preview_name_shape, 491 R.drawable.ic_shapes_24px, R.layout.preview_card_shape_content, 492 previewInfo.resolveAccentColor(res)) { 493 @Override 494 protected void bindBody(boolean forceRebind) { 495 for (int i = 0; i < mShapeIconIds.length 496 && i < previewInfo.shapeAppIcons.size(); i++) { 497 ImageView iconView = card.findViewById(mShapeIconIds[i]); 498 iconView.setBackground( 499 previewInfo.shapeAppIcons.get(i)); 500 } 501 } 502 }); 503 } 504 if (previewInfo.wallpaperAsset != null) { 505 addPage(new ThemePreviewPage(activity, R.string.preview_name_wallpaper, 506 R.drawable.ic_nav_wallpaper, R.layout.preview_card_wallpaper_content, 507 previewInfo.resolveAccentColor(res)) { 508 509 private final WallpaperPreviewLayoutListener mListener = 510 new WallpaperPreviewLayoutListener(theme, previewInfo, null, false); 511 512 @Override 513 protected boolean containsWallpaper() { 514 return true; 515 } 516 517 @Override 518 protected void bindBody(boolean forceRebind) { 519 if (card == null) { 520 return; 521 } 522 card.addOnLayoutChangeListener(mListener); 523 if (forceRebind) { 524 card.requestLayout(); 525 } 526 } 527 }); 528 } 529 } 530 rebindWallpaperIfAvailable()531 public void rebindWallpaperIfAvailable() { 532 for (ThemePreviewPage page : mPages) { 533 if (page.containsWallpaper()) { 534 page.bindBody(true); 535 } 536 } 537 } 538 updateTime()539 public void updateTime() { 540 for (ThemePreviewPage page : mPages) { 541 if (page instanceof TimeContainer) { 542 ((TimeContainer)page).updateTime(); 543 } 544 } 545 } 546 547 private static class WallpaperPreviewLayoutListener implements OnLayoutChangeListener { 548 private final ThemeBundle mTheme; 549 private final PreviewInfo mPreviewInfo; 550 private final Drawable mScrim; 551 private final boolean mIsTranslucent; 552 WallpaperPreviewLayoutListener(ThemeBundle theme, PreviewInfo previewInfo, Drawable scrim, boolean translucent)553 public WallpaperPreviewLayoutListener(ThemeBundle theme, PreviewInfo previewInfo, 554 Drawable scrim, boolean translucent) { 555 mTheme = theme; 556 mPreviewInfo = previewInfo; 557 mScrim = scrim; 558 mIsTranslucent = translucent; 559 } 560 561 @Override onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)562 public void onLayoutChange(View view, int left, int top, int right, 563 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 564 int targetWidth = right - left; 565 int targetHeight = bottom - top; 566 if (targetWidth > 0 && targetHeight > 0) { 567 Asset wallpaperPreviewAsset = mTheme.getWallpaperPreviewAsset( 568 view.getContext()); 569 if (wallpaperPreviewAsset != null) { 570 wallpaperPreviewAsset.decodeBitmap( 571 targetWidth, targetHeight, 572 bitmap -> new CenterCropBitmapTask(bitmap, view, 573 croppedBitmap -> setWallpaperBitmap(view, croppedBitmap)) 574 .execute()); 575 } 576 view.removeOnLayoutChangeListener(this); 577 } 578 } 579 setWallpaperBitmap(View view, Bitmap bitmap)580 private void setWallpaperBitmap(View view, Bitmap bitmap) { 581 Resources res = view.getContext().getResources(); 582 Drawable background = new BitmapDrawable(res, bitmap); 583 if (mIsTranslucent) { 584 background.setAlpha(ThemeCoverPage.COVER_PAGE_WALLPAPER_ALPHA); 585 } 586 if (mScrim != null) { 587 background = new LayerDrawable(new Drawable[]{background, mScrim}); 588 } 589 view.findViewById(R.id.theme_preview_card_background).setBackground(background); 590 if (mScrim == null && !mIsTranslucent) { 591 boolean shouldRecycle = false; 592 if (bitmap.getConfig() == Config.HARDWARE) { 593 bitmap = bitmap.copy(Config.ARGB_8888, false); 594 shouldRecycle = true; 595 } 596 int colorsHint = WallpaperColors.fromBitmap(bitmap).getColorHints(); 597 if (shouldRecycle) { 598 bitmap.recycle(); 599 } 600 TextView header = view.findViewById(R.id.theme_preview_card_header); 601 if ((colorsHint & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0) { 602 int colorLight = res.getColor(R.color.text_color_light, null); 603 header.setTextColor(colorLight); 604 header.setCompoundDrawableTintList(ColorStateList.valueOf(colorLight)); 605 } else { 606 header.setTextColor(res.getColor(R.color.text_color_dark, null)); 607 header.setCompoundDrawableTintList(ColorStateList.valueOf( 608 mPreviewInfo.colorAccentLight)); 609 } 610 } 611 } 612 } 613 } 614 615 /** 616 * Runs only once after the card size is known, and requests decoding wallpaper bitmaps 617 * for all the options, to warm-up the bitmap cache. 618 */ 619 private static class PreloadWallpapersLayoutListener implements OnLayoutChangeListener { 620 private static boolean alreadyRunOnce; 621 private final List<ThemeBundle> mOptions; 622 PreloadWallpapersLayoutListener(List<ThemeBundle> options)623 public PreloadWallpapersLayoutListener(List<ThemeBundle> options) { 624 mOptions = options; 625 } 626 627 @Override onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)628 public void onLayoutChange(View view, int left, int top, int right, 629 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 630 if (alreadyRunOnce) { 631 view.removeOnLayoutChangeListener(this); 632 return; 633 } 634 int targetWidth = right - left; 635 int targetHeight = bottom - top; 636 if (targetWidth > 0 && targetHeight > 0) { 637 for (ThemeBundle theme : mOptions) { 638 if (theme instanceof CustomTheme && !((CustomTheme) theme).isDefined()) { 639 continue; 640 } 641 Asset wallpaperAsset = theme.getWallpaperPreviewAsset(view.getContext()); 642 if (wallpaperAsset != null) { 643 wallpaperAsset.decodeBitmap(targetWidth, targetHeight, bitmap -> {}); 644 } 645 } 646 view.removeOnLayoutChangeListener(this); 647 alreadyRunOnce = true; 648 } 649 } 650 } 651 } 652