Fallback to static thumbnail for Live Wallpapers

When WallpaperEngine scalePreview API is not available, use a static
thumbnail for live wallpapers instead of crashing.

Also refactor to extract a single WallpaperSurfaceCallback to make this
simpler.

Bug: 12329489
Change-Id: I6d23c0e4d557ba216e8815bc4c42f775a4dbe841
diff --git a/src/com/android/wallpaper/picker/CategoryFragment.java b/src/com/android/wallpaper/picker/CategoryFragment.java
index db60c5f..9e7201e 100755
--- a/src/com/android/wallpaper/picker/CategoryFragment.java
+++ b/src/com/android/wallpaper/picker/CategoryFragment.java
@@ -15,9 +15,6 @@
  */
 package com.android.wallpaper.picker;
 
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
 
@@ -32,9 +29,6 @@
 import android.provider.Settings;
 import android.service.wallpaper.WallpaperService;
 import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.SurfaceControlViewHost;
-import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -47,7 +41,6 @@
 import androidx.appcompat.app.AlertDialog;
 import androidx.cardview.widget.CardView;
 import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.core.content.ContextCompat;
 import androidx.fragment.app.Fragment;
 import androidx.viewpager.widget.PagerAdapter;
 import androidx.viewpager.widget.ViewPager;
@@ -73,6 +66,7 @@
 import com.android.wallpaper.util.SizeCalculator;
 import com.android.wallpaper.util.WallpaperConnection;
 import com.android.wallpaper.util.WallpaperConnection.WallpaperConnectionListener;
+import com.android.wallpaper.util.WallpaperSurfaceCallback;
 import com.android.wallpaper.widget.BottomActionBar;
 import com.android.wallpaper.widget.LiveTileOverlay;
 import com.android.wallpaper.widget.LockScreenPreviewer;
@@ -96,12 +90,6 @@
         implements CategorySelectorFragmentHost, ThumbnailUpdater, WallpaperDestinationCallback,
         WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost {
 
-    private final Rect mPreviewLocalRect = new Rect();
-    private final Rect mPreviewGlobalRect = new Rect();
-    private final int[] mLivePreviewLocation = new int[2];
-    private final WallpaperSurfaceCallback mWallpaperSurfaceCallback =
-            new WallpaperSurfaceCallback();
-
     /**
      * Interface to be implemented by an Activity hosting a {@link CategoryFragment}
      */
@@ -135,6 +123,7 @@
     private SurfaceView mWorkspaceSurface;
     private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
     private SurfaceView mWallpaperSurface;
+    private WallpaperSurfaceCallback mWallpaperSurfaceCallback;
     private ImageView mLockscreenPreview;
     private PreviewPager mPreviewPager;
     private List<View> mWallPaperPreviews;
@@ -151,13 +140,13 @@
     // The wallpaper information which is currently shown on the lock preview.
     private WallpaperInfo mLockPreviewWallpaperInfo;
 
-    // Home workspace surface is behind the app window, and so must the home image wallpaper like
-    // the live wallpaper. This view is rendered on mWallpaperSurface for home image wallpaper.
-    private ImageView mHomeImageWallpaper;
-    private boolean mIsCollapsingByUserSelecting;
     private LockScreenPreviewer mLockScreenPreviewer;
     private View mRootContainer;
 
+    private final Rect mPreviewLocalRect = new Rect();
+    private final Rect mPreviewGlobalRect = new Rect();
+    private final int[] mLivePreviewLocation = new int[2];
+
     public CategoryFragment() {
         mCategorySelectorFragment = new CategorySelectorFragment();
     }
@@ -176,6 +165,8 @@
         mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback(
                 mWorkspaceSurface, getContext());
         mWallpaperSurface = homePreviewCard.findViewById(R.id.wallpaper_surface);
+        mWallpaperSurfaceCallback = new WallpaperSurfaceCallback(getContext(), mHomePreview,
+                mWallpaperSurface);
         mWallPaperPreviews.add(homePreviewCard);
 
         CardView lockscreenPreviewCard = (CardView) inflater.inflate(
@@ -409,7 +400,6 @@
             mIndividualPickerFragment.resizeLayout(mBottomSheetBehavior.getPeekHeight());
             mIndividualPickerFragment.scrollToPosition(position);
             if (mBottomSheetBehavior.getState() != STATE_COLLAPSED) {
-                mIsCollapsingByUserSelecting = true;
                 mBottomSheetBehavior.setState(STATE_COLLAPSED);
             }
         });
