Use SurfaceView to render grid preview

Demo: https://drive.google.com/open?id=1wQ8dT5bfTxSh-NRQpNBwTCkKOtOOUMPj

Bug: 150224413
Test: Manual
Change-Id: I006055da5e372029ecf99c4c3aac6d866688ef41
diff --git a/res/layout/grid_preview_card.xml b/res/layout/grid_preview_card.xml
index ae66b83..90b5578 100644
--- a/res/layout/grid_preview_card.xml
+++ b/res/layout/grid_preview_card.xml
@@ -24,6 +24,12 @@
         android:id="@+id/grid_preview_image"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@color/primary_color"/>
+        android:background="@color/primary_color" />
+
+    <SurfaceView
+        android:id="@+id/grid_preview_surface"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/primary_color" />
 
 </androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/src/com/android/customization/model/grid/GridOptionsManager.java b/src/com/android/customization/model/grid/GridOptionsManager.java
index 1599dde..a334f5e 100644
--- a/src/com/android/customization/model/grid/GridOptionsManager.java
+++ b/src/com/android/customization/model/grid/GridOptionsManager.java
@@ -16,6 +16,8 @@
 package com.android.customization.model.grid;
 
 import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -23,7 +25,6 @@
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.module.ThemesUserEventLogger;
 
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -60,7 +61,17 @@
         new FetchTask(mProvider, callback).execute();
     }
 
