[LivePicker 2/n] Add LiveWallpaper preview support

Create a new LivePreviewFragment with the same UI and
functionality of Android's LivePicker.

Note that there's still redundancy between
ImagePreviewFragment and LivePreviewFragment, follow up
CLs will clean this up and share as much as possible.

Still missing is support for delete and settings action
buttons. The new LivePreviewFragment won't be hooked up
until all the functionality is implemented.

Bug: 141391722
Change-Id: I859f9122c368c01575c23f73a80ffdb519ef0e71
diff --git a/Android.mk b/Android.mk
index 369f739..39eeeb6 100755
--- a/Android.mk
+++ b/Android.mk
@@ -71,6 +71,7 @@
     androidx.appcompat_appcompat \
     androidx.cardview_cardview \
     androidx.recyclerview_recyclerview \
+    androidx.slice_slice-view \
     androidx-constraintlayout_constraintlayout \
     com.google.android.material_material \
     androidx.exifinterface_exifinterface \
@@ -113,7 +114,15 @@
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_SDK_VERSION := system_current
+LOCAL_PRIVILEGED_MODULE := true
+
+ifneq (,$(wildcard frameworks/base))
+  LOCAL_PRIVATE_PLATFORM_APIS := true
+else
+  LOCAL_SDK_VERSION := system_current
+  LOCAL_STATIC_JAVA_LIBRARIES += libSharedWallpaper
+endif
+
 LOCAL_PACKAGE_NAME := WallpaperPicker2
 LOCAL_JETIFIER_ENABLED := true
 
diff --git a/res/drawable/gradient_background.xml b/res/drawable/gradient_background.xml
new file mode 100644
index 0000000..47d864a
--- /dev/null
+++ b/res/drawable/gradient_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+
+     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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient android:angle="270"
+        android:centerY=".1"
+        android:startColor="@color/translucent_black"
+        android:centerColor="@android:color/transparent"
+        android:endColor="@android:color/transparent"
+        />
+</shape>
\ No newline at end of file
diff --git a/res/layout/fragment_preview.xml b/res/layout/fragment_image_preview.xml
similarity index 65%
rename from res/layout/fragment_preview.xml
rename to res/layout/fragment_image_preview.xml
index 82c481f..6c3e324 100755
--- a/res/layout/fragment_preview.xml
+++ b/res/layout/fragment_image_preview.xml
@@ -1,4 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -37,13 +52,19 @@
           android:layout_height="wrap_content"
           android:layout_gravity="bottom">
 
-        <include
-            layout="@layout/preview_page_info"
+        <FrameLayout
             android:id="@+id/bottom_sheet"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:background="@drawable/preview_bottom_sheet_background"
+            android:theme="@style/WallpaperPicker.BottomPaneStyle"
             app:behavior_peekHeight="@dimen/preview_attribution_pane_collapsed_height"
-            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"/>
+            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
+          <include
+              layout="@layout/preview_page_info"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"/>
+        </FrameLayout>
 
     </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
diff --git a/res/layout/fragment_live_preview.xml b/res/layout/fragment_live_preview.xml
new file mode 100755
index 0000000..418a289
--- /dev/null
+++ b/res/layout/fragment_live_preview.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<FrameLayout 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="match_parent"
+             android:fitsSystemWindows="false">
+
+  <View
+      android:layout_width="match_parent"
+      android:layout_height="@dimen/preview_gradient_background_height"
+      android:layout_gravity="top"
+      android:background="@drawable/gradient_background"/>
+
+      <FrameLayout
+          android:id="@+id/loading"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:background="@android:color/black"
+          android:forceHasOverlappingRendering="false">
+
+          <ImageView
+              android:id="@+id/loading_indicator"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_gravity="center"
+              android:visibility="visible"
+              android:fitsSystemWindows="false"/>
+
+    </FrameLayout>
+
+  <FrameLayout
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:fitsSystemWindows="true">
+
+      <androidx.coordinatorlayout.widget.CoordinatorLayout
+          android:id="@+id/coordinator_layout"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:layout_gravity="bottom">
+
+        <LinearLayout
+            android:id="@+id/bottom_sheet"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:background="@drawable/preview_bottom_sheet_background"
+            android:theme="@style/WallpaperPicker.BottomPaneStyle"
+            app:behavior_peekHeight="@dimen/preview_attribution_pane_collapsed_height"
+            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
+
+          <com.google.android.material.tabs.TabLayout
+              android:id="@+id/tablayout"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              app:tabTextAppearance="@style/WallpaperPicker.Preview.TextAppearance.NoAllCaps"
+              app:tabIndicatorColor="?android:attr/textColorPrimary"
+              android:visibility="gone"/>
+
+          <com.android.wallpaper.widget.ConstraintViewPager
+              android:id="@+id/viewpager"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
+        </LinearLayout>
+
+    </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top"
+        style="@style/TranslucentToolbarStyle"/>
+
+  </FrameLayout>
+
+</FrameLayout>
diff --git a/res/layout/preview_page_info.xml b/res/layout/preview_page_info.xml
index 9299426..2489561 100644
--- a/res/layout/preview_page_info.xml
+++ b/res/layout/preview_page_info.xml
@@ -21,8 +21,7 @@
     android:layout_width="match_parent"
     android:orientation="vertical"
     android:paddingHorizontal="@dimen/preview_attribution_pane_horizontal_padding"
