Improve wallpaper preview loading transition

- Fix WallpaperPreviewBitmapTransformation rounding error when scaling
- Precompute the thumbnail background color to use as placeholder if
possible
- Allow the placeholder to be serialized in WallpaperInfo so it's
available when opening the preview
- If available, obtain a low res preview as bitmap
- Remove unused views from wallpaper_preview_card
- Make TouchForwardingLayout transparent from loading to avoid it
showing in the fragment transition

Before/after videos: https://drive.google.com/drive/folders/1-PqL0BCOuet9pnrYVphewpwAcvVoBCwo?usp=sharing

Bug: 184111918
Test: visual check
Change-Id: Icaabafd5c430a0324899e471aa8f28453ab04f1a
diff --git a/src/com/android/wallpaper/picker/ImagePreviewFragment.java b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
index 6fbfd43..feb3699 100755
--- a/src/com/android/wallpaper/picker/ImagePreviewFragment.java
+++ b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
@@ -78,6 +78,8 @@
 import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
 
 import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -107,11 +109,13 @@
     protected WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
     protected ViewGroup mLockPreviewContainer;
     protected LockScreenPreviewer mLockScreenPreviewer;
+    private Future<Integer> mPlaceholderColorFuture;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mWallpaperAsset = mWallpaper.getAsset(requireContext().getApplicationContext());
+        mPlaceholderColorFuture = mWallpaper.computePlaceholderColor(requireContext());
     }
 
     @Override
@@ -120,11 +124,6 @@
     }
 
     @Override
-    protected int getLoadingIndicatorResId() {
-        return R.id.loading_indicator;
-    }
-
-    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         View view = super.onCreateView(inflater, container, savedInstanceState);
@@ -133,10 +132,6 @@
         mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
                 activity.getWindowManager().getDefaultDisplay());
 
-        // TODO: Consider moving some part of this to the base class when live preview is ready.
-        view.findViewById(R.id.low_res_image).setVisibility(View.GONE);
-        view.findViewById(R.id.full_res_image).setVisibility(View.GONE);
-        mLoadingProgressBar.hide();
         mContainer = view.findViewById(R.id.container);
         mTouchForwardingLayout = mContainer.findViewById(R.id.touch_forwarding_layout);
         mTouchForwardingLayout.setForwardingEnabled(true);
@@ -152,6 +147,9 @@
         mWorkspaceSurfaceCallback = createWorkspaceSurfaceCallback(mWorkspaceSurface);
         mWallpaperSurface = mContainer.findViewById(R.id.wallpaper_surface);
         mLockPreviewContainer = mContainer.findViewById(R.id.lock_screen_preview_container);
+        int placeHolderColor = ResourceUtils.getColorAttr(getContext(),
+                android.R.attr.colorBackground);
+        mWorkspaceSurface.setResizeBackgroundColor(placeHolderColor);
         mLockScreenPreviewer = new LockScreenPreviewer(getLifecycle(), getContext(),
                 mLockPreviewContainer);
         mLockScreenPreviewer.setDateViewVisibility(!mFullScreenAnimation.isFullScreen());
@@ -170,7 +168,6 @@
 
         // Trim some memory from Glide to make room for the full-size image in this fragment.
         Glide.get(activity).setMemoryCategory(MemoryCategory.LOW);
-        setUpLoadingIndicator();
 
         return view;
     }
@@ -178,27 +175,6 @@
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        mWallpaperAsset.decodeRawDimensions(getActivity(), dimensions -> {
-            // Don't continue loading the wallpaper if the Fragment is detached.
-            if (getActivity() == null) {
-                return;
-            }
-
-            // Return early and show a dialog if dimensions are null (signaling a decoding error).
-            if (dimensions == null) {
-                showLoadWallpaperErrorDialog();
-                return;
-            }
-
-            // To avoid applying the wallpaper when it's not parsed. Now it's parsed, enable the
-            // bottom action bar to allow applying the wallpaper.
-            if (mBottomActionBar != null) {
-                mBottomActionBar.enableActions();
-            }
-
-            mRawWallpaperSize = dimensions;
-            initFullResView();
-        });
     }
 
     protected void onWallpaperColorsChanged(@Nullable WallpaperColors colors) {
@@ -230,9 +206,6 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (mLoadingProgressBar != null) {
-            mLoadingProgressBar.hide();
-        }
 
         if (mFullResImageView != null) {
             mFullResImageView.recycle();
@@ -303,14 +276,6 @@
         // disallow user to pan outside the view we show the wallpaper in.
         mFullResImageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
 
-        // Set a solid black "page bitmap" so MosaicView draws a black background while waiting
-        // for the image to load or a transparent one if a thumbnail already loaded.
-        Bitmap backgroundBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        int preColor = ResourceUtils.getColorAttr(getActivity(), android.R.attr.colorSecondary);
-        int color = (mLowResImageView.getDrawable() == null) ? preColor : Color.TRANSPARENT;
-        backgroundBitmap.setPixel(0, 0, color);
-        mFullResImageView.setImage(ImageSource.bitmap(backgroundBitmap));
-
         // Then set a fallback "page bitmap" to cover the whole MosaicView, which is an actual
         // (lower res) version of the image to be displayed.
         Point targetPageBitmapSize = new Point(mRawWallpaperSize);
@@ -321,17 +286,15 @@
                         return;
                     }
 
-                    // Some of these may be null depending on if the Fragment is paused, stopped,
-                    // or destroyed.
-                    if (mLoadingProgressBar != null) {
-                        mLoadingProgressBar.hide();
-                    }
                     // The page bitmap may be null if there was a decoding error, so show an
                     // error dialog.
                     if (pageBitmap == null) {
                         showLoadWallpaperErrorDialog();
                         return;
                     }
+                    // Some of these may be null depending on if the Fragment is paused, stopped,
+                    // or destroyed.
+                    mWallpaperSurface.setBackgroundColor(Color.TRANSPARENT);
                     if (mFullResImageView != null) {
                         // Set page bitmap.
                         mFullResImageView.setImage(ImageSource.bitmap(pageBitmap));
@@ -424,6 +387,7 @@
         mFullResImageView.setAlpha(0f);
         mFullResImageView.animate()
                 .alpha(1f)
+                .setInterpolator(ALPHA_OUT)
                 .setDuration(shortAnimationDuration)
                 .setListener(new AnimatorListenerAdapter() {
                     @Override
@@ -435,18 +399,6 @@
                         }
                     }
                 });