@@ -647,34 +637,37 @@
         if (mWallpaperConnection != null) {
             mWallpaperConnection.disconnect();
         }
-        ImageView previewView = mWallpaperIndex == 0 ? mHomePreview : mLockscreenPreview;
-        previewView.getLocationOnScreen(mLivePreviewLocation);
-        mPreviewGlobalRect.set(0, 0, previewView.getMeasuredWidth(),
-                previewView.getMeasuredHeight());
-        mPreviewLocalRect.set(mPreviewGlobalRect);
-        mPreviewGlobalRect.offset(mLivePreviewLocation[0], mLivePreviewLocation[1]);
 
-        mWallpaperConnection = new WallpaperConnection(
-                getWallpaperIntent(homeWallpaper.getWallpaperComponent()), activity,
-                new WallpaperConnectionListener() {
-                    @Override
-                    public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {
-                        if (mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo) {
-                            mLockScreenPreviewer.setColor(colors);
+        if (WallpaperConnection.isPreviewAvailable()) {
+            ImageView previewView = mWallpaperIndex == 0 ? mHomePreview : mLockscreenPreview;
+            previewView.getLocationOnScreen(mLivePreviewLocation);
+            mPreviewGlobalRect.set(0, 0, previewView.getMeasuredWidth(),
+                    previewView.getMeasuredHeight());
+            mPreviewLocalRect.set(mPreviewGlobalRect);
+            mPreviewGlobalRect.offset(mLivePreviewLocation[0], mLivePreviewLocation[1]);
+            mWallpaperConnection = new WallpaperConnection(
+                    getWallpaperIntent(homeWallpaper.getWallpaperComponent()), activity,
+                    new WallpaperConnectionListener() {
+                        @Override
+                        public void onWallpaperColorsChanged(WallpaperColors colors,
+                                int displayId) {
+                            if (mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo) {
+                                mLockScreenPreviewer.setColor(colors);
+                            }
                         }
-                    }
-                }, mPreviewGlobalRect);
+                    }, mPreviewGlobalRect);
 
-        LiveTileOverlay.INSTANCE.update(new RectF(mPreviewLocalRect),
-                ((CardView) previewView.getParent()).getRadius());
+            LiveTileOverlay.INSTANCE.update(new RectF(mPreviewLocalRect),
+                    ((CardView) previewView.getParent()).getRadius());
 
-        mWallpaperConnection.setVisibility(true);
-        previewView.post(() -> {
-            if (mWallpaperConnection != null && !mWallpaperConnection.connect()) {
-                mWallpaperConnection = null;
-                LiveTileOverlay.INSTANCE.detach(previewView.getOverlay());
-            }
-        });
+            mWallpaperConnection.setVisibility(true);
+            previewView.post(() -> {
+                if (mWallpaperConnection != null && !mWallpaperConnection.connect()) {
+                    mWallpaperConnection = null;
+                    LiveTileOverlay.INSTANCE.detach(previewView.getOverlay());
+                }
+            });
+        }
     }
 
     private void updateThumbnail(WallpaperInfo wallpaperInfo, ImageView thumbnailView,
@@ -696,7 +689,8 @@
 
         boolean renderInImageWallpaperSurface =
                 !(wallpaperInfo instanceof LiveWallpaperInfo) && isHomeWallpaper;
-        ImageView imageView = renderInImageWallpaperSurface ? mHomeImageWallpaper : thumbnailView;
+        ImageView imageView = renderInImageWallpaperSurface
+                ? mWallpaperSurfaceCallback.getHomeImageWallpaper() : thumbnailView;
         if (imageView != null) {
             wallpaperInfo.getThumbAsset(activity.getApplicationContext())
                     .loadPreviewImage(activity, imageView,
@@ -706,9 +700,9 @@
         if (isHomeWallpaper) {
             LiveTileOverlay.INSTANCE.detach(thumbnailView.getOverlay());
             if (wallpaperInfo instanceof LiveWallpaperInfo) {
-                if(mHomeImageWallpaper != null) {
+                if (mWallpaperSurfaceCallback.getHomeImageWallpaper() != null) {
                     wallpaperInfo.getThumbAsset(activity.getApplicationContext()).loadPreviewImage(
-                            activity, mHomeImageWallpaper,
+                            activity, mWallpaperSurfaceCallback.getHomeImageWallpaper(),
                             getResources().getColor(R.color.secondary_color));
                 }
                 setUpLiveWallpaperPreview(wallpaperInfo);
@@ -720,7 +714,8 @@
             }
         } else {
             // lock screen wallpaper
-            if (wallpaperInfo instanceof LiveWallpaperInfo) {
+            if (wallpaperInfo instanceof LiveWallpaperInfo
+                    && WallpaperConnection.isPreviewAvailable()) {
                 LiveTileOverlay.INSTANCE.attach(thumbnailView.getOverlay());
             } else {
                 LiveTileOverlay.INSTANCE.detach(thumbnailView.getOverlay());
@@ -747,45 +742,6 @@
         mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
     }
 
-    private class WallpaperSurfaceCallback implements SurfaceHolder.Callback {
-
-        private Surface mLastSurface;
-        private SurfaceControlViewHost mHost;
-
-        @Override
-        public void surfaceCreated(SurfaceHolder holder) {
-            if (mLastSurface != holder.getSurface()) {
-                mLastSurface = holder.getSurface();
-                mHomeImageWallpaper = new ImageView(getContext());
-                mHomeImageWallpaper.setBackgroundColor(
-                        ContextCompat.getColor(getContext(), R.color.primary_color));
-                mHomeImageWallpaper.measure(makeMeasureSpec(mHomePreview.getWidth(), EXACTLY),
-                        makeMeasureSpec(mHomePreview.getHeight(), EXACTLY));
-                mHomeImageWallpaper.layout(0, 0, mHomePreview.getWidth(), mHomePreview.getHeight());
-
-                cleanUp();
-                mHost = new SurfaceControlViewHost(getContext(),
-                        getContext().getDisplay(), mWallpaperSurface.getHostToken());
-                mHost.setView(mHomeImageWallpaper, mHomeImageWallpaper.getWidth(),
-                        mHomeImageWallpaper.getHeight());
-                mWallpaperSurface.setChildSurfacePackage(mHost.getSurfacePackage());
-            }
-        }
-
-        @Override
-        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
-
-        @Override
-        public void surfaceDestroyed(SurfaceHolder holder) { }
-
-        public void cleanUp() {
-            if (mHost != null) {
-                mHost.release();
-                mHost = null;
-            }
-        }
-    };
-
     private static class PreviewPagerAdapter extends PagerAdapter {
 
         private List<View> mPages;
diff --git a/src/com/android/wallpaper/picker/LivePreviewFragment.java b/src/com/android/wallpaper/picker/LivePreviewFragment.java
index 7af31ac..a28b4a0 100644
--- a/src/com/android/wallpaper/picker/LivePreviewFragment.java
+++ b/src/com/android/wallpaper/picker/LivePreviewFragment.java
@@ -69,10 +69,12 @@
 import com.android.wallpaper.util.ScreenSizeCalculator;
 import com.android.wallpaper.util.SizeCalculator;
 import com.android.wallpaper.util.WallpaperConnection;
+import com.android.wallpaper.util.WallpaperSurfaceCallback;
 import com.android.wallpaper.widget.BottomActionBar;
 import com.android.wallpaper.widget.BottomActionBar.AccessibilityCallback;
 import com.android.wallpaper.widget.LiveTileOverlay;
 import com.android.wallpaper.widget.LockScreenPreviewer;
+import com.android.wallpaper.widget.WallpaperColorsLoader;
 import com.android.wallpaper.widget.WallpaperInfoView;
 
 import com.google.android.material.tabs.TabLayout;
@@ -115,9 +117,11 @@
     private ViewGroup mPreviewContainer;
     private TouchForwardingLayout mTouchForwardingLayout;
     private SurfaceView mWorkspaceSurface;
+    private SurfaceView mWallpaperSurface;
     private ViewGroup mLockPreviewContainer;
     private LockScreenPreviewer mLockScreenPreviewer;
     private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
+    private WallpaperSurfaceCallback mWallpaperSurfaceCallback;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -190,9 +194,13 @@
         mLockPreviewContainer = mPreviewContainer.findViewById(R.id.lock_screen_preview_container);
         mLockScreenPreviewer = new LockScreenPreviewer(getLifecycle(), activity,
                 mLockPreviewContainer);
+        mWallpaperSurface = mHomePreviewCard.findViewById(R.id.wallpaper_surface);
         mWorkspaceSurface = mHomePreviewCard.findViewById(R.id.workspace_surface);
+
         mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback(
                 mWorkspaceSurface, getContext());
+        mWallpaperSurfaceCallback = new WallpaperSurfaceCallback(getContext(),
+                mHomePreview, mWallpaperSurface);
 
         TabLayout tabs = inflater.inflate(R.layout.full_preview_tabs,
                 view.findViewById(R.id.toolbar_tabs_container))
@@ -231,10 +239,15 @@
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
+        updateWallpaperSurface();
         setupCurrentWallpaperPreview();
         renderWorkspaceSurface();
     }
 
+    private void updateWallpaperSurface() {
+        mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
+    }
+
     private void updateScreenPreview(boolean isHomeSelected) {
         mWorkspaceSurface.setVisibility(isHomeSelected ? View.VISIBLE : View.INVISIBLE);
         mLockPreviewContainer.setVisibility(isHomeSelected ? View.INVISIBLE : View.VISIBLE);
@@ -289,13 +302,17 @@
             mWallpaperConnection = null;
         }
         mWorkspaceSurfaceCallback.cleanUp();
+        mWallpaperSurfaceCallback.cleanUp();
     }
 
     private void previewLiveWallpaper(ImageView thumbnailView) {
         thumbnailView.post(() -> {
-            mWallpaper.getThumbAsset(requireActivity().getApplicationContext()).loadPreviewImage(
-                    requireActivity(), thumbnailView,
-                    getResources().getColor(R.color.secondary_color));
+            if (mWallpaperSurfaceCallback.getHomeImageWallpaper() != null) {
+                mWallpaper.getThumbAsset(requireActivity().getApplicationContext())
+                        .loadPreviewImage(requireActivity(),
+                                mWallpaperSurfaceCallback.getHomeImageWallpaper(),
+                                getResources().getColor(R.color.secondary_color));
+            }
             LiveTileOverlay.INSTANCE.detach(thumbnailView.getOverlay());
 
             setUpLiveWallpaperPreview(mWallpaper, thumbnailView,
@@ -317,18 +334,25 @@
             thumbnail.setBounds(previewView.getLeft(), previewView.getTop(), previewView.getRight(),
                     previewView.getBottom());
         }
-        repositionPreview(previewView);
 
-        mWallpaperConnection = new WallpaperConnection(
-                getWallpaperIntent(homeWallpaper.getWallpaperComponent()),
-                activity,
-                /* listener= */ this,
-                mPreviewGlobalRect);
+        if (WallpaperConnection.isPreviewAvailable()) {
+            repositionPreview(previewView);
+            mWallpaperConnection = new WallpaperConnection(
+                    getWallpaperIntent(homeWallpaper.getWallpaperComponent()),
+                    activity,
+                    /* listener= */ this,
+                    mPreviewGlobalRect);
 
-        LiveTileOverlay.INSTANCE.update(new RectF(mPreviewLocalRect),
-                ((CardView) previewView.getParent()).getRadius());
+            LiveTileOverlay.INSTANCE.update(new RectF(mPreviewLocalRect),
+                    ((CardView) previewView.getParent()).getRadius());
 
-        mWallpaperConnection.setVisibility(true);
+            mWallpaperConnection.setVisibility(true);
+        } else {
+            WallpaperColorsLoader.getWallpaperColors(
+                    activity,
+                    homeWallpaper.getThumbAsset(activity),
+                    mLockScreenPreviewer::setColor);
+        }
         mLoadingScrim.post(() -> mLoadingScrim.animate()
                 .alpha(0f)
                 .setStartDelay(50)
diff --git a/src/com/android/wallpaper/util/WallpaperConnection.java b/src/com/android/wallpaper/util/WallpaperConnection.java
index 1b5bdae..27b02ee 100644
--- a/src/com/android/wallpaper/util/WallpaperConnection.java
+++ b/src/com/android/wallpaper/util/WallpaperConnection.java
@@ -42,6 +42,13 @@
  */
 public class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection {
 
+    /**
+     * Returns whether live preview is available in framework.
+     */
+    public static boolean isPreviewAvailable() {
+        return WallpaperEngineCompat.supportsScalePreview();
+    }
+
     private static final String TAG = "WallpaperConnection";
     private final Activity mActivity;
     private final Intent mIntent;
diff --git a/src/com/android/wallpaper/util/WallpaperSurfaceCallback.java b/src/com/android/wallpaper/util/WallpaperSurfaceCallback.java
new file mode 100644
index 0000000..64f13bd
--- /dev/null
+++ b/src/com/android/wallpaper/util/WallpaperSurfaceCallback.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wallpaper.util;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import android.content.Context;
+import android.view.Surface;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.android.wallpaper.R;
+
+/**
+ * Default implementation of {@link SurfaceHolder.Callback} to render a static wallpaper when the
+ * surface has been created.
+ */
+public class WallpaperSurfaceCallback implements SurfaceHolder.Callback {
+
+    /**
+     * Listener used to be notified when this surface is created
+     */
+    public interface SurfaceListener {
+        /**
+         * Called when {@link WallpaperSurfaceCallback#surfaceCreated(SurfaceHolder)} is called.
+         */
+        void onSurfaceCreated();
+    }
+
+    private Surface mLastSurface;
+    private SurfaceControlViewHost mHost;
+    // Home workspace surface is behind the app window, and so must the home image wallpaper like
+    // the live wallpaper. This view is rendered on here for home image wallpaper.
+    private ImageView mHomeImageWallpaper;
+    private final Context mContext;
+    private final ImageView mHomePreview;
+    private final SurfaceView mWallpaperSurface;
+    @Nullable
+    private final SurfaceListener mListener;
+
+    public WallpaperSurfaceCallback(Context context, ImageView homePreview,
+            SurfaceView wallpaperSurface, @Nullable  SurfaceListener listener) {
+        mContext = context;
+        mHomePreview = homePreview;
+        mWallpaperSurface = wallpaperSurface;
+        mListener = listener;
+    }
+
+    public WallpaperSurfaceCallback(Context context, ImageView homePreview,
+            SurfaceView wallpaperSurface) {
+        this(context, homePreview, wallpaperSurface, null);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        if (mLastSurface != holder.getSurface()) {
+            mLastSurface = holder.getSurface();
+            mHomeImageWallpaper = new ImageView(mContext);
+            mHomeImageWallpaper.setBackgroundColor(
+                    ContextCompat.getColor(mContext, R.color.primary_color));
+            mHomeImageWallpaper.measure(makeMeasureSpec(mHomePreview.getWidth(), EXACTLY),
+                    makeMeasureSpec(mHomePreview.getHeight(), EXACTLY));
+            mHomeImageWallpaper.layout(0, 0, mHomePreview.getWidth(),
+                    mHomePreview.getHeight());
+
+            cleanUp();
+            mHost = new SurfaceControlViewHost(mContext,
+                    mContext.getDisplay(), mWallpaperSurface.getHostToken());
+            mHost.setView(mHomeImageWallpaper, mHomeImageWallpaper.getWidth(),
+                    mHomeImageWallpaper.getHeight());
+            mWallpaperSurface.setChildSurfacePackage(mHost.getSurfacePackage());
+        }
+        if (mListener != null) {
+            mListener.onSurfaceCreated();
+        }
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) { }
+
+    /**
+     * Call to release resources.
+     */
+    public void cleanUp() {
+        if (mHost != null) {
+            mHost.release();
+            mHost = null;
+        }
+    }
+
+    @Nullable
+    public ImageView getHomeImageWallpaper() {
+        return mHomeImageWallpaper;
+    }
+}