-    android:background="@drawable/preview_bottom_sheet_background"
-    android:theme="@android:style/Theme.DeviceDefault.Settings">
+    android:theme="@style/WallpaperPicker.BottomPaneStyle">
 
     <Space
         android:id="@+id/preview_attribution_pane_title_spacer"
diff --git a/res/layout/preview_page_settings.xml b/res/layout/preview_page_settings.xml
new file mode 100644
index 0000000..72e83a5
--- /dev/null
+++ b/res/layout/preview_page_settings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="center_horizontal"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:paddingHorizontal="@dimen/preview_attribution_pane_horizontal_padding"
+    android:theme="@style/WallpaperPicker.BottomPaneStyle">
+
+    <androidx.slice.widget.SliceView
+        android:id="@+id/settings_slice"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+    <Space
+        android:id="@+id/preview_attribution_pane_spacer"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"/>
+
+    <Button
+        style="@style/ButtonStyle"
+        android:id="@+id/preview_attribution_pane_set_wallpaper_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/set_wallpaper_button_text"/>
+
+</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 53c95c0..843ff79 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -126,6 +126,7 @@
     <dimen name="preview_attribution_pane_description_height">34dp</dimen>
     <dimen name="preview_attribution_pane_button_bottom_margin">8dp</dimen>
 
+    <dimen name="preview_gradient_background_height">256dp</dimen>
 
     <!-- Dimensions for the "start rotation" dialog. -->
     <dimen name="start_rotation_dialog_subhead_margin_top">19dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fe99050..7d2a752 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -300,4 +300,10 @@
 
     <!-- Label for a checkbox which lets user preview the displayed image as wallpaper. [CHAR LIMIT=30] -->
     <string name="preview">Preview</string>
+
+    <!-- Label for the 'Info' tab of view pager in wallpaper preview activity. [CHAR_LIMIT=25] -->
+    <string name="tab_info">Info</string>
+    <!-- Label for the 'Customize' tab of view pager in wallpaper preview activity.
+         [CHAR_LIMIT=25] -->
+    <string name="tab_customize">Customize</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6259281..1796b1d 100755
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -111,6 +111,15 @@
     </style>
 
     <!-- Preview attribution pane styles -->
+    <style name="WallpaperPicker.BottomPaneStyle" parent="@android:style/Theme.DeviceDefault.Settings">
+        <item name="android:textColorPrimary">@color/material_white_100</item>
+        <item name="android:textColorSecondary">@color/white_70_alpha</item>
+        <item name="tabTextAppearance">@style/WallpaperPicker.Preview.TextAppearance.NoAllCaps</item>
+        <item name="tabIndicatorColor">?android:attr/textColorPrimary</item>
+        <item name="tabGravity">fill</item>
+        <item name="tabMaxWidth">0dp</item>
+    </style>
+
     <style name="preview_attribution_pane_title">
         <item name="android:textColor">@color/material_white_text</item>
         <item name="android:textSize">@dimen/abc_text_size_subhead_material</item>
@@ -121,6 +130,11 @@
         <item name="android:textSize">@dimen/abc_text_size_body_2_material</item>
     </style>
 
+    <style name="WallpaperPicker.Preview.TextAppearance.NoAllCaps"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.TabWidget">
+        <item name="android:textAllCaps">false</item>
+    </style>
+
     <!-- Set wallpaper destination item -->
     <style name="set_wallpaper_destination_item">
         <item name="android:minHeight">@dimen/set_wallpaper_dialog_item_min_height</item>
diff --git a/src/com/android/wallpaper/model/LiveWallpaperInfo.java b/src/com/android/wallpaper/model/LiveWallpaperInfo.java
index a408aed..ad752b7 100755
--- a/src/com/android/wallpaper/model/LiveWallpaperInfo.java
+++ b/src/com/android/wallpaper/model/LiveWallpaperInfo.java
@@ -262,11 +262,12 @@
     @Override
     public List<String> getAttributions(Context context) {
         List<String> attributions = new ArrayList<>();
-        CharSequence labelCharSeq = mInfo.loadLabel(context.getPackageManager());
+        PackageManager packageManager = context.getPackageManager();
+        CharSequence labelCharSeq = mInfo.loadLabel(packageManager);
         attributions.add(labelCharSeq == null ? null : labelCharSeq.toString());
 
         try {
-            CharSequence authorCharSeq = mInfo.loadAuthor(context.getPackageManager());
+            CharSequence authorCharSeq = mInfo.loadAuthor(packageManager);
             if (authorCharSeq != null) {
                 String author = authorCharSeq.toString();
                 attributions.add(author);
@@ -275,6 +276,16 @@
             // No author specified, so no other attribution to add.
         }
 
+        try {
+            CharSequence descCharSeq = mInfo.loadDescription(packageManager);
+            if (descCharSeq != null) {
+                String desc = descCharSeq.toString();
+                attributions.add(desc);
+            }
+        } catch (Resources.NotFoundException e) {
+            // No description specified, so no other attribution to add.
+        }
+
         return attributions;
     }
 
@@ -294,6 +305,18 @@
         return null;
     }
 
