Add live wallpaper support for grid picker

Video: https://drive.google.com/file/d/14f5NtRrss8iBSron2pAviGEVd4Fk9Sjj/view?usp=sharing

Filed b/156059583 to remove PreviewPage from grid picker, then let's start to refactor the common code between GridFragment and GridFullPreviewFragment.

Test: Manually
Fixes: 155938545
Change-Id: I61ddaa49468cca740572af6e3bb54829334e1cb4
diff --git a/res/layout/grid_preview_card.xml b/res/layout/grid_preview_card.xml
index 90b5578..689d9a6 100644
--- a/res/layout/grid_preview_card.xml
+++ b/res/layout/grid_preview_card.xml
@@ -32,4 +32,9 @@
         android:layout_height="match_parent"
         android:background="@color/primary_color" />
 
+    <SurfaceView
+        android:id="@+id/wallpaper_surface"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
 </androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/src/com/android/customization/picker/grid/GridFragment.java b/src/com/android/customization/picker/grid/GridFragment.java
index c9af437..c9b655d 100644
--- a/src/com/android/customization/picker/grid/GridFragment.java
+++ b/src/com/android/customization/picker/grid/GridFragment.java
@@ -16,6 +16,8 @@
 package com.android.customization.picker.grid;
 
 import static android.app.Activity.RESULT_OK;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
 
 import static com.android.customization.picker.ViewOnlyFullPreviewActivity.SECTION_GRID;
 import static com.android.customization.picker.grid.GridFullPreviewFragment.EXTRA_GRID_OPTION;
@@ -26,20 +28,20 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Message;
 import android.os.RemoteException;
-import android.util.DisplayMetrics;
+import android.service.wallpaper.WallpaperService;
 import android.util.Log;
 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.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.Toast;
@@ -47,6 +49,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.cardview.widget.CardView;
+import androidx.core.content.ContextCompat;
 import androidx.core.widget.ContentLoadingProgressBar;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -62,12 +65,15 @@
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.ContentUriAsset;
+import com.android.wallpaper.model.LiveWallpaperInfo;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
 import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.picker.AppbarFragment;
 import com.android.wallpaper.util.SurfaceViewUtils;
+import com.android.wallpaper.util.WallpaperConnection;
 import com.android.wallpaper.widget.BottomActionBar;
+import com.android.wallpaper.widget.LiveTileOverlay;
 import com.android.wallpaper.widget.PreviewPager;
 
 import com.bumptech.glide.Glide;
@@ -86,6 +92,10 @@
 
     private static final String TAG = "GridFragment";
 
+    private final Rect mPreviewLocalRect = new Rect();
+    private final Rect mPreviewGlobalRect = new Rect();
+    private final int[] mLivePreviewLocation = new int[2];
+
     /**
      * Interface to be implemented by an Activity hosting a {@link GridFragment}
      */
@@ -100,10 +110,6 @@
     }
 
     private WallpaperInfo mHomeWallpaper;
-    private float mScreenAspectRatio;
-    private int mCardHeight;
-    private int mCardWidth;
-    private BitmapDrawable mCardBackground;
     private GridPreviewAdapter mAdapter;
     private RecyclerView mOptionsContainer;
     private OptionSelectorController<GridOption> mOptionsController;
@@ -171,9 +177,6 @@
         mOptionsContainer = view.findViewById(R.id.options_container);
         mLoading = view.findViewById(R.id.loading_indicator);
         mError = view.findViewById(R.id.error_section);
-        final Resources res = getResources();
-        DisplayMetrics dm = res.getDisplayMetrics();
-        mScreenAspectRatio = (float) dm.heightPixels / dm.widthPixels;
 
         // Clear memory cache whenever grid fragment view is being loaded.
         Glide.get(getContext()).clearMemory();
@@ -182,26 +185,36 @@
         CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
                 .getCurrentWallpaperFactory(getContext().getApplicationContext());
 
-        factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> {
-            mHomeWallpaper = homeWallpaper;
-            loadWallpaperBackground();
-
-        }, false);
-        view.addOnLayoutChangeListener(new OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                mCardHeight = mPreviewPager.getHeight() - mPreviewPager.getPaddingTop() -
-                        res.getDimensionPixelSize(R.dimen.indicator_container_height);
-                mCardWidth = (int) (mCardHeight / mScreenAspectRatio);
-                view.removeOnLayoutChangeListener(this);
-                loadWallpaperBackground();
-            }
-        });
+        factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) ->
+                mHomeWallpaper = homeWallpaper, false);
         return view;
     }
 
     @Override
+    public void onResume() {
+        super.onResume();
+        if (mAdapter != null) {
+            mAdapter.setWallpaperConnectionVisibility(true);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mAdapter != null) {
+            mAdapter.setWallpaperConnectionVisibility(false);
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mAdapter != null) {
+            mAdapter.disconnectWallpaperConnection();
+        }
+    }
+
+    @Override
     public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == FULL_PREVIEW_REQUEST_CODE && resultCode == RESULT_OK) {
@@ -222,20 +235,6 @@
         mGridManager.apply(gridOption, mApplyGridCallback);
     }
 
