[2/n] Move new picker logic to AOSP

- Wallpaper section

Bug: 190354625
Test: Build and run the app
Change-Id: I32d1eb29fff4a24529a5b6036f0aa30755c4f65b
diff --git a/res/drawable/ic_nav_wallpaper.xml b/res/drawable/ic_nav_wallpaper.xml
new file mode 100644
index 0000000..c935666
--- /dev/null
+++ b/res/drawable/ic_nav_wallpaper.xml
@@ -0,0 +1,25 @@
+<!--
+     Copyright (C) 2019 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.
+-->
+<!-- Represents the wallpaper icon (a "landscape" shape in a box) -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9,12.71l2.14,2.58l3,-3.87L18,16.57H6L9,12.71zM5,5h6V3H5C3.9,3 3,3.9 3,5v6h2V5zM19,19h-6v2h6c1.1,0 2,-0.9 2,-2v-6h-2V19zM5,19v-6H3v6c0,1.1 0.9,2 2,2h6v-2H5zM19,5v6h2V5c0,-1.1 -0.9,-2 -2,-2h-6v2H19zM16,9c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1c-0.55,0 -1,0.45 -1,1S15.45,9 16,9z"/>
+</vector>
diff --git a/res/drawable/wallpaper_section_background.xml b/res/drawable/wallpaper_section_background.xml
new file mode 100644
index 0000000..208f543
--- /dev/null
+++ b/res/drawable/wallpaper_section_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+     Copyright (C) 2021 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners android:radius="28dp" />
+    <solid android:color="?androidprv:attr/colorSurface" />
+</shape>
diff --git a/res/layout/wallpaper_section_view.xml b/res/layout/wallpaper_section_view.xml
new file mode 100644
index 0000000..e963b3a
--- /dev/null
+++ b/res/layout/wallpaper_section_view.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+<com.android.wallpaper.picker.WallpaperSectionView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="@dimen/section_bottom_padding"
+    android:paddingHorizontal="@dimen/section_horizontal_padding"
+    android:orientation="vertical">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/wallpaper_section_background"
+        android:contentDescription="@string/wallpaper_preview_card_content_description">
+
+        <!-- Width percent = 146(preview width in spec) / 364(parent width in spec) = 0.4 -->
+        <!-- The height will be determined programmatically -->
+        <include
+            android:id="@+id/lock_preview"
+            layout="@layout/wallpaper_preview_card"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginTop="@dimen/wallpaper_preview_margin_top"
+            app:layout_constraintEnd_toStartOf="@id/home_preview"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_percent="0.4" />
+
+        <include
+            android:id="@+id/home_preview"
+            layout="@layout/wallpaper_preview_card"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginTop="@dimen/wallpaper_preview_margin_top"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/lock_preview"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_percent="0.4" />
+
+        <include
+            android:id="@+id/permission_needed"
+            layout="@layout/permission_needed_layout"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/home_preview" />
+
+        <Button
+            android:id="@+id/wallpaper_picker_entry"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginVertical="@dimen/wallpaper_picker_entry_margin_vertical"
+            android:background="@drawable/btn_transparent_background"
+            android:textColor="?android:attr/colorAccent"
+            android:drawablePadding="@dimen/wallpaper_picker_entry_drawable_padding"
+            android:drawableStart="@drawable/ic_nav_wallpaper"
+            android:drawableTint="?android:attr/colorAccent"
+            android:text="@string/wallpaper_picker_entry_title"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/permission_needed" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.wallpaper.picker.WallpaperSectionView>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2d63edc..34c327d 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -193,4 +193,16 @@
     <!-- The size of lock screen preview 2 on the full screen. -->
     <dimen name="lock_screen_preview2_date_text_size">20dp</dimen>
     <dimen name="lock_screen_preview2_time_text_size">170dp</dimen>
+
+    <!-- Common dimensions for option sections -->
+    <dimen name="section_container_vertical_margin">24dp</dimen>
+    <dimen name="section_horizontal_padding">24dp</dimen>
+    <dimen name="section_vertical_padding">24dp</dimen>
+    <dimen name="section_top_padding">16dp</dimen>
+    <dimen name="section_bottom_padding">16dp</dimen>
+
+    <!-- For the wallpaper section -->
+    <dimen name="wallpaper_preview_margin_top">24dp</dimen>
+    <dimen name="wallpaper_picker_entry_drawable_padding">8dp</dimen>
+    <dimen name="wallpaper_picker_entry_margin_vertical">18dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6ea6272..4f3d62e 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -351,4 +351,10 @@
     <!-- Label for a button which lets user show certain parts of the fullscreen preview UI. [CHAR LIMIT=30] -->
     <string name="show_ui_preview_text">Show UI Preview</string>
 