+    /**
+     * Get an optional description for the action button if provided by this LiveWallpaper.
+     */
+    @Nullable
+    public CharSequence getActionDescription(Context context) {
+        try {
+            return mInfo.loadContextDescription(context.getPackageManager());
+        } catch (Resources.NotFoundException e) {
+            return null;
+        }
+    }
+
     @Override
     public Asset getAsset(Context context) {
         return null;
diff --git a/src/com/android/wallpaper/picker/ImagePreviewFragment.java b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
index d0b0fa5..e514075 100755
--- a/src/com/android/wallpaper/picker/ImagePreviewFragment.java
+++ b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
@@ -21,7 +21,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.app.Activity;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Color;
@@ -34,6 +33,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -58,7 +58,7 @@
 import java.util.List;
 
 /**
- * Fragment which displays the UI for previewing an individual wallpaper and its attribution
+ * Fragment which displays the UI for previewing an individual static wallpaper and its attribution
  * information.
  */
 public class ImagePreviewFragment extends PreviewFragment {
@@ -70,6 +70,9 @@
     private TextView mAttributionTitle;
     private TextView mAttributionSubtitle1;
     private TextView mAttributionSubtitle2;
+    private Button mExploreButton;
+    private Button mSetWallpaperButton;
+
     private Point mDefaultCropSurfaceSize;
     private Point mScreenSize;
     private Point mRawWallpaperSize; // Native size of wallpaper image.
@@ -94,13 +97,6 @@
         return fragment;
     }
 
-    private static int getAttrColor(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
-        int colorAccent = ta.getColor(0, 0);
-        ta.recycle();
-        return colorAccent;
-    }
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -109,7 +105,7 @@
 
     @Override
     protected int getLayoutResId() {
-        return R.layout.fragment_preview;
+        return R.layout.fragment_image_preview;
     }
 
     @Override
@@ -118,16 +114,6 @@
     }
 
     @Override
-    protected int getSetWallpaperButtonResId() {
-        return R.id.preview_attribution_pane_set_wallpaper_button;
-    }
-
-    @Override
-    protected int getExploreButtonResId() {
-        return R.id.preview_attribution_pane_explore_button;
-    }
-
-    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
         View view = super.onCreateView(inflater, container, savedInstanceState);
@@ -141,6 +127,8 @@
         mAttributionTitle = view.findViewById(R.id.preview_attribution_pane_title);
         mAttributionSubtitle1 = view.findViewById(R.id.preview_attribution_pane_subtitle1);
         mAttributionSubtitle2 = view.findViewById(R.id.preview_attribution_pane_subtitle2);
+        mExploreButton = view.findViewById(R.id.preview_attribution_pane_explore_button);
+        mSetWallpaperButton = view.findViewById(R.id.preview_attribution_pane_set_wallpaper_button);
 
         mLowResImageView = view.findViewById(R.id.low_res_image);
         mSpacer = view.findViewById(R.id.spacer);
@@ -183,7 +171,12 @@
         return view;
     }
 
-    protected void setUpLoadingIndicator() {
+    @Override
+    protected void setUpBottomSheetView(ViewGroup bottomSheet) {
+        // Nothing needed here.
+    }
+
+    private void setUpLoadingIndicator() {
         Context context = requireContext();
         mProgressDrawable = new MaterialProgressDrawable(context.getApplicationContext(),
                 mLoadingIndicator);
@@ -211,20 +204,6 @@
         }, 500);
     }
 
