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