-
-        mLoadingProgressBar.animate()
-                .alpha(0f)
-                .setDuration(shortAnimationDuration)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        if (mLoadingProgressBar != null) {
-                            mLoadingProgressBar.hide();
-                        }
-                    }
-                });
     }
 
     /**
@@ -512,9 +464,8 @@
         Resources res = appContext.getResources();
         Point cropSurfaceSize = WallpaperCropUtils.calculateCropSurfaceSize(res, maxCrop, minCrop);
 
-        Rect cropRect = WallpaperCropUtils.calculateCropRect(appContext, hostViewSize,
+        return WallpaperCropUtils.calculateCropRect(appContext, hostViewSize,
                 cropSurfaceSize, mRawWallpaperSize, visibleFileRect, wallpaperZoom);
-        return cropRect;
     }
 
     @Override
@@ -559,7 +510,28 @@
                         R.layout.fullscreen_wallpaper_preview, null);
                 mFullResImageView = wallpaperPreviewContainer.findViewById(R.id.full_res_image);
                 mLowResImageView = wallpaperPreviewContainer.findViewById(R.id.low_res_image);
-                initFullResView();
+                mWallpaperAsset.decodeRawDimensions(getActivity(), dimensions -> {
+                    // Don't continue loading the wallpaper if the Fragment is detached.
+                    if (getActivity() == null) {
+                        return;
+                    }
+
+                    // Return early and show a dialog if dimensions are null (signaling a decoding
+                    // error).
+                    if (dimensions == null) {
+                        showLoadWallpaperErrorDialog();
+                        return;
+                    }
+
+                    // To avoid applying the wallpaper when it's not parsed. Now it's parsed, enable
+                    // the bottom action bar to allow applying the wallpaper.
+                    if (mBottomActionBar != null) {
+                        mBottomActionBar.enableActions();
+                    }
+
+                    mRawWallpaperSize = dimensions;
+                    initFullResView();
+                });
                 // Scale the mWallpaperSurface based on system zoom's scale so that the wallpaper is
                 // rendered in a larger surface than what preview shows, simulating the behavior of
                 // the actual wallpaper surface.
@@ -585,12 +557,23 @@
 
                 // Load a low-res placeholder image if there's a thumbnail available from the asset
                 // that can be shown to the user more quickly than the full-sized image.
-                if (mWallpaperAsset.hasLowResDataSource()) {
-                    Activity activity = requireActivity();
-                    mWallpaperAsset.loadLowResDrawable(activity, mLowResImageView, Color.BLACK,
-                            new WallpaperPreviewBitmapTransformation(
-                                    activity.getApplicationContext(), isRtl()));
+                Activity activity = requireActivity();
+                int placeHolderColor = ResourceUtils.getColorAttr(activity,
+                        android.R.attr.colorBackground);
+                if (mPlaceholderColorFuture.isDone()) {
+                    try {
+                        placeHolderColor = mWallpaper.computePlaceholderColor(context).get();
+                    } catch (InterruptedException | ExecutionException e) {
+                        // Do nothing
+                    }
                 }
+                mWallpaperSurface.setResizeBackgroundColor(placeHolderColor);
+                mWallpaperSurface.setBackgroundColor(placeHolderColor);
+
+                mWallpaperAsset.loadLowResDrawable(activity, mLowResImageView, placeHolderColor,
+                        new WallpaperPreviewBitmapTransformation(
+                                activity.getApplicationContext(), isRtl()));
+
                 wallpaperPreviewContainer.measure(
                         makeMeasureSpec(width, EXACTLY),
                         makeMeasureSpec(height, EXACTLY));