-    protected int getDeviceDefaultTheme() {
-        return android.R.style.Theme_DeviceDefault;
-    }
-
-    @Override
-    public void onSet(int destination) {
-        setCurrentWallpaper(destination);
-    }
-
-    @Override
-    public void onClickTryAgain(@Destination int wallpaperDestination) {
-        setCurrentWallpaper(wallpaperDestination);
-    }
-
     @Override
     public void onClickOk() {
         FragmentActivity activity = getActivity();
@@ -252,7 +231,7 @@
 
     @Override
     protected void setBottomSheetContentAlpha(float alpha) {
-        super.setBottomSheetContentAlpha(alpha);
+        mExploreButton.setAlpha(alpha);
         mAttributionTitle.setAlpha(alpha);
         mAttributionSubtitle1.setAlpha(alpha);
         mAttributionSubtitle2.setAlpha(alpha);
@@ -278,9 +257,9 @@
             mAttributionSubtitle2.setText(attributions.get(2));
         }
 
-        setUpSetWallpaperButton();
+        setUpSetWallpaperButton(mSetWallpaperButton);
 
-        setUpExploreButton();
+        setUpExploreButton(mExploreButton);
 
         if (mExploreButton.getVisibility() == View.VISIBLE
                 && mSetWallpaperButton.getVisibility() == View.VISIBLE) {
diff --git a/src/com/android/wallpaper/picker/LivePreviewFragment.java b/src/com/android/wallpaper/picker/LivePreviewFragment.java
new file mode 100644
index 0000000..ca199a2
--- /dev/null
+++ b/src/com/android/wallpaper/picker/LivePreviewFragment.java
@@ -0,0 +1,590 @@
+/*
+ * 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.
+ */
+package com.android.wallpaper.picker;
+
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.WallpaperColors;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.service.wallpaper.IWallpaperConnection;
+import android.service.wallpaper.IWallpaperEngine;
+import android.service.wallpaper.IWallpaperService;
+import android.service.wallpaper.WallpaperService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.slice.Slice;
+import androidx.slice.widget.SliceLiveData;
+import androidx.slice.widget.SliceView;
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.compat.BuildCompat;
+import com.android.wallpaper.model.LiveWallpaperInfo;
+import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
+import com.android.wallpaper.widget.MaterialProgressDrawable;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.tabs.TabLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment which displays the UI for previewing an individual live wallpaper, its attribution
+ * information and settings slices if available.
+ */
+public class LivePreviewFragment extends PreviewFragment {
+
+    private static final String TAG = "LivePreviewFragment";
+
+    private static final String KEY_ACTION_DELETE_LIVE_WALLPAPER = "action_delete_live_wallpaper";
+    private static final String EXTRA_LIVE_WALLPAPER_INFO = "android.live_wallpaper.info";
+
+    /**
+     * Instance of {@link WallpaperConnection} used to bind to the live wallpaper service to show
+     * it in this preview fragment.
+     * @see IWallpaperConnection
+     */
+    private WallpaperConnection mWallpaperConnection;
+
+    private Intent mWallpaperIntent;
+
+    private List<Pair<String, View>> mPages;
+    private ImageView mLoadingIndicator;
+    private TextView mAttributionTitle;
+    private TextView mAttributionSubtitle1;
+    private TextView mAttributionSubtitle2;
+    private Button mExploreButton;
+    private Button mSetWallpaperButton;
+    private ViewPager mViewPager;
+    private TabLayout mTabLayout;
+    private SliceView mSettingsSliceView;
+    private LiveData<Slice> mSettingsLiveData;
+    private View mSpacer;
+    private View mLoadingScrim;
+    private MaterialProgressDrawable mProgressDrawable;
+
+    /**
+     * Creates and returns new instance of {@link LivePreviewFragment} with the provided wallpaper
+     * set as an argument.
+     */
+    public static LivePreviewFragment newInstance(
+            LiveWallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean testingModeEnabled) {
+        Bundle args = new Bundle();
+        args.putParcelable(ARG_WALLPAPER, wallpaperInfo);
+        args.putInt(ARG_PREVIEW_MODE, mode);
+        args.putBoolean(ARG_TESTING_MODE_ENABLED, testingModeEnabled);
+
+        LivePreviewFragment fragment = new LivePreviewFragment();
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        android.app.WallpaperInfo info = mWallpaper.getWallpaperComponent();
+        mWallpaperIntent = new Intent(WallpaperService.SERVICE_INTERFACE)
+                .setClassName(info.getPackageName(), info.getServiceName());
+        setUpExploreIntent(null);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mPages = new ArrayList<>();
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        if (view == null) {
+            return null;
+        }
+
+        Activity activity = requireActivity();
+
+        mLoadingScrim = view.findViewById(R.id.loading);
+        mLoadingIndicator = view.findViewById(R.id.loading_indicator);
+        setUpLoadingIndicator();
+
+        mWallpaperConnection = new WallpaperConnection(mWallpaperIntent, activity);
+        container.post(() -> {
+            if (!mWallpaperConnection.connect()) {
+                mWallpaperConnection = null;
+            }
+        });
+
+        return view;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mSettingsLiveData != null && mSettingsLiveData.hasObservers()) {
+            mSettingsLiveData.removeObserver(mSettingsSliceView);
+            mSettingsLiveData = null;
+        }
+        if (mWallpaperConnection != null) {
+            mWallpaperConnection.disconnect();
+        }
+        mWallpaperConnection = null;
+        super.onDestroy();
+    }
+
+    @Override
+    protected void setUpBottomSheetView(ViewGroup bottomSheet) {
+
+        initInfoPage();
+        initSettingsPage();
+
+        mViewPager = bottomSheet.findViewById(R.id.viewpager);
+        mTabLayout = bottomSheet.findViewById(R.id.tablayout);
+
+        // Create PagerAdapter
+        final PagerAdapter pagerAdapter = new PagerAdapter() {
+            @Override
+            public Object instantiateItem(ViewGroup container, int position) {
+                final View page = mPages.get(position).second;
+                container.addView(page);
+                return page;
+            }
+
+            @Override
+            public void destroyItem(@NonNull ViewGroup container, int position,
+                    @NonNull Object object) {
+                if (object instanceof View) {
+                    container.removeView((View) object);
+                }
+            }
+
+            @Override
+            public int getCount() {
+                return mPages.size();
+            }
+
+            @Override
+            public CharSequence getPageTitle(int position) {
+                try {
+                    return mPages.get(position).first;
+                } catch (IndexOutOfBoundsException e) {
+                    return null;
+                }
+            }
+
+            @Override
+            public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+                return (view == object);
+            }
+        };
+
+        // Add OnPageChangeListener to re-measure ViewPager's height
+        mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+            @Override
+            public void onPageSelected(int position) {
+                mViewPager.requestLayout();
+            }
+        });
+
+        // Set PagerAdapter
+        mViewPager.setAdapter(pagerAdapter);
+
+        // Make TabLayout visible if there are more than one page
+        if (mPages.size() > 1) {
+            mTabLayout.setVisibility(View.VISIBLE);
+            mTabLayout.setupWithViewPager(mViewPager);
+        }
+        mViewPager.setCurrentItem(0);
+    }
+
+    private void setUpLoadingIndicator() {
+        Context context = requireContext();
+        mProgressDrawable = new MaterialProgressDrawable(context.getApplicationContext(),
+                mLoadingIndicator);
+        mProgressDrawable.setAlpha(255);
+        mProgressDrawable.setBackgroundColor(getResources().getColor(R.color.material_white_100,
+                context.getTheme()));
+        mProgressDrawable.setColorSchemeColors(getAttrColor(
+                new ContextThemeWrapper(context, getDeviceDefaultTheme()),
+                android.R.attr.colorAccent));
+        mProgressDrawable.updateSizes(MaterialProgressDrawable.LARGE);
+        mLoadingIndicator.setImageDrawable(mProgressDrawable);
+
+        // We don't want to show the spinner every time we load a wallpaper if it loads quickly;
+        // instead, only start showing the spinner after 100 ms
+        mLoadingIndicator.postDelayed(() -> {
+            if ((mWallpaperConnection == null || !mWallpaperConnection.isEngineReady())
+                    && !mTestingModeEnabled) {
+                mLoadingIndicator.setVisibility(View.VISIBLE);
+                mLoadingIndicator.setAlpha(1f);
+                if (mProgressDrawable != null) {
+                    mProgressDrawable.start();
+                }
+            }
+        }, 100);
+    }
+
+    private void initInfoPage() {
+        View pageInfo = getLayoutInflater().inflate(R.layout.preview_page_info, null /* root */);
+
+        mAttributionTitle = pageInfo.findViewById(R.id.preview_attribution_pane_title);
+        mAttributionSubtitle1 = pageInfo.findViewById(R.id.preview_attribution_pane_subtitle1);
+        mAttributionSubtitle2 = pageInfo.findViewById(R.id.preview_attribution_pane_subtitle2);
+        mSpacer = pageInfo.findViewById(R.id.spacer);
+
+        mExploreButton = pageInfo.findViewById(R.id.preview_attribution_pane_explore_button);
+        mSetWallpaperButton = pageInfo.findViewById(
+                R.id.preview_attribution_pane_set_wallpaper_button);
+
+        mPages.add(Pair.create(getString(R.string.tab_info), pageInfo));
+    }
+
+    private void initSettingsPage() {
+        final Uri uriSettingsSlice = getSettingsSliceUri(mWallpaper.getWallpaperComponent());
+        if (uriSettingsSlice == null) {
+            return;
+        }
+
+        final View pageSettings = getLayoutInflater().inflate(R.layout.preview_page_settings,
+                null /* root */);
+
+        mSettingsSliceView = pageSettings.findViewById(R.id.settings_slice);
+        mSettingsSliceView.setMode(SliceView.MODE_LARGE);
+        mSettingsSliceView.setScrollable(false);
+
+        // Set LiveData for SliceView
+        mSettingsLiveData = SliceLiveData.fromUri(requireContext() /* context */, uriSettingsSlice);
+        mSettingsLiveData.observeForever(mSettingsSliceView);
+
+        mPages.add(Pair.create(getResources().getString(R.string.tab_customize), pageSettings));
+    }
+
+    private void populateAttributionPane() {
+        final Context context = getContext();
+
+        final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
+
+        List<String> attributions = mWallpaper.getAttributions(context);
+        if (attributions.size() > 0 && attributions.get(0) != null) {
+            mAttributionTitle.setText(attributions.get(0));
+        }
+
+        if (attributions.size() > 1 && attributions.get(1) != null) {
+            mAttributionSubtitle1.setVisibility(View.VISIBLE);
+            mAttributionSubtitle1.setText(attributions.get(1));
+        }
+
+        if (attributions.size() > 2 && attributions.get(2) != null) {
+            mAttributionSubtitle2.setVisibility(View.VISIBLE);
+            mAttributionSubtitle2.setText(attributions.get(2));
+        }
+
+        setUpSetWallpaperButton(mSetWallpaperButton);
+
+        setUpExploreButton(mExploreButton);
+
+        if (mExploreButton.getVisibility() == View.VISIBLE
+                && mSetWallpaperButton.getVisibility() == View.VISIBLE) {
+            mSpacer.setVisibility(View.VISIBLE);
+        } else {
+            mSpacer.setVisibility(View.GONE);
+        }
+
+        mBottomSheet.setVisibility(View.VISIBLE);
+
+        // Initialize the state of the BottomSheet based on the current state because if the initial
+        // and current state are the same, the state change listener won't fire and set the correct
+        // arrow asset and text alpha.
+        if (mBottomSheetInitialState == STATE_EXPANDED) {
+            setPreviewChecked(false);
+            mAttributionTitle.setAlpha(1f);
+            mAttributionSubtitle1.setAlpha(1f);
+            mAttributionSubtitle2.setAlpha(1f);
+        } else {
+            setPreviewChecked(true);
+            mAttributionTitle.setAlpha(0f);
+            mAttributionSubtitle1.setAlpha(0f);
+            mAttributionSubtitle2.setAlpha(0f);
+        }
+
+        bottomSheetBehavior.setState(mBottomSheetInitialState);
+    }
+
+    @SuppressLint("NewApi") //Already checking with isAtLeastQ
+    private Uri getSettingsSliceUri(android.app.WallpaperInfo info) {
+        if (BuildCompat.isAtLeastQ()) {
+            return info.getSettingsSliceUri();
+        }
+        return null;
+    }
+
+    @Override
+    protected int getLayoutResId() {
+        return R.layout.fragment_live_preview;
+    }
+
+    @Override
+    protected int getBottomSheetResId() {
+        return R.id.bottom_sheet;
+    }
+
+    @Override
+    protected void setCurrentWallpaper(int destination) {
+        mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, null,
+                destination, 0, null, new SetWallpaperCallback() {
+                    @Override
+                    public void onSuccess() {
+                        finishActivityWithResultOk();
+                    }
+
+                    @Override
+                    public void onError(@Nullable Throwable throwable) {
+                        showSetWallpaperErrorDialog(destination);
+                    }
+                });
+    }
+
+    @Override
+    protected void setBottomSheetContentAlpha(float alpha) {
+        mExploreButton.setAlpha(alpha);
+        mAttributionTitle.setAlpha(alpha);
+        mAttributionSubtitle1.setAlpha(alpha);
+        mAttributionSubtitle2.setAlpha(alpha);
+    }
+
+    @Override
+    protected void setUpExploreButton(Button exploreButton) {
+        super.setUpExploreButton(exploreButton);
+        if (exploreButton.getVisibility() != View.VISIBLE) {
+            return;
+        }
+        Context context = requireContext();
+        CharSequence exploreLabel = ((LiveWallpaperInfo) mWallpaper).getActionDescription(context);
+        if (!TextUtils.isEmpty(exploreLabel)) {
+            exploreButton.setText(exploreLabel);
+        }
+    }
+
+    @Nullable
+    private String getDeleteAction(ServiceInfo serviceInfo,
+            @Nullable ServiceInfo currentService) {
+        if (!isPackagePreInstalled(serviceInfo.applicationInfo)) {
+            Log.d(TAG, "This wallpaper is not pre-installed: " + serviceInfo.name);
+            return null;
+        }
+
+        // A currently set Live wallpaper should not be deleted.
+        if (currentService != null && TextUtils.equals(serviceInfo.name, currentService.name)) {
+            return null;
+        }
+
+        final Bundle metaData = serviceInfo.metaData;
+        if (metaData != null) {
+            return metaData.getString(KEY_ACTION_DELETE_LIVE_WALLPAPER);
+        }
+        return null;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mWallpaperConnection != null) {
+            mWallpaperConnection.setVisibility(true);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mWallpaperConnection != null) {
+            mWallpaperConnection.setVisibility(false);
+        }
+    }
+
+    private boolean isPackagePreInstalled(ApplicationInfo info) {
+        if (info != null && (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private class WallpaperConnection extends IWallpaperConnection.Stub
+            implements ServiceConnection {
+        private final Activity mActivity;
+        private final Intent mIntent;
+        private IWallpaperService mService;
+        private IWallpaperEngine mEngine;
+        private boolean mConnected;
+        private boolean mIsVisible;
+        private boolean mIsEngineVisible;
+        private boolean mEngineReady;
+
+        WallpaperConnection(Intent intent, Activity activity) {
+            mActivity = activity;
+            mIntent = intent;
+        }
+
+        public boolean connect() {
+            synchronized (this) {
+                if (!mActivity.bindService(mIntent, this, Context.BIND_AUTO_CREATE)) {
+                    return false;
+                }
+
+                mConnected = true;
+                return true;
+            }
+        }
+
+        public void disconnect() {
+            synchronized (this) {
+                mConnected = false;
+                if (mEngine != null) {
+                    try {
+                        mEngine.destroy();
+                    } catch (RemoteException e) {
+                        // Ignore
+                    }
+                    mEngine = null;
+                }
+                try {
+                    mActivity.unbindService(this);
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG, "Can't unbind wallpaper service. "
+                            + "It might have crashed, just ignoring.", e);
+                }
+                mService = null;
+            }
+        }
+
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (mWallpaperConnection == this) {
+                mService = IWallpaperService.Stub.asInterface(service);
+                try {
+                    View root = mActivity.getWindow().getDecorView();
+                    int displayId = root.getDisplay().getDisplayId();
+                    mService.attach(this, root.getWindowToken(),
+                            LayoutParams.TYPE_APPLICATION_MEDIA,
+                            true, root.getWidth(), root.getHeight(),
+                            new Rect(0, 0, 0, 0), displayId);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed attaching wallpaper; clearing", e);
+                }
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            mService = null;
+            mEngine = null;
+            if (mWallpaperConnection == this) {
+                Log.w(TAG, "Wallpaper service gone: " + name);
+            }
+        }
+
+        public void attachEngine(IWallpaperEngine engine, int displayId) {
+            synchronized (this) {
+                if (mConnected) {
+                    mEngine = engine;
+                    if (mIsVisible) {
+                        setEngineVisibility(true);
+                    }
+                } else {
+                    try {
+                        engine.destroy();
+                    } catch (RemoteException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        public ParcelFileDescriptor setWallpaper(String name) {
+            return null;
+        }
+
+        @Override
+        public void onWallpaperColorsChanged(WallpaperColors colors, int displayId)
+                throws RemoteException {
+
+        }
+
+        @Override
+        public void engineShown(IWallpaperEngine engine)  {
+            mLoadingScrim.post(() -> {
+                mLoadingScrim.animate()
+                        .alpha(0f)
+                        .setDuration(220)
+                        .setStartDelay(300)
+                        .setInterpolator(AnimationUtils.loadInterpolator(mActivity,
+                                android.R.interpolator.fast_out_linear_in))
+                        .withEndAction(() -> {
+                            if (mLoadingIndicator != null) {
+                                mLoadingIndicator.setVisibility(View.GONE);
+                            }
+                            if (mProgressDrawable != null) {
+                                mProgressDrawable.stop();
+                            }
+                            mLoadingScrim.setVisibility(View.INVISIBLE);
+                            populateAttributionPane();
+                        });
+            });
+            mEngineReady = true;
+        }
+
+        public boolean isEngineReady() {
+            return mEngineReady;
+        }
+
+        public void setVisibility(boolean visible) {
+            mIsVisible = visible;
+            setEngineVisibility(visible);
+        }
+
+        private void setEngineVisibility(boolean visible) {
+            if (mEngine != null && visible != mIsEngineVisible) {
+                try {
+                    mEngine.setVisibility(visible);
+                    mIsEngineVisible = visible;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failure setting wallpaper visibility ", e);
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/wallpaper/picker/PreviewFragment.java b/src/com/android/wallpaper/picker/PreviewFragment.java
index 7310318..3715f9f 100755
--- a/src/com/android/wallpaper/picker/PreviewFragment.java
+++ b/src/com/android/wallpaper/picker/PreviewFragment.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
@@ -38,7 +39,6 @@
 import android.view.Window;
 import android.widget.Button;
 import android.widget.CheckBox;
-import android.widget.LinearLayout;
 import android.widget.Toast;
 
 import androidx.annotation.CallSuper;
@@ -60,7 +60,6 @@
 import com.android.wallpaper.module.Injector;
 import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.module.UserEventLogger;
-import com.android.wallpaper.module.WallpaperPersister;
 import com.android.wallpaper.module.WallpaperPersister.Destination;
 import com.android.wallpaper.module.WallpaperPreferences;
 import com.android.wallpaper.module.WallpaperSetter;
@@ -72,8 +71,7 @@
 import java.util.List;
 
 /**
- * Fragment which displays the UI for previewing an individual wallpaper and its attribution
- * information.
+ * Base Fragment to display the UI for previewing an individual wallpaper
  */
 public abstract class PreviewFragment extends Fragment implements
         SetWallpaperDialogFragment.Listener, SetWallpaperErrorDialogFragment.Listener,
@@ -123,9 +121,7 @@
     protected WallpaperInfo mWallpaper;
     protected WallpaperSetter mWallpaperSetter;
     protected UserEventLogger mUserEventLogger;
-    protected LinearLayout mBottomSheet;
-    protected Button mExploreButton;
-    protected Button mSetWallpaperButton;
+    protected ViewGroup mBottomSheet;
 
     protected CheckBox mPreview;
 
@@ -142,6 +138,13 @@
     private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
     private LoadWallpaperErrorDialogFragment mStagedLoadWallpaperErrorDialogFragment;
 
+    protected static int getAttrColor(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -214,8 +217,7 @@
         /* bottom */ 0);
 
         mBottomSheet = view.findViewById(getBottomSheetResId());
-        mExploreButton = view.findViewById(getExploreButtonResId());
-        mSetWallpaperButton = view.findViewById(getSetWallpaperButtonResId());
+        setUpBottomSheetView(mBottomSheet);
 
         // Workaround as we don't have access to bottomDialogCornerRadius, mBottomSheet radii are
         // set to dialogCornerRadius by default.
@@ -228,24 +230,18 @@
         bottomSheetBackground.setCornerRadii(radii);
         mBottomSheet.setBackground(bottomSheetBackground);
 
-        mBottomSheetInitialState = (savedInstanceState == null)
-                ? STATE_EXPANDED
-                : savedInstanceState.getInt(KEY_BOTTOM_SHEET_STATE,
-                        STATE_EXPANDED);
+        mBottomSheetInitialState = (savedInstanceState == null) ? STATE_EXPANDED
+                : savedInstanceState.getInt(KEY_BOTTOM_SHEET_STATE, STATE_EXPANDED);
         setUpBottomSheetListeners();
 
         return view;
     }
 
-    @IdRes
-    protected abstract int getSetWallpaperButtonResId();
+    protected abstract void setUpBottomSheetView(ViewGroup bottomSheet);
 
     @IdRes
     protected abstract int getBottomSheetResId();
 
-    @IdRes
-    protected abstract int getExploreButtonResId();
-
     protected int getDeviceDefaultTheme() {
         return android.R.style.Theme_DeviceDefault;
     }
@@ -315,7 +311,7 @@
         }
     }
 
-    private void setPreviewBehavior(final View v) {
+    private void setPreviewBehavior(View v) {
         CheckBox checkbox = (CheckBox) v;
         BottomSheetBehavior<?> behavior = BottomSheetBehavior.from(mBottomSheet);
 
@@ -326,26 +322,26 @@
         }
     }
 
-    protected void setUpSetWallpaperButton() {
+    protected void setUpSetWallpaperButton(Button setWallpaperButton) {
         if (mPreviewMode == MODE_VIEW_ONLY) {
-            mSetWallpaperButton.setVisibility(View.GONE);
+            setWallpaperButton.setVisibility(View.GONE);
         } else {
-            mSetWallpaperButton.setVisibility(View.VISIBLE);
-            mSetWallpaperButton.setOnClickListener(this::onSetWallpaperClicked);
+            setWallpaperButton.setVisibility(View.VISIBLE);
+            setWallpaperButton.setOnClickListener(this::onSetWallpaperClicked);
         }
     }
 
-    protected void setUpExploreButton() {
-        mExploreButton.setVisibility(View.GONE);
+    protected void setUpExploreButton(Button exploreButton) {
+        exploreButton.setVisibility(View.GONE);
         if (mExploreIntent == null) {
             return;
         }
         Context context = requireContext();
-        mExploreButton.setVisibility(View.VISIBLE);
-        mExploreButton.setText(context.getString(
+        exploreButton.setVisibility(View.VISIBLE);
+        exploreButton.setText(context.getString(
                 mWallpaper.getActionLabelRes(context)));
 
-        mExploreButton.setOnClickListener(view -> {
+        exploreButton.setOnClickListener(view -> {
             mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context),
                     mWallpaper.getActionLabelRes(context));
 
@@ -414,12 +410,8 @@
     }
 
     private void onSetWallpaperClicked(View button) {
-        if (BuildCompat.isAtLeastN()) {
-            mWallpaperSetter.requestDestination(getContext(), getFragmentManager(), this,
-                    mWallpaper instanceof LiveWallpaperInfo);
-        } else {
-            setCurrentWallpaper(WallpaperPersister.DEST_HOME_SCREEN);
-        }
+        mWallpaperSetter.requestDestination(getContext(), getFragmentManager(), this,
+                mWallpaper instanceof LiveWallpaperInfo);
     }
 
     private void setUpBottomSheetListeners() {
@@ -460,7 +452,7 @@
     }
 
     protected void setBottomSheetContentAlpha(float alpha) {
-        mExploreButton.setAlpha(alpha);
+
     }
 
     /**
diff --git a/src/com/android/wallpaper/widget/ConstraintViewPager.java b/src/com/android/wallpaper/widget/ConstraintViewPager.java
new file mode 100644
index 0000000..ad56750
--- /dev/null
+++ b/src/com/android/wallpaper/widget/ConstraintViewPager.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package com.android.wallpaper.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.viewpager.widget.ViewPager;
+
+/**
+ * When ConstraintViewPager is being measured, it will get all height of pages and makes itself
+ * height as the same as the maximum height.
+ */
+public class ConstraintViewPager extends ViewPager {
+
+    public ConstraintViewPager(@NonNull Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public ConstraintViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Visit all child views first and then determine the maximum height for ViewPager.
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int maxChildHeight = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            view.measure(widthMeasureSpec,
+                    MeasureSpec.makeMeasureSpec(0 /* size */, MeasureSpec.UNSPECIFIED));
+            int childHeight = view.getMeasuredHeight();
+            if (childHeight > maxChildHeight) {
+                maxChildHeight = childHeight;
+            }
+        }
+
+        if (maxChildHeight != 0) {
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxChildHeight, MeasureSpec.EXACTLY);
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+}
diff --git a/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java b/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
index 59e94ba..d91c16a 100644
--- a/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
+++ b/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
@@ -21,7 +21,7 @@
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.intent.Intents.intended;
 import static androidx.test.espresso.intent.Intents.intending;
-import static androidx.test.espresso.intent.matcher.ComponentNameMatchers.hasShortClassName;
+import static androidx.test.espresso.intent.matcher.ComponentNameMatchers.hasClassName;
 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
@@ -194,7 +194,7 @@
         onView(withId(R.id.wallpaper_grid)).perform(
                 RecyclerViewActions.actionOnItemAtPosition(0, click()));
         intended(allOf(
-                hasComponent(hasShortClassName(".picker.PreviewActivity")),
+                hasComponent(hasClassName("com.android.wallpaper.picker.PreviewActivity")),
                 hasExtra(equalTo(EXTRA_WALLPAPER_INFO), equalTo(sWallpaperInfo1))));
 
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();