-    private void loadWallpaperBackground() {
-        if (mHomeWallpaper != null && mCardHeight > 0 && mCardWidth > 0) {
-            mHomeWallpaper.getThumbAsset(getContext()).decodeBitmap(mCardWidth,
-                    mCardHeight,
-                    bitmap -> {
-                        mCardBackground =
-                                new BitmapDrawable(getResources(), bitmap);
-                        if (mAdapter != null) {
-                            mAdapter.onWallpaperInfoLoaded();
-                        }
-                    });
-        }
-    }
-
     private void createAdapter() {
         mAdapter = new GridPreviewAdapter(mSelectedOption);
         mPreviewPager.setAdapter(mAdapter);
@@ -303,6 +302,13 @@
         startActivityForResult(intent, FULL_PREVIEW_REQUEST_CODE);
     }
 
+    private Intent getWallpaperIntent(android.app.WallpaperInfo info) {
+        return new Intent(WallpaperService.SERVICE_INTERFACE)
+                .setClassName(info.getPackageName(), info.getServiceName());
+    }
+
+    // TODO(b/156059583): Remove the usage of PreviewPage, and add util class to load live wallpaper
+    // for GridFragment and GridFullPreviewFragment.
     private class GridPreviewPage extends PreviewPage {
         private final int mPageId;
         private final Asset mPreviewAsset;
@@ -312,10 +318,18 @@
 
         private final String mName;
 
-        private ImageView mPreview;
-        private SurfaceView mPreviewSurface;
+        private ImageView mHomePreview;
+        private SurfaceView mGridPreviewSurface;
+        private SurfaceView mWallpaperSurface;
 
-        private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
+        private WallpaperConnection mWallpaperConnection;
+
+        // 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 final SurfaceHolder.Callback mGridSurfaceCallback = new SurfaceHolder.Callback() {
 
             private Surface mLastSurface;
             private Message mCallback;
@@ -325,9 +339,9 @@
                 if (mLastSurface != holder.getSurface()) {
                     mLastSurface = holder.getSurface();
                     Bundle result = mGridManager.renderPreview(
-                            SurfaceViewUtils.createSurfaceViewRequest(mPreviewSurface), mName);
+                            SurfaceViewUtils.createSurfaceViewRequest(mGridPreviewSurface), mName);
                     if (result != null) {
-                        mPreviewSurface.setChildSurfacePackage(
+                        mGridPreviewSurface.setChildSurfacePackage(
                                 SurfaceViewUtils.getSurfacePackage(result));
                         mCallback = SurfaceViewUtils.getCallback(result);
                     }
@@ -352,6 +366,38 @@
             }
         };
 
