1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wallpaper.picker;
17 
18 import static android.stats.style.StyleEnums.SET_WALLPAPER_ENTRY_POINT_WALLPAPER_PREVIEW;
19 import static android.view.View.MeasureSpec.EXACTLY;
20 import static android.view.View.MeasureSpec.makeMeasureSpec;
21 
22 import static com.android.wallpaper.util.WallpaperSurfaceCallback.LOW_RES_BITMAP_BLUR_RADIUS;
23 
24 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
25 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN;
26 
27 import android.animation.Animator;
28 import android.animation.AnimatorListenerAdapter;
29 import android.app.Activity;
30 import android.app.WallpaperManager;
31 import android.content.Context;
32 import android.content.res.Resources;
33 import android.graphics.Bitmap;
34 import android.graphics.Color;
35 import android.graphics.Point;
36 import android.graphics.PointF;
37 import android.graphics.Rect;
38 import android.graphics.RenderEffect;
39 import android.graphics.Shader;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.util.Log;
43 import android.view.LayoutInflater;
44 import android.view.Surface;
45 import android.view.SurfaceControlViewHost;
46 import android.view.SurfaceHolder;
47 import android.view.SurfaceView;
48 import android.view.View;
49 import android.view.ViewGroup;
50 import android.view.ViewGroup.LayoutParams;
51 import android.view.animation.Interpolator;
52 import android.view.animation.PathInterpolator;
53 import android.widget.ImageView;
54 
55 import androidx.annotation.NonNull;
56 import androidx.annotation.Nullable;
57 import androidx.annotation.VisibleForTesting;
58 
59 import com.android.wallpaper.R;
60 import com.android.wallpaper.asset.Asset;
61 import com.android.wallpaper.asset.CurrentWallpaperAsset;
62 import com.android.wallpaper.model.SetWallpaperViewModel;
63 import com.android.wallpaper.model.WallpaperInfo.ColorInfo;
64 import com.android.wallpaper.module.BitmapCropper;
65 import com.android.wallpaper.module.Injector;
66 import com.android.wallpaper.module.InjectorProvider;
67 import com.android.wallpaper.module.WallpaperPersister.Destination;
68 import com.android.wallpaper.module.WallpaperPreferences;
69 import com.android.wallpaper.util.DisplayUtils;
70 import com.android.wallpaper.util.OnFullResImageViewStateChangedListener;
71 import com.android.wallpaper.util.ResourceUtils;
72 import com.android.wallpaper.util.RtlUtils;
73 import com.android.wallpaper.util.ScreenSizeCalculator;
74 import com.android.wallpaper.util.WallpaperColorsExtractor;
75 import com.android.wallpaper.util.WallpaperCropUtils;
76 
77 import com.bumptech.glide.Glide;
78 import com.bumptech.glide.MemoryCategory;
79 import com.davemorrissey.labs.subscaleview.ImageSource;
80 import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
81 import com.google.android.material.bottomsheet.BottomSheetBehavior;
82 
83 import java.util.concurrent.ExecutionException;
84 import java.util.concurrent.Executor;
85 import java.util.concurrent.Executors;
86 import java.util.concurrent.Future;
87 
88 /**
89  * Fragment which displays the UI for previewing an individual static image wallpaper and its
90  * attribution information.
91  */
92 public class ImagePreviewFragment extends PreviewFragment {
93 
94     private static final String TAG = "ImagePreviewFragment";
95 
96     private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f;
97     private static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
98     private static final Executor sExecutor = Executors.newCachedThreadPool();
99 
100     private final WallpaperSurfaceCallback mWallpaperSurfaceCallback =
101             new WallpaperSurfaceCallback();
102     private final Injector mInjector = InjectorProvider.getInjector();
103 
104     /**
105      * Size of the screen considered for cropping the wallpaper (typically the same as
106      * {@link #mScreenSize} but it could be different on multi-display)
107      */
108     private Point mWallpaperScreenSize;
109     /**
110      * The size of the current screen
111      */
112     private Point mScreenSize;
113     protected Point mRawWallpaperSize; // Native size of wallpaper image.
114     private WallpaperPreferences mWallpaperPreferences;
115     protected Asset mWallpaperAsset;
116     protected Future<ColorInfo> mColorFuture;
117     private WallpaperPreviewBitmapTransformation mPreviewBitmapTransformation;
118     private BitmapCropper mBitmapCropper;
119     private WallpaperColorsExtractor mWallpaperColorsExtractor;
120     private DisplayUtils mDisplayUtils;
121 
122     // UI
123     protected SurfaceView mWallpaperSurface;
124     protected ImageView mLowResImageView;
125     protected SubsamplingScaleImageView mFullResImageView;
126 
127     @Override
onCreate(Bundle savedInstanceState)128     public void onCreate(Bundle savedInstanceState) {
129         super.onCreate(savedInstanceState);
130         Context context = requireContext();
131         Context appContext = context.getApplicationContext();
132         mWallpaperAsset = mWallpaper.getAsset(appContext);
133         mColorFuture = mWallpaper.computeColorInfo(context);
134         mWallpaperPreferences = mInjector.getPreferences(context);
135         mPreviewBitmapTransformation = new WallpaperPreviewBitmapTransformation(
136                 appContext, RtlUtils.isRtl(context));
137         mBitmapCropper = mInjector.getBitmapCropper();
138         mWallpaperColorsExtractor = new WallpaperColorsExtractor(sExecutor, Handler.getMain());
139     }
140 
141     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)142     public View onCreateView(LayoutInflater inflater, ViewGroup container,
143             Bundle savedInstanceState) {
144         View view = super.onCreateView(inflater, container, savedInstanceState);
145         if (view == null) {
146             return null;
147         }
148         // Until we have initialized mRawWallpaperSize, we can't set wallpaper
149         mSetWallpaperButton.setEnabled(false);
150         mSetWallpaperButtonContainer.setEnabled(false);
151         Activity activity = requireActivity();
152         mDisplayUtils = mInjector.getDisplayUtils(activity);
153         ScreenSizeCalculator screenSizeCalculator = ScreenSizeCalculator.getInstance();
154         mScreenSize = screenSizeCalculator.getScreenSize(
155                 activity.getWindowManager().getDefaultDisplay());
156         // "Wallpaper screen" size will be the size of the largest screen available
157         mWallpaperScreenSize = screenSizeCalculator.getScreenSize(
158                 mDisplayUtils.getWallpaperDisplay());
159         // Touch forwarding layout
160         setUpTouchForwardingLayout();
161         // Wallpaper surface
162         mWallpaperSurface = view.findViewById(R.id.wallpaper_surface);
163         mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
164         // Trim memory from Glide to make room for the full-size image in this fragment.
165         Glide.get(activity).setMemoryCategory(MemoryCategory.LOW);
166         return view;
167     }
168 
169     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
getFullResImageView()170     public SubsamplingScaleImageView getFullResImageView() {
171         return mFullResImageView;
172     }
173 
setUpTouchForwardingLayout()174     private void setUpTouchForwardingLayout() {
175         mTouchForwardingLayout.setForwardingEnabled(true);
176         mTouchForwardingLayout.setOnClickListener(v -> {
177             toggleWallpaperPreviewControl();
178             mTouchForwardingLayout.announceForAccessibility(
179                     getString(mPreviewScrim.getVisibility() == View.VISIBLE
180                             ? R.string.show_preview_controls_content_description
181                             : R.string.hide_preview_controls_content_description)
182             );
183         });
184         mFloatingSheet.addFloatingSheetCallback(
185                 new BottomSheetBehavior.BottomSheetCallback() {
186                     @Override
187                     public void onStateChanged(@NonNull View bottomSheet, int newState) {
188                         if (newState == STATE_EXPANDED) {
189                             mTouchForwardingLayout.setForwardingEnabled(false);
190                         } else if (newState == STATE_HIDDEN) {
191                             mTouchForwardingLayout.setForwardingEnabled(true);
192                         }
193                     }
194 
195                     @Override
196                     public void onSlide(@NonNull View bottomSheet, float slideOffset) {
197                     }
198                 });
199     }
200 
201     @Override
onDestroy()202     public void onDestroy() {
203         if (mFullResImageView != null) {
204             mFullResImageView.recycle();
205         }
206         mWallpaperSurfaceCallback.cleanUp();
207         super.onDestroy();
208     }
209 
210     @Override
setWallpaper(@estination int destination)211     protected void setWallpaper(@Destination int destination) {
212         Context context = getContext();
213         if (context == null) {
214             return;
215         }
216         if (mRawWallpaperSize == null) {
217             // This shouldn't happen, avoid direct call into setWallpaper without initializing
218             // mRawWallpaperSize first
219             showSetWallpaperErrorDialog();
220             return;
221         }
222         // Only crop extra wallpaper width for single display devices.
223         Rect cropRect = calculateCropRect(context, !mDisplayUtils.hasMultiInternalDisplays());
224         float screenScale = WallpaperCropUtils.getScaleOfScreenResolution(
225                 mFullResImageView.getScale(), cropRect, mWallpaperScreenSize.x,
226                 mWallpaperScreenSize.y);
227         Rect scaledCropRect = new Rect(
228                 Math.round((float) cropRect.left * screenScale),
229                 Math.round((float) cropRect.top * screenScale),
230                 Math.round((float) cropRect.right * screenScale),
231                 Math.round((float) cropRect.bottom * screenScale));
232         mWallpaperSetter.setCurrentWallpaper(
233                 getActivity(),
234                 mWallpaper,
235                 mWallpaperAsset,
236                 SET_WALLPAPER_ENTRY_POINT_WALLPAPER_PREVIEW,
237                 destination,
238                 mFullResImageView.getScale() * screenScale,
239                 scaledCropRect,
240                 mWallpaperColors,
241                 SetWallpaperViewModel.getCallback(mViewModelProvider));
242     }
243 
244     /**
245      * Initializes image view by initializing tiling, setting a fallback page bitmap, and
246      * initializing a zoom-scroll observer and click listener.
247      */
initFullResView()248     private synchronized void initFullResView() {
249         if (mRawWallpaperSize == null || mFullResImageView == null
250                 || mFullResImageView.isImageLoaded()) {
251             return;
252         }
253 
254         final String storedWallpaperId = mWallpaper.getStoredWallpaperId(getContext());
255         final boolean isWallpaperColorCached =
256                 storedWallpaperId != null && mWallpaperPreferences.getWallpaperColors(
257                         storedWallpaperId) != null;
258         if (isWallpaperColorCached) {
259             // Post-execute onWallpaperColorsChanged() to avoid UI blocking from the call
260             Handler.getMain().post(() -> onWallpaperColorsChanged(
261                     mWallpaperPreferences.getWallpaperColors(
262                             mWallpaper.getStoredWallpaperId(getContext()))));
263         }
264 
265         // Minimum scale will only be respected under this scale type.
266         mFullResImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM);
267         // When we set a minimum scale bigger than the scale with which the full image is shown,
268         // disallow user to pan outside the view we show the wallpaper in.
269         mFullResImageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
270 
271         Point targetPageBitmapSize = new Point(mRawWallpaperSize);
272         mWallpaperAsset.decodeBitmap(targetPageBitmapSize.x, targetPageBitmapSize.y,
273                 pageBitmap -> {
274                     if (getActivity() == null || mFullResImageView == null) {
275                         return;
276                     }
277 
278                     if (pageBitmap == null) {
279                         showLoadWallpaperErrorDialog();
280                         return;
281                     }
282 
283                     mFullResImageView.setImage(ImageSource.bitmap(pageBitmap));
284                     setDefaultWallpaperZoomAndScroll(
285                             mWallpaperAsset instanceof CurrentWallpaperAsset);
286                     mFullResImageView.setOnStateChangedListener(
287                             new OnFullResImageViewStateChangedListener() {
288                                 @Override
289                                 public void onDebouncedCenterChanged(PointF newCenter, int origin) {
290                                     recalculateColors();
291                                 }
292                             }
293                     );
294                     if (!isWallpaperColorCached) {
295                         mFullResImageView.setAlpha(0);
296                         // If not cached, delay the cross fade until the colors extracted
297                         extractColorFromBitmap(pageBitmap, true);
298                     } else {
299                         onSurfaceReady();
300                     }
301                 });
302     }
303 
304     /**
305      * Recalculate the color from a new crop of the wallpaper. Note that we do not cache the
306      * extracted. We only cache the color the first time we extract from the wallpaper as its
307      * original size.
308      */
recalculateColors()309     private void recalculateColors() {
310         Context context = getContext();
311         if (context == null) {
312             return;
313         }
314 
315         mBitmapCropper.cropAndScaleBitmap(mWallpaperAsset, mFullResImageView.getScale(),
316                 calculateCropRect(context, /* cropExtraWidth= */ true), /* adjustForRtl= */ false,
317                 new BitmapCropper.Callback() {
318                     @Override
319                     public void onBitmapCropped(Bitmap croppedBitmap) {
320                         extractColorFromBitmap(croppedBitmap, false);
321                     }
322 
323                     @Override
324                     public void onError(@Nullable Throwable e) {
325                         Log.w(TAG, "Recalculate colors, crop and scale bitmap failed.", e);
326                     }
327                 });
328     }
329 
extractColorFromBitmap(Bitmap croppedBitmap, boolean cacheColor)330     private void extractColorFromBitmap(Bitmap croppedBitmap, boolean cacheColor) {
331         Context context = getContext();
332         if (context == null) {
333             return;
334         }
335 
336         mWallpaperColorsExtractor.extractWallpaperColors(croppedBitmap,
337                 colors -> {
338                     if (mFullResImageView.getAlpha() == 0) {
339                         onSurfaceReady();
340                     }
341                     onWallpaperColorsChanged(colors);
342                     if (cacheColor) {
343                         mWallpaperPreferences.storeWallpaperColors(
344                                 mWallpaper.getStoredWallpaperId(context), colors);
345                     }
346                 });
347     }
348 
349     /**
350      * This should be called when the full resolution image is loaded and the wallpaper color is
351      * ready, either extracted from the wallpaper or retrieved from cache.
352      */
onSurfaceReady()353     private void onSurfaceReady() {
354         mProgressBar.setVisibility(View.GONE);
355         crossFadeInFullResView();
356         // Set button enabled for the visual change
357         mSetWallpaperButton.setEnabled(true);
358         // Set button container enabled to make it clickable
359         mSetWallpaperButtonContainer.setEnabled(true);
360     }
361 
362     /**
363      * Fade in the full resolution view.
364      */
crossFadeInFullResView()365     protected void crossFadeInFullResView() {
366         if (getActivity() == null || !isAdded()) {
367             return;
368         }
369         long shortAnimationDuration = getResources().getInteger(
370                 android.R.integer.config_shortAnimTime);
371 
372         mFullResImageView.setAlpha(0f);
373         mFullResImageView.animate()
374                 .alpha(1f)
375                 .setInterpolator(ALPHA_OUT)
376                 .setDuration(shortAnimationDuration)
377                 .setListener(new AnimatorListenerAdapter() {
378                     @Override
379                     public void onAnimationEnd(Animator animation) {
380                         if (mLowResImageView != null) {
381                             mLowResImageView.setImageBitmap(null);
382                         }
383                     }
384                 });
385     }
386 
387     /**
388      * Sets the default wallpaper zoom and scroll position based on a "crop surface" (with extra
389      * width to account for parallax) superimposed on the screen. Shows as much of the wallpaper as
390      * possible on the crop surface and align screen to crop surface such that the default preview
391      * matches what would be seen by the user in the left-most home screen.
392      *
393      * <p>This method is called once in the Fragment lifecycle after the wallpaper asset has loaded
394      * and rendered to the layout.
395      *
396      * @param offsetToStart {@code true} if we want to offset the visible rectangle to the start
397      *                      side of the raw wallpaper; {@code false} otherwise.
398      */
setDefaultWallpaperZoomAndScroll(boolean offsetToStart)399     private void setDefaultWallpaperZoomAndScroll(boolean offsetToStart) {
400         // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface.
401         int cropWidth = mWallpaperSurface.getMeasuredWidth();
402         int cropHeight = mWallpaperSurface.getMeasuredHeight();
403         Point crop = new Point(cropWidth, cropHeight);
404         Rect visibleRawWallpaperRect =
405                 WallpaperCropUtils.calculateVisibleRect(mRawWallpaperSize, crop);
406         if (offsetToStart && mDisplayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(
407                 requireActivity())) {
408             if (RtlUtils.isRtl(requireContext())) {
409                 visibleRawWallpaperRect.offsetTo(mRawWallpaperSize.x
410                         - visibleRawWallpaperRect.width(), visibleRawWallpaperRect.top);
411             } else {
412                 visibleRawWallpaperRect.offsetTo(/* newLeft= */ 0, visibleRawWallpaperRect.top);
413             }
414         }
415 
416         final PointF centerPosition = new PointF(visibleRawWallpaperRect.centerX(),
417                 visibleRawWallpaperRect.centerY());
418 
419         Point visibleRawWallpaperSize = new Point(visibleRawWallpaperRect.width(),
420                 visibleRawWallpaperRect.height());
421 
422         final float defaultWallpaperZoom = WallpaperCropUtils.calculateMinZoom(
423                 visibleRawWallpaperSize, crop);
424 
425         // Set min wallpaper zoom and max zoom for the full resolution image view
426         mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom));
427         mFullResImageView.setMinScale(defaultWallpaperZoom);
428 
429         // Set center to composite positioning between scaled wallpaper and screen
430         mFullResImageView.setScaleAndCenter(defaultWallpaperZoom, centerPosition);
431     }
432 
calculateCropRect(Context context, boolean cropExtraWidth)433     private Rect calculateCropRect(Context context, boolean cropExtraWidth) {
434         float wallpaperZoom = mFullResImageView.getScale();
435         Context appContext = context.getApplicationContext();
436 
437         Rect visibleFileRect = new Rect();
438         mFullResImageView.visibleFileRect(visibleFileRect);
439 
440         int cropWidth = mWallpaperSurface.getMeasuredWidth();
441         int cropHeight = mWallpaperSurface.getMeasuredHeight();
442         int maxCrop = Math.max(cropWidth, cropHeight);
443         int minCrop = Math.min(cropWidth, cropHeight);
444         Point hostViewSize = new Point(cropWidth, cropHeight);
445 
446         Resources res = appContext.getResources();
447         Point cropSurfaceSize = WallpaperCropUtils.calculateCropSurfaceSize(res, maxCrop, minCrop,
448                 cropWidth, cropHeight);
449         Rect result = WallpaperCropUtils.calculateCropRect(appContext, hostViewSize,
450                 cropSurfaceSize, mRawWallpaperSize, visibleFileRect, wallpaperZoom, cropExtraWidth);
451 
452         // Cancel the rescaling in the multi crop case. In that case the crop will be sent to
453         // WallpaperManager. WallpaperManager expects a crop that is not yet rescaled to match
454         // the screen size (as opposed to BitmapCropper which is used in the single crop case).
455         // TODO(b/270726737, b/281648899) clean that comment and that part of the code
456         if (WallpaperManager.isMultiCropEnabled()) result.scale(1f / mFullResImageView.getScale());
457         return result;
458     }
459 
460     /**
461      * surfaceCreated() is called right after Fragment.onResume() and surfaceDestroyed() is called
462      * after Fragment.onPause(). We do not clean up the surface when surfaceDestroyed() and hold
463      * it till the next onResume(). We do not need to decode the image again and thus can skip the
464      * whole logic in surfaceCreated().
465      */
466     private class WallpaperSurfaceCallback implements SurfaceHolder.Callback {
467         private Surface mLastSurface;
468         private SurfaceControlViewHost mHost;
469 
470         @Override
surfaceCreated(SurfaceHolder holder)471         public void surfaceCreated(SurfaceHolder holder) {
472             Context context = getContext();
473             Activity activity = getActivity();
474             if (context == null || activity == null || mLastSurface == holder.getSurface()) {
475                 return;
476             }
477 
478             mLastSurface = holder.getSurface();
479             if (mFullResImageView != null) {
480                 mFullResImageView.recycle();
481             }
482 
483             mProgressBar.setVisibility(View.VISIBLE);
484             View wallpaperPreviewContainer = LayoutInflater.from(context).inflate(
485                     R.layout.fullscreen_wallpaper_preview_old, null);
486             mFullResImageView = wallpaperPreviewContainer.findViewById(R.id.full_res_image);
487             mLowResImageView = wallpaperPreviewContainer.findViewById(R.id.low_res_image);
488             mLowResImageView.setRenderEffect(
489                     RenderEffect.createBlurEffect(LOW_RES_BITMAP_BLUR_RADIUS,
490                             LOW_RES_BITMAP_BLUR_RADIUS, Shader.TileMode.CLAMP));
491             // Calculate the size of mWallpaperSurface based on system zoom's scale and
492             // on the larger screen size (if more than one) so that the wallpaper is
493             // rendered in a larger surface than what preview shows, simulating the behavior of
494             // the actual wallpaper surface and so we can crop it to a size that fits in all
495             // screens.
496             float scale = WallpaperCropUtils.getSystemWallpaperMaximumScale(context);
497             int origWidth = mWallpaperSurface.getWidth();
498             int origHeight = mWallpaperSurface.getHeight();
499 
500             int scaledOrigWidth = origWidth;
501             int scaledOrigHeight = origHeight;
502 
503             if (mDisplayUtils.hasMultiInternalDisplays()) {
504                 final Point maxDisplaysDimen = mDisplayUtils.getMaxDisplaysDimension();
505                 scaledOrigWidth = Math.round(
506                         origWidth * Math.max(1, (float) maxDisplaysDimen.x / mScreenSize.x));
507                 scaledOrigHeight = Math.round(
508                         origHeight * Math.max(1, (float) maxDisplaysDimen.y / mScreenSize.y));
509             }
510             int width = (int) (scaledOrigWidth * scale);
511             int height = (int) (scaledOrigHeight * scale);
512             int left = (origWidth - width) / 2;
513             int top = (origHeight - height) / 2;
514 
515             if (RtlUtils.isRtl(context)) {
516                 left *= -1;
517             }
518 
519             LayoutParams params = mWallpaperSurface.getLayoutParams();
520             params.width = width;
521             params.height = height;
522             mWallpaperSurface.setX(left);
523             mWallpaperSurface.setY(top);
524             mWallpaperSurface.setLayoutParams(params);
525             mWallpaperSurface.requestLayout();
526 
527             // Load low res image first before the full res image is available
528             int placeHolderColor = ResourceUtils.getColorAttr(activity,
529                     android.R.attr.colorBackground);
530             if (mColorFuture.isDone()) {
531                 try {
532                     int colorValue = mColorFuture.get().getPlaceholderColor();
533                     if (colorValue != Color.TRANSPARENT) {
534                         placeHolderColor = colorValue;
535                     }
536                 } catch (InterruptedException | ExecutionException e) {
537                     // Do nothing intended
538                 }
539             }
540             mWallpaperAsset.loadLowResDrawable(activity, mLowResImageView, placeHolderColor,
541                     mPreviewBitmapTransformation);
542 
543             wallpaperPreviewContainer.measure(
544                     makeMeasureSpec(width, EXACTLY),
545                     makeMeasureSpec(height, EXACTLY));
546             wallpaperPreviewContainer.layout(0, 0, width, height);
547             mTouchForwardingLayout.setTargetView(mFullResImageView);
548 
549             cleanUp();
550             mHost = new SurfaceControlViewHost(context,
551                     context.getDisplay(), mWallpaperSurface.getHostToken());
552             mHost.setView(wallpaperPreviewContainer, wallpaperPreviewContainer.getWidth(),
553                     wallpaperPreviewContainer.getHeight());
554             mWallpaperSurface.setChildSurfacePackage(mHost.getSurfacePackage());
555 
556             mWallpaperAsset.decodeRawDimensions(getActivity(), dimensions -> {
557                 if (getActivity() == null) {
558                     return;
559                 }
560 
561                 if (dimensions == null) {
562                     showLoadWallpaperErrorDialog();
563                     return;
564                 }
565 
566                 mRawWallpaperSize = dimensions;
567                 // We can enable set wallpaper now but defer to full res view ready
568                 initFullResView();
569             });
570         }
571 
572         @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)573         public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
574             // Do nothing intended
575         }
576 
577         @Override
surfaceDestroyed(SurfaceHolder holder)578         public void surfaceDestroyed(SurfaceHolder holder) {
579             // Do nothing intended
580         }
581 
cleanUp()582         public void cleanUp() {
583             if (mHost != null) {
584                 mHost.release();
585                 mHost = null;
586             }
587         }
588     }
589 }
590