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