+        private final SurfaceHolder.Callback mWallpaperSurfaceCallback =
+                new SurfaceHolder.Callback() {
+            private Surface mLastSurface;
+
+            @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());
+
+                    SurfaceControlViewHost host = new SurfaceControlViewHost(getContext(),
+                            getContext().getDisplay(), mWallpaperSurface.getHostToken());
+                    host.setView(mHomeImageWallpaper, mHomeImageWallpaper.getWidth(),
+                            mHomeImageWallpaper.getHeight());
+                    mWallpaperSurface.setChildSurfacePackage(host.getSurfacePackage());
+                }
+                setUpWallpaperPreview();
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {}
+        };
+
         private GridPreviewPage(Activity activity, int id, Uri previewUri, String name, int rows,
                 int cols) {
             super(null, activity);
@@ -367,38 +413,115 @@
         @Override
         public void setCard(CardView card) {
             super.setCard(card);
-            mPreview = card.findViewById(R.id.grid_preview_image);
-            mPreviewSurface = card.findViewById(R.id.grid_preview_surface);
+            mHomePreview = card.findViewById(R.id.grid_preview_image);
+            mGridPreviewSurface = card.findViewById(R.id.grid_preview_surface);
+            mWallpaperSurface = card.findViewById(R.id.wallpaper_surface);
+            mGridPreviewSurface.setVisibility(View.GONE);
             // PreviewSurface is the top of its window(card view), due to #setZOrderOnTop(true).
-            mPreviewSurface.setOnClickListener(view -> showFullPreview());
+            mGridPreviewSurface.setOnClickListener(view -> showFullPreview());
         }
 
+        @Override
         public void bindPreviewContent() {
-            Resources resources = card.getResources();
-            bindWallpaperIfAvailable();
+            updateWallpaperSurface();
+            updateWorkspaceSurface();
+        }
+
+        private void updateWallpaperSurface() {
+            mWallpaperSurface.setZOrderMediaOverlay(false);
+            mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
+        }
+
+        private void updateWorkspaceSurface() {
             final boolean usesSurfaceViewForPreview = mGridManager.usesSurfaceView();
-            mPreview.setVisibility(usesSurfaceViewForPreview ? View.GONE : View.VISIBLE);
-            mPreviewSurface.setVisibility(usesSurfaceViewForPreview ? View.VISIBLE : View.GONE);
             if (usesSurfaceViewForPreview) {
-                mPreviewSurface.setZOrderOnTop(true);
-                mPreviewSurface.getHolder().addCallback(mSurfaceCallback);
+                mGridPreviewSurface.setZOrderOnTop(true);
+                mGridPreviewSurface.getHolder().addCallback(mGridSurfaceCallback);
+                mGridPreviewSurface.setVisibility(View.VISIBLE);
             } else {
                 mPreviewAsset.loadDrawableWithTransition(mActivity,
-                        mPreview /* imageView */,
+                        mHomePreview /* imageView */,
                         PREVIEW_FADE_DURATION_MS /* duration */,
                         null /* drawableLoadedListener */,
-                        resources.getColor(android.R.color.transparent,
+                        card.getResources().getColor(android.R.color.transparent,
                                 null) /* placeHolderColorJ */);
             }
         }
 
-        void bindWallpaperIfAvailable() {
-            if (card != null && mCardBackground != null) {
-                mPreview.setBackground(mCardBackground);
-                mPreviewSurface.setBackground(mCardBackground);
+        private void setUpWallpaperPreview() {
+            if (mHomeWallpaper != null && mHomeImageWallpaper != null) {
+                boolean renderInImageWallpaperSurface =
+                        !(mHomeWallpaper instanceof LiveWallpaperInfo);
+                mHomeWallpaper.getThumbAsset(getContext())
+                        .loadPreviewImage(getActivity(),
+                                renderInImageWallpaperSurface ? mHomeImageWallpaper : mHomePreview,
+                                getResources().getColor(R.color.secondary_color));
+                LiveTileOverlay.INSTANCE.detach(mHomePreview.getOverlay());
+                if (mHomeWallpaper instanceof LiveWallpaperInfo) {
+                    mHomeWallpaper.getThumbAsset(getContext().getApplicationContext())
+                            .loadPreviewImage(
+                                    getActivity(),
+                                    mHomeImageWallpaper,
+                                    getContext().getColor(R.color.secondary_color));
+                    setUpLiveWallpaperPreview(mHomeWallpaper);
+                } else {
+                    if (mWallpaperConnection != null) {
+                        mWallpaperConnection.disconnect();
+                        mWallpaperConnection = null;
+                    }
+                }
+            }
+        }
+
+        private void setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper) {
+            Activity activity = getActivity();
+            if (activity == null) {
+                return;
+            }
+
+            if (mWallpaperConnection != null) {
+                mWallpaperConnection.disconnect();
+            }
+
+            mHomePreview.getLocationOnScreen(mLivePreviewLocation);
+            mPreviewGlobalRect.set(0, 0, mHomePreview.getMeasuredWidth(),
+                    mHomePreview.getMeasuredHeight());
+            mPreviewLocalRect.set(mPreviewGlobalRect);
+            mPreviewGlobalRect.offset(mLivePreviewLocation[0], mLivePreviewLocation[1]);
+
+            mWallpaperConnection = new WallpaperConnection(
+                    getWallpaperIntent(homeWallpaper.getWallpaperComponent()), activity,
+                    new WallpaperConnection.WallpaperConnectionListener() {
+                        @Override
+                        public void onEngineShown() {}
+                    }, mPreviewGlobalRect);
+
+            LiveTileOverlay.INSTANCE.update(new RectF(mPreviewLocalRect), card.getRadius());
+
+            mWallpaperConnection.setVisibility(true);
+            mHomePreview.post(() -> {
+                if (!mWallpaperConnection.connect()) {
+                    mWallpaperConnection = null;
+                    LiveTileOverlay.INSTANCE.detach(mHomePreview.getOverlay());
+                }
+            });
+        }
+
+        void setWallpaperConnectionVisibility(boolean visibility) {
+            if (mWallpaperConnection != null) {
+                mWallpaperConnection.setVisibility(visibility);
+            }
+        }
+
+        void disconnectWallpaperConnection() {
+            LiveTileOverlay.INSTANCE.detach(mHomePreview.getOverlay());
+            if (mWallpaperConnection != null) {
+                mWallpaperConnection.disconnect();
+                mWallpaperConnection = null;
             }
         }
     }
+
     /**
      * Adapter class for mPreviewPager.
      * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe,
@@ -415,9 +538,15 @@
             }
         }
 
-        void onWallpaperInfoLoaded() {
+        void setWallpaperConnectionVisibility(boolean visibility) {
             for (GridPreviewPage page : mPages) {
-                page.bindWallpaperIfAvailable();
+                page.setWallpaperConnectionVisibility(visibility);
+            }
+        }
+
+        void disconnectWallpaperConnection() {
+            for (GridPreviewPage page : mPages) {
+                page.disconnectWallpaperConnection();
             }
         }
     }