+    <!-- The title of wallpaper picker entry in new picker. [CHAR LIMIT=30]  -->
+    <string name="wallpaper_picker_entry_title" msgid="7039652539125902659">Change wallpaper</string>
+
+    <!-- Content description of wallpaper preview card (lockscreen). [CHAR_LIMIT=40]-->
+    <string name="lockscreen_wallpaper_preview_card_content_description" msgid="8575577284424318765">Lockscreen wallpaper preview</string>
+
 </resources>
diff --git a/src/com/android/wallpaper/model/HubSectionController.java b/src/com/android/wallpaper/model/HubSectionController.java
new file mode 100644
index 0000000..e271254
--- /dev/null
+++ b/src/com/android/wallpaper/model/HubSectionController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.wallpaper.picker.SectionView;
+
+/**
+ * The interface for the behavior of section in the Customization hub.
+ *
+ * @param <T> the {@link SectionView} to create for the section
+ */
+public interface HubSectionController<T extends SectionView> {
+
+    /** Interface for Customization hub section navigation. */
+    interface HubSectionNavigationController {
+        /** Navigates to the given {@code fragment}. */
+        void navigateTo(Fragment fragment);
+    }
+
+    /** Interface for Customization hub section's dark mode responding to battery saver. */
+    interface HubSectionBatterySaverListener {
+        /** Callback when battery saver's state changed with given {@code isEnabled}. */
+        void onBatterySaverStateChanged(boolean isEnabled);
+    }
+
+    /** Returns {@code true} if the Customization hub section is available. */
+    boolean isAvailable(@Nullable Context context);
+
+    /**
+     * Returns a newly created {@link SectionView} for the section.
+     *
+     * @param context the {@link Context} to inflate view
+     */
+    T createView(Context context);
+
+    /** Saves the view state for configuration changes. */
+    default void onSaveInstanceState(Bundle savedInstanceState) {}
+
+    /** Releases the controller. */
+    default void release() {}
+}
diff --git a/src/com/android/wallpaper/model/PermissionRequester.java b/src/com/android/wallpaper/model/PermissionRequester.java
new file mode 100644
index 0000000..e15d588
--- /dev/null
+++ b/src/com/android/wallpaper/model/PermissionRequester.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import com.android.wallpaper.picker.MyPhotosStarter;
+
+/**
+ * The interface for a class that can request the permission.
+ */
+public interface PermissionRequester {
+    /**
+     * Requests the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.
+     *
+     * @param listener the listener to be notified of permissions grant status changes
+     */
+    void requestExternalStoragePermission(MyPhotosStarter.PermissionChangedListener listener);
+}
diff --git a/src/com/android/wallpaper/model/WallpaperColorsViewModel.kt b/src/com/android/wallpaper/model/WallpaperColorsViewModel.kt
new file mode 100644
index 0000000..cecadb1
--- /dev/null
+++ b/src/com/android/wallpaper/model/WallpaperColorsViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.model
+
+import android.app.WallpaperColors
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+/**
+ * ViewModel class to keep track of WallpaperColors for the current wallpaper
+ */
+class WallpaperColorsViewModel : ViewModel() {
+
+    /**
+     * WallpaperColors for the currently set home wallpaper
+     */
+    val homeWallpaperColors: MutableLiveData<WallpaperColors> by lazy {
+        MutableLiveData<WallpaperColors>()
+    }
+
+    /**
+     * WallpaperColors for the currently set lock wallpaper
+     */
+    val lockWallpaperColors: MutableLiveData<WallpaperColors> by lazy {
+        MutableLiveData<WallpaperColors>()
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaper/model/WallpaperPreviewNavigator.java b/src/com/android/wallpaper/model/WallpaperPreviewNavigator.java
new file mode 100644
index 0000000..bf1642a
--- /dev/null
+++ b/src/com/android/wallpaper/model/WallpaperPreviewNavigator.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import com.android.wallpaper.model.WallpaperInfo;
+
+/**
+ * Navigates to a wallpaper preview activity.
+ */
+public interface WallpaperPreviewNavigator {
+    /**
+     * Navigates full screen wallpaper preview
+     *
+     * @param wallpaperInfo the wallpaper to preview
+     * @param isViewAsHome  {@code true} if it's home wallpaper preview, {@code false} if it's
+     *                      lock screen wallpaper preview
+     */
+    void showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isViewAsHome);
+}
diff --git a/src/com/android/wallpaper/model/WallpaperSectionController.java b/src/com/android/wallpaper/model/WallpaperSectionController.java
new file mode 100644
index 0000000..07f1eb4
--- /dev/null
+++ b/src/com/android/wallpaper/model/WallpaperSectionController.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+
+import android.animation.Animator;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.service.wallpaper.WallpaperService;
+import android.view.LayoutInflater;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.cardview.widget.CardView;
+import androidx.core.widget.ContentLoadingProgressBar;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.asset.BitmapCachingAsset;
+import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.UserEventLogger;
+import com.android.wallpaper.picker.CategorySelectorFragment;
+import com.android.wallpaper.picker.MyPhotosStarter;
+import com.android.wallpaper.picker.WallpaperSectionView;
+import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback;
+import com.android.wallpaper.util.ResourceUtils;
+import com.android.wallpaper.util.WallpaperConnection;
+import com.android.wallpaper.util.WallpaperSurfaceCallback;
+import com.android.wallpaper.widget.LockScreenPreviewer2;
+
+/** The class to control the wallpaper section view. */
+public class WallpaperSectionController implements HubSectionController<WallpaperSectionView>,
+        LifecycleObserver {
+
+    private static final String PERMISSION_READ_WALLPAPER_INTERNAL =
+            "android.permission.READ_WALLPAPER_INTERNAL";
+    private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
+
+    private CardView mHomePreviewCard;
+    private ContentLoadingProgressBar mHomePreviewProgress;
+    private SurfaceView mWorkspaceSurface;
+    private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
+    private SurfaceView mHomeWallpaperSurface;
+    private WallpaperSurfaceCallback mHomeWallpaperSurfaceCallback;
+    private SurfaceView mLockWallpaperSurface;
+    private WallpaperSurfaceCallback mLockWallpaperSurfaceCallback;
+    private CardView mLockscreenPreviewCard;
+    private ViewGroup mLockPreviewContainer;
+    private ContentLoadingProgressBar mLockscreenPreviewProgress;
+    private WallpaperConnection mWallpaperConnection;
+
+    // The wallpaper information which is currently shown on the home preview.
+    private WallpaperInfo mHomePreviewWallpaperInfo;
+    // The wallpaper information which is currently shown on the lock preview.
+    private WallpaperInfo mLockPreviewWallpaperInfo;
+
+    private LockScreenPreviewer2 mLockScreenPreviewer;
+
+    private final Activity mActivity;
+    private final Context mAppContext;
+    private final LifecycleOwner mLifecycleOwner;
+    private final PermissionRequester mPermissionRequester;
+    private final WallpaperColorsViewModel mWallpaperColorsViewModel;
+    private final WorkspaceViewModel mWorkspaceViewModel;
+    private final HubSectionNavigationController mHubSectionNavigationController;
+    private final WallpaperPreviewNavigator mWallpaperPreviewNavigator;
+    private final Bundle mSavedInstanceState;
+
+    public WallpaperSectionController(Activity activity, LifecycleOwner lifecycleOwner,
+            PermissionRequester permissionRequester, WallpaperColorsViewModel colorsViewModel,
+            WorkspaceViewModel workspaceViewModel,
+            HubSectionNavigationController hubSectionNavigationController,
+            WallpaperPreviewNavigator wallpaperPreviewNavigator,
+            Bundle savedInstanceState) {
+        mActivity = activity;
+        mLifecycleOwner = lifecycleOwner;
+        mPermissionRequester = permissionRequester;
+        mAppContext = mActivity.getApplicationContext();
+        mWallpaperColorsViewModel = colorsViewModel;
+        mWorkspaceViewModel = workspaceViewModel;
+        mHubSectionNavigationController = hubSectionNavigationController;
+        mWallpaperPreviewNavigator = wallpaperPreviewNavigator;
+        mSavedInstanceState = savedInstanceState;
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    @MainThread
+    public void onResume() {
+        refreshCurrentWallpapers(/* forceRefresh= */ mSavedInstanceState == null);
+        if (mWallpaperConnection != null) {
+            mWallpaperConnection.setVisibility(true);
+        }
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+    @MainThread
+    public void onPause() {
+        if (mWallpaperConnection != null) {
+            mWallpaperConnection.setVisibility(false);
+        }
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    @MainThread
+    public void onStop() {
+        if (mWallpaperConnection != null) {
+            mWallpaperConnection.disconnect();
+            mWallpaperConnection = null;
+        }
+    }
+
+    @Override
+    public boolean isAvailable(Context context) {
+        return true;
+    }
+
+    @Override
+    public WallpaperSectionView createView(Context context) {
+        WallpaperSectionView wallpaperSectionView = (WallpaperSectionView) LayoutInflater.from(
+                context).inflate(R.layout.wallpaper_section_view, /* root= */ null);
+        mHomePreviewCard = wallpaperSectionView.findViewById(R.id.home_preview);
+        // TODO(santie) completely remove wallpaper_preview_image once we get rid of the old ui
+        mHomePreviewCard.findViewById(R.id.wallpaper_preview_image).setVisibility(View.GONE);
+        mHomePreviewCard.setContentDescription(mAppContext.getString(
+                R.string.wallpaper_preview_card_content_description));
+        mWorkspaceSurface = mHomePreviewCard.findViewById(R.id.workspace_surface);
+        mHomePreviewProgress = mHomePreviewCard.findViewById(R.id.wallpaper_preview_spinner);
+        mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback(
+                mWorkspaceSurface, mAppContext);
+        mHomeWallpaperSurface = mHomePreviewCard.findViewById(R.id.wallpaper_surface);
+        mHomeWallpaperSurfaceCallback = new WallpaperSurfaceCallback(mActivity, mHomePreviewCard,
+                mHomeWallpaperSurface, () -> {
+            if (mHomePreviewWallpaperInfo != null) {
+                maybeLoadThumbnail(mHomePreviewWallpaperInfo, mHomeWallpaperSurfaceCallback);
+            }
+        });
+
+        mLockscreenPreviewCard = wallpaperSectionView.findViewById(R.id.lock_preview);
+        mLockscreenPreviewCard.setContentDescription(mAppContext.getString(
+                R.string.lockscreen_wallpaper_preview_card_content_description));
+        mLockscreenPreviewProgress = mLockscreenPreviewCard.findViewById(
+                R.id.wallpaper_preview_spinner);
+        mLockscreenPreviewCard.findViewById(R.id.wallpaper_preview_image).setVisibility(View.GONE);
+        mLockscreenPreviewCard.findViewById(R.id.workspace_surface).setVisibility(View.GONE);
+        mLockWallpaperSurface = mLockscreenPreviewCard.findViewById(R.id.wallpaper_surface);
+        mLockWallpaperSurfaceCallback = new WallpaperSurfaceCallback(mActivity,
+                mLockscreenPreviewCard, mLockWallpaperSurface, () -> {
+            if (mLockPreviewWallpaperInfo != null) {
+                maybeLoadThumbnail(mLockPreviewWallpaperInfo, mLockWallpaperSurfaceCallback);
+            }
+        });
+        mLockPreviewContainer = mLockscreenPreviewCard.findViewById(
+                R.id.lock_screen_preview_container);
+        mLockPreviewContainer.setVisibility(View.INVISIBLE);
+        mLockScreenPreviewer = new LockScreenPreviewer2(mLifecycleOwner.getLifecycle(), context,
+                mLockPreviewContainer);
+
+        setupCurrentWallpaperPreview(wallpaperSectionView);
+        final int shortDuration = mAppContext.getResources().getInteger(
+                android.R.integer.config_shortAnimTime);
+        fadeWallpaperPreview(true, shortDuration);
+        mLifecycleOwner.getLifecycle().addObserver(this);
+        updateWallpaperSurface();
+        updateWorkspaceSurface();
+
+        wallpaperSectionView.findViewById(R.id.wallpaper_picker_entry).setOnClickListener(
+                v -> mHubSectionNavigationController.navigateTo(new CategorySelectorFragment()));
+
+        mWorkspaceViewModel.getUpdateWorkspace().observe(mLifecycleOwner, update ->
+                updateWorkspacePreview(mWorkspaceSurface, mWorkspaceSurfaceCallback,
+                        mWallpaperColorsViewModel.getHomeWallpaperColors().getValue())
+        );
+
+        return wallpaperSectionView;
+    }
+
+    private void updateWorkspacePreview(SurfaceView workspaceSurface,
+            WorkspaceSurfaceHolderCallback callback, @Nullable WallpaperColors colors) {
+        // Reattach SurfaceView to trigger #surfaceCreated to update preview for different option.
+        ViewGroup parent = (ViewGroup) workspaceSurface.getParent();
+        int viewIndex = parent.indexOfChild(workspaceSurface);
+        parent.removeView(workspaceSurface);
+        if (callback != null) {
+            callback.resetLastSurface();
+            callback.setWallpaperColors(colors);
+        }
+        parent.addView(workspaceSurface, viewIndex);
+    }
+
+    @Override
+    public void release() {
+        if (mLockScreenPreviewer != null) {
+            mLockScreenPreviewer.release();
+            mLockScreenPreviewer = null;
+        }
+        if (mHomeWallpaperSurfaceCallback != null) {
+            mHomeWallpaperSurfaceCallback.cleanUp();
+        }
+        if (mLockWallpaperSurfaceCallback != null) {
+            mLockWallpaperSurfaceCallback.cleanUp();
+        }
+        if (mWorkspaceSurfaceCallback != null) {
+            mWorkspaceSurfaceCallback.cleanUp();
+        }
+        mLifecycleOwner.getLifecycle().removeObserver(this);
+    }
+
+    private void setupCurrentWallpaperPreview(View rootView) {
+        if (canShowCurrentWallpaper()) {
+            showCurrentWallpaper(rootView, true);
+        } else {
+            showCurrentWallpaper(rootView, false);
+
+            Button mAllowAccessButton = rootView
+                    .findViewById(R.id.permission_needed_allow_access_button);
+            mAllowAccessButton.setOnClickListener(view ->
+                    mPermissionRequester.requestExternalStoragePermission(
+                            new MyPhotosStarter.PermissionChangedListener() {
+
+                                @Override
+                                public void onPermissionsGranted() {
+                                    showCurrentWallpaper(rootView, true);
+                                }
+
+                                @Override
+                                public void onPermissionsDenied(boolean dontAskAgain) {
+                                    if (!dontAskAgain) {
+                                        return;
+                                    }
+                                    showPermissionNeededDialog();
+                                }
+                            })
+            );
+
+            // Replace explanation text with text containing the Wallpapers app name which replaces
+            // the placeholder.
+            Resources resources = mAppContext.getResources();
+            String appName = resources.getString(R.string.app_name);
+            String explanation = resources.getString(R.string.permission_needed_explanation,
+                    appName);
+            TextView explanationView = rootView.findViewById(R.id.permission_needed_explanation);
+            explanationView.setText(explanation);
+        }
+    }
+
+    private boolean canShowCurrentWallpaper() {
+        return isPermissionGranted(mAppContext, PERMISSION_READ_WALLPAPER_INTERNAL)
+                || isPermissionGranted(mAppContext, READ_EXTERNAL_STORAGE);
+    }
+
+    private boolean isPermissionGranted(Context context, String permission) {
+        return context.getPackageManager().checkPermission(permission,
+                context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void showCurrentWallpaper(View rootView, boolean show) {
+        rootView.findViewById(R.id.home_preview)
+                .setVisibility(show ? View.VISIBLE : View.GONE);
+        rootView.findViewById(R.id.lock_preview)
+                .setVisibility(show ? View.VISIBLE : View.GONE);
+        rootView.findViewById(R.id.permission_needed)
+                .setVisibility(show ? View.GONE : View.VISIBLE);
+    }
+
+    private void showPermissionNeededDialog() {
+        String permissionNeededMessage = mAppContext.getResources().getString(
+                R.string.permission_needed_explanation_go_to_settings);
+        AlertDialog dialog = new AlertDialog.Builder(mAppContext, R.style.LightDialogTheme)
+                .setMessage(permissionNeededMessage)
+                .setPositiveButton(android.R.string.ok, /* onClickListener= */ null)
+                .setNegativeButton(
+                        R.string.settings_button_label,
+                        (dialogInterface, i) -> {
+                            Intent appInfoIntent = new Intent();
+                            appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                            Uri uri = Uri.fromParts("package",
+                                    mAppContext.getPackageName(), /* fragment= */ null);
+                            appInfoIntent.setData(uri);
+                            mActivity.startActivityForResult(appInfoIntent,
+                                    SETTINGS_APP_INFO_REQUEST_CODE);
+                        })
+                .create();
+        dialog.show();
+    }
+
+    /**
+     * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to
+     * the device from the {@link CurrentWallpaperInfoFactory}.
+     */
+    private void refreshCurrentWallpapers(boolean forceRefresh) {
+        CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
+                .getCurrentWallpaperFactory(mAppContext);
+
+        factory.createCurrentWallpaperInfos(
+                (homeWallpaper, lockWallpaper, presentationMode) -> {
+                    // A config change may have destroyed the activity since the refresh
+                    // started, so check for that.
+                    if (!isActivityAlive()) {
+                        return;
+                    }
+
+                    mHomePreviewWallpaperInfo = homeWallpaper;
+                    mLockPreviewWallpaperInfo =
+                            lockWallpaper == null ? homeWallpaper : lockWallpaper;
+
+                    updatePreview(mHomePreviewWallpaperInfo, true);
+                    updatePreview(mLockPreviewWallpaperInfo, false);
+
+                    WallpaperManager manager = WallpaperManager.getInstance(mAppContext);
+
+                    WallpaperColors homeColors =
+                            manager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+                    onHomeWallpaperColorsChanged(homeColors);
+                    WallpaperColors lockColors = homeColors;
+
+                    if (lockWallpaper != null) {
+                        lockColors = manager.getWallpaperColors(WallpaperManager.FLAG_LOCK);
+
+                    }
+                    onLockWallpaperColorsChanged(lockColors);
+                }, forceRefresh);
+    }
+
+    private void updatePreview(WallpaperInfo wallpaperInfo, boolean isHomeWallpaper) {
+        if (wallpaperInfo == null) {
+            return;
+        }
+
+        if (!isActivityAlive()) {
+            return;
+        }
+
+        UserEventLogger eventLogger = InjectorProvider.getInjector().getUserEventLogger(
+                mAppContext);
+
+        WallpaperSurfaceCallback surfaceCallback = isHomeWallpaper
+                ? mHomeWallpaperSurfaceCallback : mLockWallpaperSurfaceCallback;
+        // Load thumb regardless of live wallpaper to make sure we have a placeholder while
+        // the live wallpaper initializes in that case.
+        Asset thumbAsset = maybeLoadThumbnail(wallpaperInfo, surfaceCallback);
+
+        if (isHomeWallpaper) {
+            if (mWallpaperConnection != null) {
+                mWallpaperConnection.disconnect();
+                mWallpaperConnection = null;
+            }
+            if (wallpaperInfo instanceof LiveWallpaperInfo) {
+                setUpLiveWallpaperPreview(wallpaperInfo);
+            }
+        }
+
+        View preview = isHomeWallpaper ? mHomePreviewCard : mLockscreenPreviewCard;
+        preview.setOnClickListener(view -> {
+            mWallpaperPreviewNavigator.showViewOnlyPreview(wallpaperInfo, isHomeWallpaper);
+            eventLogger.logCurrentWallpaperPreviewed();
+        });
+    }
+
+    @NonNull
+    private Asset maybeLoadThumbnail(WallpaperInfo wallpaperInfo,
+            WallpaperSurfaceCallback surfaceCallback) {
+        ImageView imageView = surfaceCallback.getHomeImageWallpaper();
+        Asset thumbAsset = new BitmapCachingAsset(mAppContext,
+                wallpaperInfo.getThumbAsset(mAppContext));
+        if (imageView != null && imageView.getDrawable() == null) {
+            thumbAsset.loadPreviewImage(mActivity, imageView,
+                    ResourceUtils.getColorAttr(mActivity, android.R.attr.colorSecondary));
+        }
+        return thumbAsset;
+    }
+
+    private void onHomeWallpaperColorsChanged(WallpaperColors wallpaperColors) {
+        if (wallpaperColors != null && wallpaperColors.equals(
+                mWallpaperColorsViewModel.getHomeWallpaperColors().getValue())) {
+            return;
+        }
+        mWallpaperColorsViewModel.getHomeWallpaperColors().setValue(wallpaperColors);
+    }
+
+    private void onLockWallpaperColorsChanged(WallpaperColors wallpaperColors) {
+        if (wallpaperColors != null && wallpaperColors.equals(
+                mWallpaperColorsViewModel.getLockWallpaperColors().getValue())) {
+            return;
+        }
+        mWallpaperColorsViewModel.getLockWallpaperColors().setValue(wallpaperColors);
+        if (mLockScreenPreviewer != null) {
+            mLockScreenPreviewer.setColor(wallpaperColors);
+        }
+    }
+
+    private void setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper) {
+        if (!isActivityAlive()) {
+            return;
+        }
+
+        if (WallpaperConnection.isPreviewAvailable()) {
+            final boolean isLockLive = mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo;
+            mWallpaperConnection = new WallpaperConnection(
+                    getWallpaperIntent(homeWallpaper.getWallpaperComponent()), mActivity,
+                    new WallpaperConnection.WallpaperConnectionListener() {
+                        @Override
+                        public void onWallpaperColorsChanged(WallpaperColors colors,
+                                int displayId) {
+                            if (isLockLive && mLockScreenPreviewer != null) {
+                                mLockScreenPreviewer.setColor(colors);
+                                onLockWallpaperColorsChanged(colors);
+                            }
+                            onHomeWallpaperColorsChanged(colors);
+                        }
+                    },
+                    mHomeWallpaperSurface, isLockLive ? mLockWallpaperSurface : null);
+
+            mWallpaperConnection.setVisibility(true);
+            mHomeWallpaperSurface.post(() -> {
+                if (mWallpaperConnection != null && !mWallpaperConnection.connect()) {
+                    mWallpaperConnection = null;
+                }
+            });
+        }
+    }
+
+    private Intent getWallpaperIntent(android.app.WallpaperInfo info) {
+        return new Intent(WallpaperService.SERVICE_INTERFACE)
+                .setClassName(info.getPackageName(), info.getServiceName());
+    }
+
+    private void updateWallpaperSurface() {
+        mHomeWallpaperSurface.getHolder().addCallback(mHomeWallpaperSurfaceCallback);
+        mHomeWallpaperSurface.setZOrderMediaOverlay(true);
+        mLockWallpaperSurface.getHolder().addCallback(mLockWallpaperSurfaceCallback);
+        mLockWallpaperSurface.setZOrderMediaOverlay(true);
+    }
+
+    private void updateWorkspaceSurface() {
+        mWorkspaceSurface.setZOrderMediaOverlay(true);
+        mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
+    }
+
+    private boolean isActivityAlive() {
+        return !mActivity.isDestroyed() && !mActivity.isFinishing();
+    }
+
+    private void fadeWallpaperPreview(boolean isFadeIn, int duration) {
+        setupFade(mHomePreviewCard, mHomePreviewProgress, duration, isFadeIn);
+        setupFade(mLockscreenPreviewCard, mLockscreenPreviewProgress, duration, isFadeIn);
+    }
+
+    private void setupFade(CardView cardView, ContentLoadingProgressBar progressBar, int duration,
+            boolean fadeIn) {
+        cardView.setAlpha(fadeIn ? 0.0f : 1.0f);
+        cardView.animate()
+                .alpha(fadeIn ? 1.0f : 0.0f)
+                .setDuration(duration)
+                .setListener(new Animator.AnimatorListener() {
+                    @Override
+                    public void onAnimationCancel(Animator animator) {
+                        progressBar.hide();
+                        setWallpaperPreviewsVisibility(View.VISIBLE);
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animator) {
+                        progressBar.hide();
+                        setWallpaperPreviewsVisibility(View.VISIBLE);
+                    }
+
+                    @Override
+                    public void onAnimationRepeat(Animator animator) {
+                    }
+
+                    @Override
+                    public void onAnimationStart(Animator animator) {
+                        setWallpaperPreviewsVisibility(View.INVISIBLE);
+                    }
+                });
+        progressBar.animate()
+                .alpha(fadeIn ? 1.0f : 0.0f)
+                .setDuration(duration * 2)
+                .setStartDelay(duration)
+                .withStartAction(progressBar::show)
+                .withEndAction(progressBar::hide);
+    }
+
+    private void setWallpaperPreviewsVisibility(int visibility) {
+        if (mHomeWallpaperSurface != null) {
+            mHomeWallpaperSurface.setVisibility(visibility);
+        }
+        if (mLockWallpaperSurface != null) {
+            mLockWallpaperSurface.setVisibility(visibility);
+        }
+        if (mWorkspaceSurface != null) {
+            mWorkspaceSurface.setVisibility(visibility);
+        }
+        if (mLockPreviewContainer != null) {
+            mLockPreviewContainer.setVisibility(visibility);
+        }
+    }
+}
diff --git a/src/com/android/wallpaper/model/WorkspaceViewModel.kt b/src/com/android/wallpaper/model/WorkspaceViewModel.kt
new file mode 100644
index 0000000..bafd6aa
--- /dev/null
+++ b/src/com/android/wallpaper/model/WorkspaceViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.model
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+/** ViewModel class to keep track of workspace updates. */
+class WorkspaceViewModel : ViewModel() {
+
+    /**
+     * Triggers workspace updates through flipping the value from {@code false} to {@code true},
+     * or from {@code true} to {@code false}.
+     */
+    val updateWorkspace: MutableLiveData<Boolean> by lazy {
+        MutableLiveData<Boolean>()
+    }
+}
diff --git a/src/com/android/wallpaper/picker/SectionView.java b/src/com/android/wallpaper/picker/SectionView.java
new file mode 100644
index 0000000..31a2014
--- /dev/null
+++ b/src/com/android/wallpaper/picker/SectionView.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.picker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+/**
+ * The SectionView base for views hosting in the {@link
+ * com.android.customization.picker.hub.HubFragment}.
+ */
+public abstract class SectionView extends LinearLayout {
+
+    /** The callback for the section view updates. */
+    public interface SectionViewListener {
+        void onViewActivated(@Nullable Context context, boolean viewActivated);
+    }
+
+    protected SectionViewListener mSectionViewListener;
+    private String mTitle;
+
+    public SectionView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setTitle(String title) {
+        mTitle = title;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /** Sets the listener to the {@code SectionView} instance for reacting the view changes. */
+    public void setViewListener(SectionViewListener sectionViewListener) {
+        mSectionViewListener = sectionViewListener;
+    }
+}
diff --git a/src/com/android/wallpaper/picker/WallpaperSectionView.java b/src/com/android/wallpaper/picker/WallpaperSectionView.java
new file mode 100644
index 0000000..1d20e08
--- /dev/null
+++ b/src/com/android/wallpaper/picker/WallpaperSectionView.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.picker;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+
+import androidx.cardview.widget.CardView;
+
+import com.android.wallpaper.picker.SectionView;
+import com.android.wallpaper.R;
+import com.android.wallpaper.util.ScreenSizeCalculator;
+import com.android.wallpaper.util.SizeCalculator;
+
+/**
+ * The wallpaper section view in the Customization Hub fragment.
+ */
+public final class WallpaperSectionView extends SectionView {
+
+    private CardView mHomePreviewCard;
+    private CardView mLockscreenPreviewCard;
+
+    public WallpaperSectionView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        matchDeviceShape(mHomePreviewCard);
+        matchDeviceShape(mLockscreenPreviewCard);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mHomePreviewCard = findViewById(R.id.home_preview);
+        mLockscreenPreviewCard = findViewById(R.id.lock_preview);
+
+        // Disable the shadows of these card views.
+        mHomePreviewCard.setCardElevation(0);
+        mLockscreenPreviewCard.setCardElevation(0);
+    }
+
+    private void matchDeviceShape(CardView cardView) {
+        // Match device aspect ratio
+        float screenAspectRatio =
+                ScreenSizeCalculator.getInstance().getScreenAspectRatio(getContext());
+        int cardWidth = cardView.getMeasuredWidth();
+        int cardHeight = (int) (cardWidth * screenAspectRatio);
+        ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
+        layoutParams.height = cardHeight;
+
+        // Match device corner
+        cardView.setRadius(
+                SizeCalculator.getPreviewCornerRadius((Activity) getContext(), cardWidth));
+    }
+}