-    private static class FetchTask extends AsyncTask<Void, Void, List<GridOption>> {
+    /** See if using surface view to render grid options */
+    public boolean usesSurfaceView() {
+        return mProvider.usesSurfaceView();
+    }
+
+    /** Call through content provider API to render preview */
+    public void renderPreview(Bundle bundle, String gridName) {
+        mProvider.renderPreview(gridName, bundle);
+    }
+
+    private static class FetchTask extends AsyncTask<Void, Void, Pair<List<GridOption>, String>> {
         private final LauncherGridOptionsProvider mProvider;
         @Nullable private final OptionsFetchedListener<GridOption> mCallback;
 
@@ -71,15 +82,16 @@
         }
 
         @Override
-        protected List<GridOption> doInBackground(Void[] params) {
+        protected Pair<List<GridOption>, String> doInBackground(Void[] params) {
             return mProvider.fetch(false);
         }
 
         @Override
-        protected void onPostExecute(List<GridOption> gridOptions) {
+        protected void onPostExecute(Pair<List<GridOption>, String> gridOptionsResult) {
             if (mCallback != null) {
-                if (gridOptions != null && !gridOptions.isEmpty()) {
-                    mCallback.onOptionsLoaded(gridOptions);
+                if (gridOptionsResult != null && gridOptionsResult.first != null
+                        && !gridOptionsResult.first.isEmpty()) {
+                    mCallback.onOptionsLoaded(gridOptionsResult.first);
                 } else {
                     mCallback.onError(null);
                 }
diff --git a/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
index 7c7f8d4..8eb7383 100644
--- a/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
+++ b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
@@ -25,12 +25,16 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.text.TextUtils;
+import android.util.Pair;
+import android.view.SurfaceView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
 import com.android.customization.model.ResourceConstants;
+import com.android.systemui.shared.system.SurfaceViewRequestUtils;
 import com.android.wallpaper.R;
 
 import com.bumptech.glide.Glide;
@@ -53,10 +57,14 @@
     private static final String COL_PREVIEW_COUNT = "preview_count";
     private static final String COL_IS_DEFAULT = "is_default";
 
+    private static final String METHOD_GET_PREVIEW = "get_preview";
+    private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version";
+
     private final Context mContext;
     private final String mGridProviderAuthority;
     private final ProviderInfo mProviderInfo;
     private List<GridOption> mOptions;
+    private String mVersion;
 
     public LauncherGridOptionsProvider(Context context, String authorityMetadataKey) {
         mContext = context;
@@ -78,18 +86,23 @@
         return mProviderInfo != null;
     }
 
+    boolean usesSurfaceView() {
+        // If no version code is returned, fall back to V1.
+        return TextUtils.equals(mVersion, "V2");
+    }
+
     /**
      * Retrieve the available grids.
      * @param reload whether to reload grid options if they're cached.
      */
     @WorkerThread
     @Nullable
-    List<GridOption> fetch(boolean reload) {
+    Pair<List<GridOption>, String> fetch(boolean reload) {
         if (!areGridsAvailable()) {
             return null;
         }
         if (mOptions != null && !reload) {
-            return mOptions;
+            return Pair.create(mOptions, mVersion);
         }
         Uri optionsUri = new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_CONTENT)
@@ -100,6 +113,7 @@
         String iconPath = mContext.getResources().getString(Resources.getSystem().getIdentifier(
                 ResourceConstants.CONFIG_ICON_MASK, "string", ResourceConstants.ANDROID_PACKAGE));
         try (Cursor c = resolver.query(optionsUri, null, null, null, null)) {
+            mVersion = c.getExtras().getString(METADATA_KEY_PREVIEW_VERSION);
             mOptions = new ArrayList<>();
             while(c.moveToNext()) {
                 String name = c.getString(c.getColumnIndex(COL_NAME));
@@ -120,8 +134,26 @@
             Glide.get(mContext).clearDiskCache();
         } catch (Exception e) {
             mOptions = null;
+            mVersion = null;
         }
-        return mOptions;
+        return Pair.create(mOptions, mVersion);
+    }
+
+    /**
+     * Request rendering of home screen preview via Launcher to Wallpaper using SurfaceView
+     * @param name      the grid option name
+     * @param bundle    surface view request bundle generated from
+     *                  {@link SurfaceViewRequestUtils#createSurfaceBundle(SurfaceView)}.
+     */
+    void renderPreview(String name, Bundle bundle) {
+        Uri preview = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(mProviderInfo.authority)
+                .appendPath(PREVIEW)
+                .appendPath(name)
+                .build();
+        bundle.putString("name", name);
+        mContext.getContentResolver().call(preview, METHOD_GET_PREVIEW, null, bundle);
     }
 
     int applyGrid(String name) {
diff --git a/src/com/android/customization/picker/grid/GridFragment.java b/src/com/android/customization/picker/grid/GridFragment.java
index 49a3771..7a01f26 100644
--- a/src/com/android/customization/picker/grid/GridFragment.java
+++ b/src/com/android/customization/picker/grid/GridFragment.java
@@ -26,6 +26,8 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
@@ -45,6 +47,7 @@
 import com.android.customization.picker.BasePreviewAdapter;
 import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
 import com.android.customization.widget.OptionSelectorController;
+import com.android.systemui.shared.system.SurfaceViewRequestUtils;
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.ContentUriAsset;
@@ -246,13 +249,18 @@
         private final int mRows;
         private final Activity mActivity;
 
-		private ImageView mPreview;
+        private final String mName;
 
-        private GridPreviewPage(Activity activity, int id, Uri previewUri, int rows, int cols) {
+        private ImageView mPreview;
+        private SurfaceView mPreviewSurface;
+
+        private GridPreviewPage(Activity activity, int id, Uri previewUri, String name, int rows,
+                int cols) {
             super(null);
             mPageId = id;
             mPreviewAsset = new ContentUriAsset(activity, previewUri,
                     RequestOptions.fitCenterTransform());
+            mName = name;
             mRows = rows;
             mCols = cols;
             mActivity = activity;
@@ -260,23 +268,48 @@
 
         @Override
         public void setCard(CardView card) {
-        	super.setCard(card);
-        	mPreview = card.findViewById(R.id.grid_preview_image);
+            super.setCard(card);
+            mPreview = card.findViewById(R.id.grid_preview_image);
+            mPreviewSurface = card.findViewById(R.id.grid_preview_surface);
         }
 
         public void bindPreviewContent() {
             Resources resources = card.getResources();
             bindWallpaperIfAvailable();
-            mPreviewAsset.loadDrawableWithTransition(mActivity,
-                    mPreview /* imageView */,
-                    PREVIEW_FADE_DURATION_MS /* duration */,
-                    null /* drawableLoadedListener */,
-                    resources.getColor(android.R.color.transparent, null) /* placeHolderColorJ */);
+            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(new SurfaceHolder.Callback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        Bundle bundle = SurfaceViewRequestUtils.createSurfaceBundle(
+                                mPreviewSurface);
+                        mGridManager.renderPreview(bundle, mName);
+                    }
+
+                    @Override
+                    public void surfaceChanged(SurfaceHolder holder, int format, int width,
+                            int height) {}
+
+                    @Override
+                    public void surfaceDestroyed(SurfaceHolder holder) {}
+                });
+            } else {
+                mPreviewAsset.loadDrawableWithTransition(mActivity,
+                        mPreview /* imageView */,
+                        PREVIEW_FADE_DURATION_MS /* duration */,
+                        null /* drawableLoadedListener */,
+                        resources.getColor(android.R.color.transparent,
+                                null) /* placeHolderColorJ */);
+            }
         }
 
         void bindWallpaperIfAvailable() {
             if (card != null && mCardBackground != null) {
                 mPreview.setBackground(mCardBackground);
+                mPreviewSurface.setBackground(mCardBackground);
             }
         }
     }
@@ -292,7 +325,7 @@
             for (int i = 0; i < gridOption.previewPagesCount; i++) {
                 addPage(new GridPreviewPage(getActivity(), i,
                         gridOption.previewImageUri.buildUpon().appendPath("" + i).build(),
-                        gridOption.rows, gridOption.cols));
+                        gridOption.name, gridOption.rows, gridOption.cols));
             }
         }