Merge tag 'android-security-10.0.0_r53' into int/10/fp2

Android security 10.0.0 release 53

* tag 'android-security-10.0.0_r53':

Change-Id: Icad011c44b831172789ea805cb88418f221ae2ef
diff --git a/Android.mk b/Android.mk
index e7e051e..39eeeb6 100755
--- a/Android.mk
+++ b/Android.mk
@@ -16,24 +16,13 @@
 
 LOCAL_PATH := $(call my-dir)
 
-#
-# Prebuilt Java Libraries
-#
-include $(CLEAR_VARS)
-LOCAL_MODULE := libStyleProtos
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_SRC_FILES := libs/style_protos.jar
-LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_SDK_VERSION := current
-include $(BUILD_PREBUILT)
-
 include $(CLEAR_VARS)
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_MODULE := wallpaper2-glide-target
 LOCAL_SDK_VERSION := current
 LOCAL_SRC_FILES := ../../../prebuilts/maven_repo/bumptech/com/github/bumptech/glide/glide/SNAPSHOT/glide-SNAPSHOT$(COMMON_JAVA_PACKAGE_SUFFIX)
 LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_JETIFIER_ENABLED := true
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
@@ -64,7 +53,7 @@
 LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MIN_SDK_VERSION := 26
 LOCAL_MODULE := wallpaper-subsampling-scale-image-view
 LOCAL_MANIFEST_FILE := ../../../external/subsampling-scale-image-view/library/src/main/AndroidManifest.xml
 
@@ -82,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 \
@@ -99,14 +89,7 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_MANIFEST_FILE := AndroidManifest.xml
 
-ifneq (,$(wildcard frameworks/base))
-  LOCAL_STATIC_JAVA_LIBRARIES += SystemUISharedLib styleprotosnano
-  LOCAL_PRIVATE_PLATFORM_APIS := true
-else
-  LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI libStyleProtos
-  LOCAL_SDK_VERSION := current
-endif
-
+LOCAL_SDK_VERSION := current
 LOCAL_MODULE := WallpaperPicker2CommonDepsLib
 LOCAL_PRIVILEGED_MODULE := true
 
@@ -131,12 +114,18 @@
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 LOCAL_PROGUARD_ENABLED := disabled
 
+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
 
 include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0192398..6096d10 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
       android:name="com.android.wallpaper.NOTIFY_ROTATING_WALLPAPER_CHANGED" />
 
   <application
-      tools:replace="android:name"
+      tools:replace="android:icon,android:name"
       android:allowBackup="true"
       android:icon="@mipmap/product_logo_wallpapers_launcher_color_48"
       android:label="@string/app_name"
@@ -37,7 +37,7 @@
     <activity android:name="com.android.wallpaper.picker.TopLevelPickerActivity"
         android:label="@string/app_name"
         android:theme="@style/WallpaperTheme"
-        android:resizeableActivity="true">
+        android:resizeableActivity="false">
       <intent-filter>
         <action android:name="android.intent.action.SET_WALLPAPER"/>
         <category android:name="android.intent.category.DEFAULT"/>
@@ -50,14 +50,13 @@
         android:label="@string/app_name">
       <intent-filter>
         <action android:name="android.intent.action.MAIN"/>
-        <category android:name="android.intent.category.LAUNCHER"/>
       </intent-filter>
     </activity-alias>
 
     <activity android:name="com.android.wallpaper.picker.individual.IndividualPickerActivity"
         android:label="@string/app_name"
         android:theme="@style/WallpaperTheme"
-        android:resizeableActivity="true"
+        android:resizeableActivity="false"
         android:parentActivityName="com.android.wallpaper.picker.TopLevelPickerActivity">
     </activity>
 
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..f3db20e
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,2 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/libs/style_protos.jar b/libs/style_protos.jar
deleted file mode 100644
index 8708555..0000000
--- a/libs/style_protos.jar
+++ /dev/null
Binary files differ
diff --git a/res/drawable/gradient_background.xml b/res/drawable/gradient_background.xml
new file mode 100644
index 0000000..78f013a
--- /dev/null
+++ b/res/drawable/gradient_background.xml
@@ -0,0 +1,23 @@
+<?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:startColor="@color/translucent_black_lighter"
+
+        android:endColor="@android:color/transparent"
+        />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_delete_24px.xml b/res/drawable/ic_delete_24px.xml
new file mode 100644
index 0000000..bc83f36
--- /dev/null
+++ b/res/drawable/ic_delete_24px.xml
@@ -0,0 +1,30 @@
+<!--
+     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.
+-->
+<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="M15,4V3H9v1H4v2h1v13c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V6h1V4H15zM17,19H7V6h10V19z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M9,8h2v9h-2z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M13,8h2v9h-2z"/>
+</vector>
diff --git a/res/drawable/ic_settings.xml b/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..f24a0a8
--- /dev/null
+++ b/res/drawable/ic_settings.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24.0dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24.0dp" >
+
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z" />
+
+</vector>
\ No newline at end of file
diff --git a/res/layout/activity_preview.xml b/res/layout/activity_preview.xml
index 3e121c5..a08260a 100755
--- a/res/layout/activity_preview.xml
+++ b/res/layout/activity_preview.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
@@ -22,4 +22,4 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-</RelativeLayout>
+</FrameLayout>
diff --git a/res/layout/fragment_category_picker.xml b/res/layout/fragment_category_picker.xml
index 51bf8d7..3c74180 100755
--- a/res/layout/fragment_category_picker.xml
+++ b/res/layout/fragment_category_picker.xml
@@ -26,6 +26,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:clipToPadding="false"
+        android:fitsSystemWindows="true"
         android:paddingLeft="@dimen/grid_padding"
         android:paddingTop="@dimen/grid_padding"
         android:scrollbarSize="@dimen/grid_padding"
diff --git a/res/layout/fragment_image_preview.xml b/res/layout/fragment_image_preview.xml
new file mode 100755
index 0000000..a7273fb
--- /dev/null
+++ b/res/layout/fragment_image_preview.xml
@@ -0,0 +1,85 @@
+<?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">
+
+  <ImageView
+    android:id="@+id/low_res_image"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:scaleType="centerCrop"
+    android:background="@android:color/black"/>
+
+  <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
+      android:id="@+id/full_res_image"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent" />
+
+  <androidx.core.widget.ContentLoadingProgressBar
+      android:id="@+id/loading_indicator"
+      style="@android:style/Widget.DeviceDefault.ProgressBar"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center"
+      android:indeterminate="true"/>
+
+  <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">
+
+        <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">
+          <include
+              layout="@layout/preview_page_info"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"/>
+        </FrameLayout>
+
+    </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/preview_gradient_background_height"
+        android:layout_gravity="top"
+        android:background="@drawable/gradient_background"/>
+    <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/fragment_live_preview.xml b/res/layout/fragment_live_preview.xml
new file mode 100755
index 0000000..0185272
--- /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">
+
+      <FrameLayout
+          android:id="@+id/loading"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:background="@android:color/black"
+          android:forceHasOverlappingRendering="false">
+
+          <androidx.core.widget.ContentLoadingProgressBar
+              android:id="@+id/loading_indicator"
+              style="@android:style/Widget.DeviceDefault.ProgressBar"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_gravity="center"
+              android:indeterminate="true"/>
+
+    </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>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/preview_gradient_background_height"
+            android:layout_gravity="top"
+            android:background="@drawable/gradient_background"/>
+
+        <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/fragment_preview.xml b/res/layout/fragment_preview.xml
deleted file mode 100755
index 82c481f..0000000
--- a/res/layout/fragment_preview.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<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">
-
-  <ImageView
-    android:id="@+id/low_res_image"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:scaleType="centerCrop"
-    android:background="@android:color/black"/>
-
-  <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
-      android:id="@+id/full_res_image"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent" />
-
-  <ImageView
-    android:id="@+id/loading_indicator"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:visibility="invisible"
-    android:fitsSystemWindows="false"/>
-
-  <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">
-
-        <include
-            layout="@layout/preview_page_info"
-            android:id="@+id/bottom_sheet"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            app:behavior_peekHeight="@dimen/preview_attribution_pane_collapsed_height"
-            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"/>
-
-    </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 587e120..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"
@@ -49,7 +48,7 @@
         android:layout_marginTop="@dimen/preview_attribution_pane_author_top_margin"
         android:forceHasOverlappingRendering="false"
         android:gravity="center"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead"
+        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Subtitle"
         android:textColor="@color/material_white_100"
         android:visibility="gone"/>
 
@@ -60,7 +59,8 @@
         android:minHeight="@dimen/preview_attribution_pane_description_height"
         android:forceHasOverlappingRendering="false"
         android:gravity="center_horizontal"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Caption"
+        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
+        android:textSize="12dp"
         android:textColor="@color/material_white_100"
         android:visibility="gone"/>
 
diff --git a/res/layout/preview_page_settings.xml b/res/layout/preview_page_settings.xml
new file mode 100644
index 0000000..4faebb6
--- /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_settings_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/menu/preview_menu.xml b/res/menu/preview_menu.xml
index 7c9d610..f83560e 100755
--- a/res/menu/preview_menu.xml
+++ b/res/menu/preview_menu.xml
@@ -20,4 +20,18 @@
           app:actionLayout="@layout/preview_action"
           app:showAsAction="always|withText"
           android:title="@string/preview"/>
+
+    <item
+        android:id="@+id/configure"
+        android:icon="@drawable/ic_settings"
+        app:showAsAction="ifRoom"
+        android:title="@string/configure_wallpaper"
+        android:visible="false"/>
+
+    <item
+        android:id="@+id/delete_wallpaper"
+        android:icon="@drawable/ic_delete_24px"
+        app:showAsAction="ifRoom"
+        android:title="@string/delete_live_wallpaper"
+        android:visible="false"/>
 </menu>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index ea86de2..da7170e 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Gesentreerde snoei"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Rek"</string>
     <string name="preview" msgid="1774602101743861071">"Voorskou"</string>
+    <string name="tab_info" msgid="818614080690111416">"Inligting"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Pasmaak"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Instellings …"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Vee uit"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Vee hierdie muurpapier op jou foon uit?"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index bca3e97..11a3912 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"መሃል ላይ ከርክም"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ወጥር"</string>
     <string name="preview" msgid="1774602101743861071">"ቅድመ-እይታ"</string>
+    <string name="tab_info" msgid="818614080690111416">"መረጃ"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"ብጁ አድርግ"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ቅንብሮች…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ሰርዝ"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"ይህ ልጣፍ ከስልክዎ ይሰረዝ?"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 2a6c183..cdbd47a 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -30,11 +30,11 @@
     <string name="rotating_wallpaper_presentation_mode_message" msgid="3361676041605733288">"خلفية يومية"</string>
     <string name="wallpaper_destination_both" msgid="1124197176741944063">"الشاشة الرئيسية وشاشة التأمين"</string>
     <string name="home_screen_message" msgid="106444102822522813">"الشاشة الرئيسية"</string>
-    <string name="lock_screen_message" msgid="1534506081955058013">"شاشة التأمين"</string>
+    <string name="lock_screen_message" msgid="1534506081955058013">"شاشة القفل"</string>
     <string name="home_and_lock_short_label" msgid="2937922943541927983">"الشاشة الرئيسية وشاشة التأمين"</string>
     <string name="set_wallpaper_dialog_message" msgid="6114951028768599417">"تعيين كخلفية"</string>
     <string name="set_wallpaper_home_screen_destination" msgid="7315594722013109354">"الشاشة الرئيسية"</string>
-    <string name="set_wallpaper_lock_screen_destination" msgid="6224685559375417945">"شاشة التأمين"</string>
+    <string name="set_wallpaper_lock_screen_destination" msgid="6224685559375417945">"شاشة القفل"</string>
     <string name="set_wallpaper_both_destination" msgid="6967226064958263939">"الشاشة الرئيسية وشاشة التأمين"</string>
     <string name="no_backup_image_wallpaper_label" msgid="6316627676107284851">"خلفية الصور التي يتم عرضها بالتناوب"</string>
     <string name="permission_needed_explanation" msgid="139166837541426823">"لعرض الخلفية الحالية هنا، يحتاج تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> إلى الوصول إلى مساحة تخزين الجهاز."</string>
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"توسيط مع اقتصاص المساحة الزائدة"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"توسيع للعرض بملء الشاشة"</string>
     <string name="preview" msgid="1774602101743861071">"معاينة"</string>
+    <string name="tab_info" msgid="818614080690111416">"معلومات"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"تخصيص"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"الإعدادات…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"حذف"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"هل تريد حذف هذه الخلفية من هاتفك؟"</string>
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 95a66fd..0d89701 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"মধ্য অংশৰ পৰা কৰা ক্ৰপ"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"প্ৰসাৰিত"</string>
     <string name="preview" msgid="1774602101743861071">"পূৰ্বদৰ্শন"</string>
+    <string name="tab_info" msgid="818614080690111416">"তথ্য"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"কাষ্টমাইজ কৰক"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ছেটিংসমূহ…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"মচক"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"এই ৱালপেপাৰখন আপোনাৰ ফ’নটোৰ পৰা মচিবনে?"</string>
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 162d0c9..412dca2 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Mərkəzi kəsim"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Dartın"</string>
     <string name="preview" msgid="1774602101743861071">"Önizləmə"</string>
+    <string name="tab_info" msgid="818614080690111416">"Məlumat"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Fərdiləşdirin"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Ayarlar…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Silin"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Bu divar kağızı telefondan silinsin?"</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index b005851..ada913d 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Opseci u centru"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Razvuci"</string>
     <string name="preview" msgid="1774602101743861071">"Pregled"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informacije"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Prilagodite"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Podešavanja…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Izbriši"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Želite da izbrišete ovu pozadinu sa telefona?"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index bfb434f..b8f7fd9 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Запоўніць"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Расцягнуць"</string>
     <string name="preview" msgid="1774602101743861071">"Перадпрагляд"</string>
+    <string name="tab_info" msgid="818614080690111416">"Інфармацыя"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Наладка"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Налады…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Выдаліць"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Выдаліць гэтыя шпалеры з тэлефона?"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 4953524..2e6d874 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Центриране с подрязване"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Разтегляне"</string>
     <string name="preview" msgid="1774602101743861071">"Визуализация"</string>
+    <string name="tab_info" msgid="818614080690111416">"Информация"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Персонализиране"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Настройки…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Изтриване"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Да се изтрие ли този тапет от телефона ви?"</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index d51734f..38818ba 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"সীমানার দিকে কাটছাঁট করা"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"প্রসারিত"</string>
     <string name="preview" msgid="1774602101743861071">"প্রিভিউ"</string>
+    <string name="tab_info" msgid="818614080690111416">"তথ্য"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"কাস্টমাইজ করুন"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"সেটিংস…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"মুছে দিন"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"আপনার ফোন থেকে এই ওয়ালপেপার মুছে ফেলতে চান?"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 167b77f..bd8682f 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Sredina s odrezanim rubovima"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Razvučeno"</string>
     <string name="preview" msgid="1774602101743861071">"Pregled"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informacije"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Prilagođavanje"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Postavke…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Izbriši"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Izbrisati ovu pozadinsku sliku s telefona?"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index b2fd98d..df8eec4 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centra i retalla"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Amplia"</string>
     <string name="preview" msgid="1774602101743861071">"Previsualitza"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informació"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalitza"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Configuració…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Suprimeix"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Vols suprimir aquest fons de pantalla del telèfon?"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index cebf392..c93f9a8 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Vycentrovat a oříznout"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Roztáhnout"</string>
     <string name="preview" msgid="1774602101743861071">"Náhled"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informace"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Přizpůsobení"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Nastavení…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Smazat"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Smazat tapetu z telefonu?"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 049affe..c4d4ca1 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centrer og beskær"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Stræk"</string>
     <string name="preview" msgid="1774602101743861071">"Forhåndsvisning"</string>
+    <string name="tab_info" msgid="818614080690111416">"Oplysninger"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Tilpas"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Indstillinger…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Slet"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Vil du slette denne baggrund på din telefon?"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 48bb948..4f0296b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Zentriert anpassen"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Strecken"</string>
     <string name="preview" msgid="1774602101743861071">"Vorschau"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informationen"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Anpassen"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Einstellungen…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Löschen"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Diesen Hintergrund von deinem Smartphone löschen?"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index d14cc2b..78007b5 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Περικοπή στο κέντρο"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Τέντωμα"</string>
     <string name="preview" msgid="1774602101743861071">"Προεπισκόπηση"</string>
+    <string name="tab_info" msgid="818614080690111416">"Πληροφορίες"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Προσαρμογή"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Ρυθμίσεις…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Διαγραφή"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Διαγραφή αυτής της ταπετσαρίας από το τηλέφωνό σας;"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 158ce10..638dda6 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centre crop"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Stretch"</string>
     <string name="preview" msgid="1774602101743861071">"Preview"</string>
+    <string name="tab_info" msgid="818614080690111416">"Info"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Customise"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Settings…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Delete"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Delete this wallpaper from your phone?"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..638dda6
--- /dev/null
+++ b/res/values-en-rCA/strings.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+     Copyright (C) 2017 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="wallpaper_app_name" msgid="1719889291772891695">"Wallpapers"</string>
+    <!-- no translation found for app_name (8773648973927541493) -->
+    <skip />
+    <string name="select_wallpaper_label" msgid="6989581259339646085">"Wallpaper categories"</string>
+    <string name="set_wallpaper_button_text" msgid="4426286890442731310">"Set Wallpaper"</string>
+    <string name="set_wallpaper_progress_message" msgid="7986528287618716715">"Setting wallpaper…"</string>
+    <string name="try_again" msgid="8278874823700921234">"Try again"</string>
+    <string name="set_wallpaper_error_message" msgid="6819986999041085130">"Unable to set wallpaper."</string>
+    <string name="load_wallpaper_error_message" msgid="7913278480467707374">"Unable to load wallpaper. The image is either corrupted or unavailable."</string>
+    <string name="static_wallpaper_presentation_mode_message" msgid="417940227049360906">"Currently set"</string>
+    <string name="rotating_wallpaper_presentation_mode_message" msgid="3361676041605733288">"Daily wallpaper"</string>
+    <string name="wallpaper_destination_both" msgid="1124197176741944063">"Home &amp; Lock screen"</string>
+    <string name="home_screen_message" msgid="106444102822522813">"Home screen"</string>
+    <string name="lock_screen_message" msgid="1534506081955058013">"Lock screen"</string>
+    <string name="home_and_lock_short_label" msgid="2937922943541927983">"Home &amp; Lock"</string>
+    <string name="set_wallpaper_dialog_message" msgid="6114951028768599417">"Set wallpaper"</string>
+    <string name="set_wallpaper_home_screen_destination" msgid="7315594722013109354">"Home screen"</string>
+    <string name="set_wallpaper_lock_screen_destination" msgid="6224685559375417945">"Lock screen"</string>
+    <string name="set_wallpaper_both_destination" msgid="6967226064958263939">"Home screen and lock screen"</string>
+    <string name="no_backup_image_wallpaper_label" msgid="6316627676107284851">"Rotating Image Wallpaper"</string>
+    <string name="permission_needed_explanation" msgid="139166837541426823">"To display the current wallpaper here, <xliff:g id="APP_NAME">%1$s</xliff:g> needs access to your device\'s storage."</string>
+    <string name="permission_needed_explanation_go_to_settings" msgid="3923551582092599609">"To display the current wallpaper here, Wallpapers needs access to your device’s storage.\n\nTo change this setting, go to the Permissions area of the Wallpapers’ app info."</string>
+    <string name="permission_needed_allow_access_button_label" msgid="1943133660612924306">"Allow access"</string>
+    <string name="no_backup_image_wallpaper_description" msgid="8303268619408738057">"Live wallpaper service for rotating wallpapers"</string>
+    <string name="daily_refresh_tile_title" msgid="3270456074558525091">"Daily wallpaper"</string>
+    <string name="daily_refresh_tile_subtitle" msgid="3976682014885446443">"Tap to turn on"</string>
+    <string name="start_rotation_dialog_body_live_wallpaper_needed" msgid="5132580257563846082">"Wallpaper will change automatically every day. To finish setup, tap &lt;strong&gt;Set wallpaper&lt;/strong&gt; on the next screen."</string>
+    <string name="start_rotation_dialog_wifi_only_option_message" msgid="3126269859713666225">"Download future wallpapers on Wi-Fi only"</string>
+    <string name="start_rotation_dialog_continue" msgid="276678987852274872">"Continue"</string>
+    <string name="start_rotation_progress_message" msgid="7872623873682262083">"Downloading first wallpaper…"</string>
+    <string name="start_rotation_error_message" msgid="3053799836719618972">"Unable to download first wallpaper. Please check your network settings and try again."</string>
+    <string name="start_rotation_dialog_body" msgid="7903554799046364916">"Wallpaper will change automatically every day"</string>
+    <string name="settings_button_label" msgid="8724734130079207955">"Settings"</string>
+    <string name="explore" msgid="7468719504199497281">"Explore"</string>
+    <string name="next_wallpaper" msgid="3911873152952596232">"Next wallpaper"</string>
+    <string name="wallpaper_disabled_message" msgid="7309484130562148185">"Setting a wallpaper is disabled on this device"</string>
+    <string name="wallpaper_disabled_by_administrator_message" msgid="1551430406714747884">"Setting a wallpaper is disabled by your device administrator"</string>
+    <string name="wallpaper_set_successfully_message" msgid="2958998799111688578">"Wallpaper set successfully"</string>
+    <string name="wallpapers_unavailable_offline_message" msgid="8136405438621689532">"You need an Internet connection to view wallpapers. Please connect and try again."</string>
+    <string name="currently_set_home_wallpaper_thumbnail" msgid="4022381436821898917">"Currently set home screen wallpaper thumbnail"</string>
+    <string name="currently_set_lock_wallpaper_thumbnail" msgid="2094209303934569997">"Currently set lock screen wallpaper thumbnail"</string>
+    <string name="currently_set_wallpaper_thumbnail" msgid="8651887838745545107">"Currently set wallpaper thumbnail"</string>
+    <string name="wallpaper_thumbnail" msgid="569931475923605974">"Wallpaper thumbnail"</string>
+    <string name="explore_home_screen" msgid="8756346794535765482">"Explore home screen wallpaper"</string>
+    <string name="explore_lock_screen" msgid="268938342103703665">"Explore lock screen wallpaper"</string>
+    <string name="refresh_daily_wallpaper_home_content_description" msgid="2770445044556164259">"Refresh daily home screen wallpaper"</string>
+    <string name="refresh_daily_wallpaper_content_description" msgid="4362142658237147583">"Refresh daily wallpaper"</string>
+    <string name="refreshing_daily_wallpaper_dialog_message" msgid="1975910873362855761">"Refreshing daily wallpaper…"</string>
+    <string name="refresh_daily_wallpaper_failed_message" msgid="4749879993812557166">"Failed to refresh daily wallpaper. Please check your network connection and try again."</string>
+    <string name="collapse_attribution_panel" msgid="4367971404848122275">"Collapse wallpaper info panel"</string>
+    <string name="expand_attribution_panel" msgid="6975094181456095915">"Expand wallpaper info panel"</string>
+    <string name="on_device_wallpapers_category_title" msgid="805819102071369004">"On-device wallpapers"</string>
+    <string name="on_device_wallpapers_category_title_desktop" msgid="316919420410065369">"On device"</string>
+    <string name="on_device_wallpaper_title" msgid="5262564748034629524">"Android wallpaper"</string>
+    <string name="live_wallpapers_category_title" msgid="1814374812192366349">"Live wallpapers"</string>
+    <string name="my_photos_category_title" msgid="4294567122144565273">"My photos"</string>
+    <string name="my_photos_generic_wallpaper_title" msgid="7002867526154631172">"My photo"</string>
+    <string name="fallback_wallpaper_title" msgid="6154655421012506001">"Wallpaper"</string>
+    <string name="app_not_found" msgid="4431461707854088231">"App isn\'t installed."</string>
+    <string name="center_wallpaper_position" msgid="4166894762352288883">"Centre"</string>
+    <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centre crop"</string>
+    <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Stretch"</string>
+    <string name="preview" msgid="1774602101743861071">"Preview"</string>
+    <string name="tab_info" msgid="818614080690111416">"Info"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Customise"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Settings…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Delete"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Delete this wallpaper from your phone?"</string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 158ce10..638dda6 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centre crop"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Stretch"</string>
     <string name="preview" msgid="1774602101743861071">"Preview"</string>
+    <string name="tab_info" msgid="818614080690111416">"Info"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Customise"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Settings…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Delete"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Delete this wallpaper from your phone?"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 158ce10..638dda6 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centre crop"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Stretch"</string>
     <string name="preview" msgid="1774602101743861071">"Preview"</string>
+    <string name="tab_info" msgid="818614080690111416">"Info"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Customise"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Settings…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Delete"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Delete this wallpaper from your phone?"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..81420be
--- /dev/null
+++ b/res/values-en-rXC/strings.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+     Copyright (C) 2017 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="wallpaper_app_name" msgid="1719889291772891695">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎Wallpapers‎‏‎‎‏‎"</string>
+    <!-- no translation found for app_name (8773648973927541493) -->
+    <skip />
+    <string name="select_wallpaper_label" msgid="6989581259339646085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎Wallpaper categories‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper_button_text" msgid="4426286890442731310">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎Set Wallpaper‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper_progress_message" msgid="7986528287618716715">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎Setting wallpaper…‎‏‎‎‏‎"</string>
+    <string name="try_again" msgid="8278874823700921234">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎Try again‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper_error_message" msgid="6819986999041085130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‎Unable to set wallpaper.‎‏‎‎‏‎"</string>
+    <string name="load_wallpaper_error_message" msgid="7913278480467707374">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎Unable to load wallpaper. The image is either corrupted or unavailable.‎‏‎‎‏‎"</string>
+    <string name="static_wallpaper_presentation_mode_message" msgid="417940227049360906">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‎‎Currently set‎‏‎‎‏‎"</string>
+    <string name="rotating_wallpaper_presentation_mode_message" msgid="3361676041605733288">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‎Daily wallpaper‎‏‎‎‏‎"</string>
+    <string name="wallpaper_destination_both" msgid="1124197176741944063">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎Home &amp; Lock screen‎‏‎‎‏‎"</string>
+    <string name="home_screen_message" msgid="106444102822522813">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎Home screen‎‏‎‎‏‎"</string>
+    <string name="lock_screen_message" msgid="1534506081955058013">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎Lock screen‎‏‎‎‏‎"</string>
+    <string name="home_and_lock_short_label" msgid="2937922943541927983">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎Home &amp; Lock‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper_dialog_message" msgid="6114951028768599417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‎Set wallpaper‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper_home_screen_destination" msgid="7315594722013109354">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎Home screen‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper_lock_screen_destination" msgid="6224685559375417945">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎Lock screen‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper_both_destination" msgid="6967226064958263939">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎Home screen and lock screen‎‏‎‎‏‎"</string>
+    <string name="no_backup_image_wallpaper_label" msgid="6316627676107284851">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎Rotating Image Wallpaper‎‏‎‎‏‎"</string>
+    <string name="permission_needed_explanation" msgid="139166837541426823">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎To display the current wallpaper here, ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ needs access to your device\'s storage.‎‏‎‎‏‎"</string>
+    <string name="permission_needed_explanation_go_to_settings" msgid="3923551582092599609">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‏‎‎‏‎To display the current wallpaper here, Wallpapers needs access to your device’s storage.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎To change this setting, go to the Permissions area of Wallpapers’ app info.‎‏‎‎‏‎"</string>
+    <string name="permission_needed_allow_access_button_label" msgid="1943133660612924306">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎Allow access‎‏‎‎‏‎"</string>
+    <string name="no_backup_image_wallpaper_description" msgid="8303268619408738057">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎Live wallpaper service for rotating wallpapers‎‏‎‎‏‎"</string>
+    <string name="daily_refresh_tile_title" msgid="3270456074558525091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎Daily wallpaper‎‏‎‎‏‎"</string>
+    <string name="daily_refresh_tile_subtitle" msgid="3976682014885446443">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‏‏‎Tap to turn on‎‏‎‎‏‎"</string>
+    <string name="start_rotation_dialog_body_live_wallpaper_needed" msgid="5132580257563846082">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎Wallpaper will change automatically every day. To finish setup, tap &lt;strong&gt;Set wallpaper&lt;/strong&gt; on the next screen.‎‏‎‎‏‎"</string>
+    <string name="start_rotation_dialog_wifi_only_option_message" msgid="3126269859713666225">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‎‎‏‎Download future wallpapers on Wi-Fi only‎‏‎‎‏‎"</string>
+    <string name="start_rotation_dialog_continue" msgid="276678987852274872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎Continue‎‏‎‎‏‎"</string>
+    <string name="start_rotation_progress_message" msgid="7872623873682262083">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎Downloading first wallpaper…‎‏‎‎‏‎"</string>
+    <string name="start_rotation_error_message" msgid="3053799836719618972">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‎‎‎Unable to download first wallpaper. Please check your network settings and try again.‎‏‎‎‏‎"</string>
+    <string name="start_rotation_dialog_body" msgid="7903554799046364916">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎Wallpaper will change automatically every day‎‏‎‎‏‎"</string>
+    <string name="settings_button_label" msgid="8724734130079207955">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎Settings‎‏‎‎‏‎"</string>
+    <string name="explore" msgid="7468719504199497281">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎Explore‎‏‎‎‏‎"</string>
+    <string name="next_wallpaper" msgid="3911873152952596232">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎Next wallpaper‎‏‎‎‏‎"</string>
+    <string name="wallpaper_disabled_message" msgid="7309484130562148185">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎Setting a wallpaper is disabled on this device‎‏‎‎‏‎"</string>
+    <string name="wallpaper_disabled_by_administrator_message" msgid="1551430406714747884">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎Setting a wallpaper is disabled by your device administrator‎‏‎‎‏‎"</string>
+    <string name="wallpaper_set_successfully_message" msgid="2958998799111688578">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎Wallpaper set successfully‎‏‎‎‏‎"</string>
+    <string name="wallpapers_unavailable_offline_message" msgid="8136405438621689532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎You need an Internet connection to view wallpapers. Please connect and try again.‎‏‎‎‏‎"</string>
+    <string name="currently_set_home_wallpaper_thumbnail" msgid="4022381436821898917">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‎Currently set home screen wallpaper thumbnail‎‏‎‎‏‎"</string>
+    <string name="currently_set_lock_wallpaper_thumbnail" msgid="2094209303934569997">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‏‎Currently set lock screen wallpaper thumbnail‎‏‎‎‏‎"</string>
+    <string name="currently_set_wallpaper_thumbnail" msgid="8651887838745545107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‎Currently set wallpaper thumbnail‎‏‎‎‏‎"</string>
+    <string name="wallpaper_thumbnail" msgid="569931475923605974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎Wallpaper thumbnail‎‏‎‎‏‎"</string>
+    <string name="explore_home_screen" msgid="8756346794535765482">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎Explore home screen wallpaper‎‏‎‎‏‎"</string>
+    <string name="explore_lock_screen" msgid="268938342103703665">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎Explore lock screen wallpaper‎‏‎‎‏‎"</string>
+    <string name="refresh_daily_wallpaper_home_content_description" msgid="2770445044556164259">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎Refresh daily home screen wallpaper‎‏‎‎‏‎"</string>
+    <string name="refresh_daily_wallpaper_content_description" msgid="4362142658237147583">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎Refresh daily wallpaper‎‏‎‎‏‎"</string>
+    <string name="refreshing_daily_wallpaper_dialog_message" msgid="1975910873362855761">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎Refreshing daily wallpaper…‎‏‎‎‏‎"</string>
+    <string name="refresh_daily_wallpaper_failed_message" msgid="4749879993812557166">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎Failed to refresh daily wallpaper. Please check your network connection and try again.‎‏‎‎‏‎"</string>
+    <string name="collapse_attribution_panel" msgid="4367971404848122275">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‎Collapse wallpaper info panel‎‏‎‎‏‎"</string>
+    <string name="expand_attribution_panel" msgid="6975094181456095915">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎Expand wallpaper info panel‎‏‎‎‏‎"</string>
+    <string name="on_device_wallpapers_category_title" msgid="805819102071369004">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎On-device wallpapers‎‏‎‎‏‎"</string>
+    <string name="on_device_wallpapers_category_title_desktop" msgid="316919420410065369">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‏‎On device‎‏‎‎‏‎"</string>
+    <string name="on_device_wallpaper_title" msgid="5262564748034629524">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎Android wallpaper‎‏‎‎‏‎"</string>
+    <string name="live_wallpapers_category_title" msgid="1814374812192366349">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎Live wallpapers‎‏‎‎‏‎"</string>
+    <string name="my_photos_category_title" msgid="4294567122144565273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎My photos‎‏‎‎‏‎"</string>
+    <string name="my_photos_generic_wallpaper_title" msgid="7002867526154631172">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎My photo‎‏‎‎‏‎"</string>
+    <string name="fallback_wallpaper_title" msgid="6154655421012506001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‎Wallpaper‎‏‎‎‏‎"</string>
+    <string name="app_not_found" msgid="4431461707854088231">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎App isn\'t installed.‎‏‎‎‏‎"</string>
+    <string name="center_wallpaper_position" msgid="4166894762352288883">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎Center‎‏‎‎‏‎"</string>
+    <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎Center crop‎‏‎‎‏‎"</string>
+    <string name="stretch_wallpaper_position" msgid="5002680983147456935">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‏‏‏‎Stretch‎‏‎‎‏‎"</string>
+    <string name="preview" msgid="1774602101743861071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‏‎‎‏‏‏‏‎Preview‎‏‎‎‏‎"</string>
+    <string name="tab_info" msgid="818614080690111416">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‎Info‎‏‎‎‏‎"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎Customize‎‏‎‎‏‎"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎Settings…‎‏‎‎‏‎"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎Delete‎‏‎‎‏‎"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎Delete this wallpaper from your phone?‎‏‎‎‏‎"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index a95e589..00e1822 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -28,7 +28,7 @@
     <string name="load_wallpaper_error_message" msgid="7913278480467707374">"No se puede cargar el fondo de pantalla. La imagen está dañada o no está disponible."</string>
     <string name="static_wallpaper_presentation_mode_message" msgid="417940227049360906">"Establecido actualmente"</string>
     <string name="rotating_wallpaper_presentation_mode_message" msgid="3361676041605733288">"Fondo de pantalla diario"</string>
-    <string name="wallpaper_destination_both" msgid="1124197176741944063">"Pantalla principal y bloqueada"</string>
+    <string name="wallpaper_destination_both" msgid="1124197176741944063">"Pantalla principal y de bloqueo"</string>
     <string name="home_screen_message" msgid="106444102822522813">"Pantalla principal"</string>
     <string name="lock_screen_message" msgid="1534506081955058013">"Pantalla bloqueada"</string>
     <string name="home_and_lock_short_label" msgid="2937922943541927983">"Pantalla principal y bloqueada"</string>
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Recortar y centrar"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Estirar"</string>
     <string name="preview" msgid="1774602101743861071">"Vista previa"</string>
+    <string name="tab_info" msgid="818614080690111416">"Información"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizar"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Configuración…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Borrar"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"¿Quieres borrar este fondo de pantalla del teléfono?"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index f28956d..f2bfa6e 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Recortar el centro"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Expandir"</string>
     <string name="preview" msgid="1774602101743861071">"Vista previa"</string>
+    <string name="tab_info" msgid="818614080690111416">"Información"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizar"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Ajustes…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Eliminar"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"¿Quieres eliminar este fondo de pantalla de tu teléfono?"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 8216c44..9b3ad6a 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Keskele kärpimine"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Venitamine"</string>
     <string name="preview" msgid="1774602101743861071">"Eelvaade"</string>
+    <string name="tab_info" msgid="818614080690111416">"Teave"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Kohandamine"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Seaded …"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Kustuta"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Kas soovite taustapildi telefonist kustutada?"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index ef4d945..f76681b 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Erdian, moztuta"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Pantailara egokituta"</string>
     <string name="preview" msgid="1774602101743861071">"Aurreikusi"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informazioa"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Pertsonalizatu"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Ezarpenak…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Ezabatu"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Telefonotik ezabatu nahi duzu horma-papera?"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 898c514..105ce8c 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"برش از مرکز برای پر کردن صفحه."</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"کشیدن برای پر کردن صفحه"</string>
     <string name="preview" msgid="1774602101743861071">"پیش‌نمایش"</string>
+    <string name="tab_info" msgid="818614080690111416">"اطلاعات"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"سفارشی کردن"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"تنظیمات…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"حذف"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"این کاغذدیواری از تلفن حذف شود؟"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 2a5d754..43980b4 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Keskirajaus"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Venytä"</string>
     <string name="preview" msgid="1774602101743861071">"Esikatselu"</string>
+    <string name="tab_info" msgid="818614080690111416">"Tiedot"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personoi"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Asetukset…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Poista"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Poistetaanko tämä taustakuva puhelimelta?"</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 539256a..0a263cb 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Rogné au centre"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Étiré"</string>
     <string name="preview" msgid="1774602101743861071">"Aperçu"</string>
+    <string name="tab_info" msgid="818614080690111416">"Info"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personnaliser"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Paramètres…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Supprimer"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Supprimer ce fond d\'écran de votre téléphone?"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f075e19..d4699bb 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Recadrer et centrer"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Étirer"</string>
     <string name="preview" msgid="1774602101743861071">"Prévisualiser"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informations"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personnaliser"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Paramètres…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Supprimer"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Supprimer ce fond d\'écran de votre téléphone ?"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index fc49812..30870fe 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Recortar no centro"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Estirar"</string>
     <string name="preview" msgid="1774602101743861071">"Mostrar vista previa"</string>
+    <string name="tab_info" msgid="818614080690111416">"Información"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizar"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Configuración…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Eliminar"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Queres eliminar este fondo de pantalla do teu teléfono?"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index e9fcf97..66c9ec5 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"મધ્યમાંથી કાપેલ"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ખેંચેલ"</string>
     <string name="preview" msgid="1774602101743861071">"પ્રીવ્યૂ કરો"</string>
+    <string name="tab_info" msgid="818614080690111416">"માહિતી"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"કસ્ટમાઇઝ કરો"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"સેટિંગ…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ડિલીટ કરો"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"તમારા ફોનમાંથી આ વૉલપેપર ડિલીટ કરીએ?"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 269f05d..c1b55ed 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -37,9 +37,9 @@
     <string name="set_wallpaper_lock_screen_destination" msgid="6224685559375417945">"लॉक स्‍क्रीन"</string>
     <string name="set_wallpaper_both_destination" msgid="6967226064958263939">"होम स्क्रीन और लॉक स्क्रीन"</string>
     <string name="no_backup_image_wallpaper_label" msgid="6316627676107284851">"तय समय के बाद बदलने वाला इमेज वॉलपेपर"</string>
-    <string name="permission_needed_explanation" msgid="139166837541426823">"यहां पर मौजूदा वॉलपेपर दिखाने के लिए, <xliff:g id="APP_NAME">%1$s</xliff:g> को आपके डिवाइस की मेमोरी का एक्सेस चाहिए."</string>
-    <string name="permission_needed_explanation_go_to_settings" msgid="3923551582092599609">"यहां पर मौजूदा वॉलपेपर दिखाने के लिए, वॉलपेपर को आपके डिवाइस की मेमोरी का एक्सेस चाहिए. \n\nवॉलपेपर ऐप्लिकेशन की जानकारी के मंज़ूरी वाले विकल्प पर जाकर आप यह सेटिंग बदल सकते हैं."</string>
-    <string name="permission_needed_allow_access_button_label" msgid="1943133660612924306">"एक्सेस दें"</string>
+    <string name="permission_needed_explanation" msgid="139166837541426823">"यहां पर मौजूदा वॉलपेपर दिखाने के लिए, <xliff:g id="APP_NAME">%1$s</xliff:g> को आपके डिवाइस की मेमोरी का ऐक्सेस चाहिए."</string>
+    <string name="permission_needed_explanation_go_to_settings" msgid="3923551582092599609">"यहां पर मौजूदा वॉलपेपर दिखाने के लिए, वॉलपेपर को आपके डिवाइस की मेमोरी का ऐक्सेस चाहिए. \n\nवॉलपेपर ऐप्लिकेशन की जानकारी के मंज़ूरी वाले विकल्प पर जाकर आप यह सेटिंग बदल सकते हैं."</string>
+    <string name="permission_needed_allow_access_button_label" msgid="1943133660612924306">"ऐक्सेस दें"</string>
     <string name="no_backup_image_wallpaper_description" msgid="8303268619408738057">"तय समय के बाद बदलने वाले वॉलपेपर के लिए लाइव वॉलपेपर सेवा"</string>
     <string name="daily_refresh_tile_title" msgid="3270456074558525091">"रोज़ वॉलपेपर बदलने की सुविधा"</string>
     <string name="daily_refresh_tile_subtitle" msgid="3976682014885446443">"चालू करने के लिए टैप करें"</string>
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"बीच का हिस्सा रखें"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"स्क्रीन के हिसाब से खींचें"</string>
     <string name="preview" msgid="1774602101743861071">"झलक"</string>
+    <string name="tab_info" msgid="818614080690111416">"जानकारी"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"पसंद के मुताबिक बनाएं"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"सेटिंग…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"मिटाएं"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"क्या आप अपने फ़ोन से यह वॉलपेपर मिटाना चाहते हैं?"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index baa985b..b3f96b4 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centriranje s obrezivanjem"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Rastezanje"</string>
     <string name="preview" msgid="1774602101743861071">"Pregled"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informacije"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Prilagodba"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Postavke…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Izbriši"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Želite li izbrisati tu pozadinu s telefona?"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index de05be5..df08a11 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Középre igazítás körülvágással"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Nyújtás"</string>
     <string name="preview" msgid="1774602101743861071">"Előnézet"</string>
+    <string name="tab_info" msgid="818614080690111416">"Információ"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Személyre szabás"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Beállítások…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Törlés"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Törli ezt a háttérképet a telefonjáról?"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 9ea0696..cf12757 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Կենտրոնում, կտրած"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Ձգել"</string>
     <string name="preview" msgid="1774602101743861071">"Նախադիտել"</string>
+    <string name="tab_info" msgid="818614080690111416">"Տեղեկություն"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Կարգավորել"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Կարգավորումներ"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Ջնջել"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Ջնջե՞լ այս պաստառը հեռախոսից։"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 565f9c4..acef669 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -42,8 +42,8 @@
     <string name="permission_needed_allow_access_button_label" msgid="1943133660612924306">"Izinkan akses"</string>
     <string name="no_backup_image_wallpaper_description" msgid="8303268619408738057">"Layanan wallpaper animasi untuk memutar wallpaper"</string>
     <string name="daily_refresh_tile_title" msgid="3270456074558525091">"Wallpaper harian"</string>
-    <string name="daily_refresh_tile_subtitle" msgid="3976682014885446443">"Tap untuk mengaktifkan"</string>
-    <string name="start_rotation_dialog_body_live_wallpaper_needed" msgid="5132580257563846082">"Wallpaper akan otomatis berubah setiap hari. Untuk menyelesaikan penyiapan, tap &lt;strong&gt;Setel wallpaper&lt;/strong&gt; di layar selanjutnya."</string>
+    <string name="daily_refresh_tile_subtitle" msgid="3976682014885446443">"Ketuk untuk mengaktifkan"</string>
+    <string name="start_rotation_dialog_body_live_wallpaper_needed" msgid="5132580257563846082">"Wallpaper akan otomatis berubah setiap hari. Untuk menyelesaikan penyiapan, ketuk &lt;strong&gt;Setel wallpaper&lt;/strong&gt; di layar selanjutnya."</string>
     <string name="start_rotation_dialog_wifi_only_option_message" msgid="3126269859713666225">"Download wallpaper mendatang hanya melalui Wi-Fi"</string>
     <string name="start_rotation_dialog_continue" msgid="276678987852274872">"Lanjutkan"</string>
     <string name="start_rotation_progress_message" msgid="7872623873682262083">"Mendownload wallpaper pertama…"</string>
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Crop tengah"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Regang"</string>
     <string name="preview" msgid="1774602101743861071">"Pratinjau"</string>
+    <string name="tab_info" msgid="818614080690111416">"Info"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Sesuaikan"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Setelan…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Hapus"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Hapus wallpaper ini dari ponsel Anda?"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7a1d53f..a80fc19 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Miðjuskurður"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Teygja"</string>
     <string name="preview" msgid="1774602101743861071">"Forskoða"</string>
+    <string name="tab_info" msgid="818614080690111416">"Upplýsingar"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Sérsníða"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Stillingar…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Eyða"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Viltu eyða þessu veggfóðri úr símanum?"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 2d8cc15..cbf5531 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Ritaglia al centro"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Allunga"</string>
     <string name="preview" msgid="1774602101743861071">"Anteprima"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informazioni"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizza"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Impostazioni…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Elimina"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Vuoi eliminare questo sfondo dal telefono?"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index d8a697f..e35a08e 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ממורכז וחתוך"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"מתוח"</string>
     <string name="preview" msgid="1774602101743861071">"תצוגה מקדימה"</string>
+    <string name="tab_info" msgid="818614080690111416">"מידע"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"התאמה אישית"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"הגדרות…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"מחיקה"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"למחוק את הטפט הזה מהטלפון?"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index bc84a91..d88d496 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"中央で切り抜き"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"拡大"</string>
     <string name="preview" msgid="1774602101743861071">"プレビュー"</string>
+    <string name="tab_info" msgid="818614080690111416">"情報"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"カスタマイズ"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"設定…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"削除"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"スマートフォンからこの壁紙を削除しますか?"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 9a41fc9..907b5af 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ცენტრში ჩამოჭრა"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"გადაჭიმვა"</string>
     <string name="preview" msgid="1774602101743861071">"გადახედვა"</string>
+    <string name="tab_info" msgid="818614080690111416">"ინფორმაცია"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"მორგება"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"პარამეტრები..."</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"წაშლა"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"წაიშალოს ეს ფონი თქვენი ტელეფონიდან?"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 03094c0..9384fbe 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Ортасынан қию"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Созу"</string>
     <string name="preview" msgid="1774602101743861071">"Алдын ала қарау"</string>
+    <string name="tab_info" msgid="818614080690111416">"Ақпарат"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Реттеу"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Параметрлер…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Жою"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Бұл тұсқағаз телефоннан жойылсын ба?"</string>
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index dba3d35..bf89c50 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ច្រឹប​កណ្តាល"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ទាញ"</string>
     <string name="preview" msgid="1774602101743861071">"មើលសាកល្បង"</string>
+    <string name="tab_info" msgid="818614080690111416">"ព័ត៌មាន"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"ប្ដូរ​តាម​បំណង"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ការកំណត់…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"លុប"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"លុប​ផ្ទាំងរូបភាពនេះ​ពី​ទូរសព្ទរបស់អ្នកដែរទេ?"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index c6cf1d4..f2c5564 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ಮಧ್ಯಕ್ಕೆ ಕ್ರಾಪ್ ಮಾಡಿ"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ವಿಸ್ತರಿಸಿ"</string>
     <string name="preview" msgid="1774602101743861071">"ಪೂರ್ವವೀಕ್ಷಣೆ"</string>
+    <string name="tab_info" msgid="818614080690111416">"ಮಾಹಿತಿ"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"ಕಸ್ಟಮೈಜ್‌ ಮಾಡಿ"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು..."</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ಅಳಿಸಿ"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"ನಿಮ್ಮ ಫೋನ್‌ನಿಂದ ಈ ವಾಲ್‌ಪೇಪರ್ ಅನ್ನು ಅಳಿಸುವುದೇ?"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 6f10f4f..9e9736f 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"중앙 자르기"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"확대"</string>
     <string name="preview" msgid="1774602101743861071">"미리보기"</string>
+    <string name="tab_info" msgid="818614080690111416">"정보"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"맞춤설정"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"설정…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"삭제"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"휴대전화에서 이 배경화면을 삭제하시겠습니까?"</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index b56b372..8c95a09 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Ортосуна тууралап кесүү"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Чоюу"</string>
     <string name="preview" msgid="1774602101743861071">"Алдын ала көрүү"</string>
+    <string name="tab_info" msgid="818614080690111416">"Маалымат"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Ыңгайлаштыруу"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Жөндөөлөр…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Жок кылуу"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Бул тушкагаз телефонуңуздан өчүрүлсүнбү?"</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 1f5bc34..d0d533f 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ຕັດເຄິ່ງກາງ"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ຍືດ"</string>
     <string name="preview" msgid="1774602101743861071">"ຕົວຢ່າງ"</string>
+    <string name="tab_info" msgid="818614080690111416">"ຂໍ້ມູນ"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"ປັບແຕ່ງ"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ການຕັ້ງຄ່າ…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ລຶບອອກ"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"ລຶບຮູບພື້ນຫຼັງນີ້ອອກຈາກໂທລະສັບຂອງທ່ານບໍ?"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 80fa37e..ddba93e 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Apkirpti centre"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Ištempti"</string>
     <string name="preview" msgid="1774602101743861071">"Peržiūra"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informacija"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Tinkinimas"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Nustatymai…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Ištrinti"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Ištrinti šį ekrano foną iš telefono?"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 318c0f8..0aeed74 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Apgriezt centrā"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Pielāgot"</string>
     <string name="preview" msgid="1774602101743861071">"Priekšskatīt"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informācija"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Pielāgošana"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Iestatījumi…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Dzēst"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Vai dzēst šo fona tapeti no tālruņa?"</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 9f2901f..315becc 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Исечи централно"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Растегни"</string>
     <string name="preview" msgid="1774602101743861071">"Преглед"</string>
+    <string name="tab_info" msgid="818614080690111416">"Информации"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Приспособување"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Поставки…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Избриши"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Да се избрише тапетов од телефонот?"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index f70fbf0..09af34c 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"നടുവിൽ വലുപ്പം മാറ്റുക"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"വലിച്ചുനീട്ടുക"</string>
     <string name="preview" msgid="1774602101743861071">"പ്രിവ്യൂ"</string>
+    <string name="tab_info" msgid="818614080690111416">"വിവരം"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"ഇഷ്‌ടാനുസൃതമാക്കുക"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ക്രമീകരണം…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ഇല്ലാതാക്കുക"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഈ വാൾപേപ്പർ ഇല്ലാതാക്കണോ?"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 7352b7c..1009b28 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Голлуулж тайрах"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Сунгах"</string>
     <string name="preview" msgid="1774602101743861071">"Урьдчилан үзэх"</string>
+    <string name="tab_info" msgid="818614080690111416">"Мэдээлэл"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Өөрчлөх"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Тохиргоо…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Устгах"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Энэ ханын зургийг утаснаасаа устгах уу?"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index bc7de36..a45a507 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -37,9 +37,9 @@
     <string name="set_wallpaper_lock_screen_destination" msgid="6224685559375417945">"लॉक स्क्रीन"</string>
     <string name="set_wallpaper_both_destination" msgid="6967226064958263939">"होम स्क्रीन आणि लॉक स्क्रीन"</string>
     <string name="no_backup_image_wallpaper_label" msgid="6316627676107284851">"फिरती इमेज वॉलपेपर"</string>
-    <string name="permission_needed_explanation" msgid="139166837541426823">"येथे सध्याचा वॉलपेपर दाखवण्यासाठी, <xliff:g id="APP_NAME">%1$s</xliff:g> ला तुमच्या डिव्हाइसच्या स्टोरेजचा अॅक्सेस हवा आहे."</string>
-    <string name="permission_needed_explanation_go_to_settings" msgid="3923551582092599609">"येथे सध्याचा वॉलपेपर दाखवण्यासाठी, वॉलपेपरला तुमच्या डिव्हाइसच्या स्टोरेजचा अॅक्सेस हवा आहे.\n\nहे सेटिंग बदलण्यासाठी, वॉलपेपरच्या ॲप माहितीच्या परवानग्या भागावर जा."</string>
-    <string name="permission_needed_allow_access_button_label" msgid="1943133660612924306">"अॅक्सेस द्या"</string>
+    <string name="permission_needed_explanation" msgid="139166837541426823">"येथे सध्याचा वॉलपेपर दाखवण्यासाठी, <xliff:g id="APP_NAME">%1$s</xliff:g> ला तुमच्या डिव्हाइसच्या स्टोरेजचा ॲक्सेस हवा आहे."</string>
+    <string name="permission_needed_explanation_go_to_settings" msgid="3923551582092599609">"येथे सध्याचा वॉलपेपर दाखवण्यासाठी, वॉलपेपरला तुमच्या डिव्हाइसच्या स्टोरेजचा ॲक्सेस हवा आहे.\n\nहे सेटिंग बदलण्यासाठी, वॉलपेपरच्या ॲप माहितीच्या परवानग्या भागावर जा."</string>
+    <string name="permission_needed_allow_access_button_label" msgid="1943133660612924306">"ॲक्सेस द्या"</string>
     <string name="no_backup_image_wallpaper_description" msgid="8303268619408738057">"फिरत्या वॉलपेपरसाठी लाइव्ह वॉलपेपर सेवा"</string>
     <string name="daily_refresh_tile_title" msgid="3270456074558525091">"दैनिक वॉलपेपर"</string>
     <string name="daily_refresh_tile_subtitle" msgid="3976682014885446443">"चालू करण्यासाठी टॅप करा"</string>
@@ -53,7 +53,7 @@
     <string name="explore" msgid="7468719504199497281">"एक्सप्लोर करा"</string>
     <string name="next_wallpaper" msgid="3911873152952596232">"पुढील वॉलपेपर"</string>
     <string name="wallpaper_disabled_message" msgid="7309484130562148185">"या डिव्हाइसवर वॉलपेपर सेट करणे बंद केलेले आहे"</string>
-    <string name="wallpaper_disabled_by_administrator_message" msgid="1551430406714747884">"तुमच्या डिव्हाइस अॅडमिनिस्ट्रेटरने वॉलपेपर सेट करणे बंद केले आहे"</string>
+    <string name="wallpaper_disabled_by_administrator_message" msgid="1551430406714747884">"तुमच्या डिव्हाइस ॲडमिनिस्ट्रेटरने वॉलपेपर सेट करणे बंद केले आहे"</string>
     <string name="wallpaper_set_successfully_message" msgid="2958998799111688578">"वॉलपेपर यशस्वीरीत्या सेट झाला"</string>
     <string name="wallpapers_unavailable_offline_message" msgid="8136405438621689532">"वॉलपेपर पाहण्यासाठी तुमच्याकडे इंटरनेट कनेक्शन असणे गरजेचे आहे. कृपया कनेक्ट करा आणि पुन्हा प्रयत्न करा."</string>
     <string name="currently_set_home_wallpaper_thumbnail" msgid="4022381436821898917">"सध्या होम स्क्रीन वॉलपेपर थंबनेल सेट केले आहे"</string>
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"मध्यभागी क्रॉप केलेले"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ताणा"</string>
     <string name="preview" msgid="1774602101743861071">"पूर्वावलोकन करा"</string>
+    <string name="tab_info" msgid="818614080690111416">"माहिती"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"कस्टमाइझ करा"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"सेटिंग्ज…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"हटवा"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"तुमच्या फोनवरून हा वॉलपेपर हटवायचा आहे का?"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index d10b20d..84bad3d 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Pangkas ke tengah"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Regang"</string>
     <string name="preview" msgid="1774602101743861071">"Pratonton"</string>
+    <string name="tab_info" msgid="818614080690111416">"Maklumat"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Sesuaikan"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Tetapan..."</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Padam"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Padamkan kertas dinding ini daripada telefon anda?"</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index b5d4855..6b8ae70 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"အလယ်တွင် ဖြတ်ညှိရန်"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ဆွဲဆန့်ရန်"</string>
     <string name="preview" msgid="1774602101743861071">"အစမ်းကြည့်ရန်"</string>
+    <string name="tab_info" msgid="818614080690111416">"အချက်အလက်"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"စိတ်ကြိုက်ပြုလုပ်ရန်"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ဆက်တင်များ…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ဖျက်ရန်"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"ဤနောက်ခံပုံကို သင့်ပင်မစာမျက်နှာမှ ဖျက်ပစ်မလား။"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 2028c9d..14da40b 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Midtstilt beskjæring"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Strekk"</string>
     <string name="preview" msgid="1774602101743861071">"Ta en forhåndskikk"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informasjon"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Tilpass"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Innstillinger"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Slett"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Vil du slette denne bakgrunnen fra telefonen din?"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 28a1052..e3ec544 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"मध्यभागमा क्रप गरिएको"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"तन्काइएको"</string>
     <string name="preview" msgid="1774602101743861071">"पूर्वावलोकन"</string>
+    <string name="tab_info" msgid="818614080690111416">"जानकारी"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"आफू अनुकूल पार्नुहोस्"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"सेटिङ…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"मेट्नुहोस्"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"तपाईंको फोनबाट यो वालपेपर मेटाउने हो?"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 543e654..8c259e2 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Gecentreerd bijsnijden"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Uitrekken"</string>
     <string name="preview" msgid="1774602101743861071">"Voorbeeld"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informatie"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Aanpassen"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Instellingen…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Verwijderen"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Wil je deze achtergrond verwijderen van je telefoon?"</string>
 </resources>
diff --git a/res/values-notnight-v27/styles.xml b/res/values-notnight-v27/styles.xml
index 683d09d..e090c4c 100755
--- a/res/values-notnight-v27/styles.xml
+++ b/res/values-notnight-v27/styles.xml
@@ -20,10 +20,8 @@
         <item name="colorPrimary">?android:colorPrimary</item>
         <item name="colorControlActivated">?attr/colorPrimary</item>
         <item name="colorControlNormal">?android:attr/colorPrimary</item>
-        <item name="android:statusBarColor">?attr/colorPrimary</item>
-        <item name="android:windowLightStatusBar">true</item>
 
-        <item name="android:navigationBarColor">?android:colorPrimaryDark</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:navigationBarDividerColor">@android:color/transparent</item>
         <item name="android:windowLightNavigationBar">true</item>
 
@@ -39,8 +37,6 @@
 
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
-        <item name="android:fitsSystemWindows">false</item>
-        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
     </style>
 
     <!-- Dialog themes -->
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 32e791e..6bbea17 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ସେଣ୍ଟର୍ କ୍ରପ୍"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ପ୍ରସାରିତ କରନ୍ତୁ"</string>
     <string name="preview" msgid="1774602101743861071">"ପ୍ରିଭ୍ୟୁ"</string>
+    <string name="tab_info" msgid="818614080690111416">"ସୂଚନା"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"କଷ୍ଟମାଇଜ୍ କରନ୍ତୁ"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ସେଟିଂସ୍..."</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ଡିଲିଟ୍ କରନ୍ତୁ"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"ଆପଣଙ୍କର ଫୋନ୍‌ରୁ ଏହି ୱାଲ୍‌ପେପର୍‌କୁ ଡିଲିଟ୍ କରିବେ କି?"</string>
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 9d8f5f7..ebeed97 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ਵਿਚਕਾਰੋਂ ਕਾਂਟ-ਛਾਂਟ ਕਰੋ"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ਫੈਲਾਓ"</string>
     <string name="preview" msgid="1774602101743861071">"ਪੂਰਵ-ਝਲਕ"</string>
+    <string name="tab_info" msgid="818614080690111416">"ਜਾਣਕਾਰੀ"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ਸੈਟਿੰਗਾਂ…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ਮਿਟਾਓ"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"ਕੀ ਇਹ ਵਾਲਪੇਪਰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਮਿਟਾਉਣਾ ਹੈ?"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 82d7408..9801576 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Przycięta na środku"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Rozciągnięcie"</string>
     <string name="preview" msgid="1774602101743861071">"Podgląd"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informacje"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Dostosuj"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Ustawienia…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Usuń"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Usunąć tę tapetę z telefonu?"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 3ba961d..59b2054 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Recortar e centrar"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Esticar"</string>
     <string name="preview" msgid="1774602101743861071">"Pré-visualizar"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informações"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizar"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Definições…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Eliminar"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Pretende eliminar esta imagem de fundo do telemóvel?"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index f26164b..1589064 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Corte central"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Expandir"</string>
     <string name="preview" msgid="1774602101743861071">"Visualizar"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informações"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizar"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Configurações…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Excluir"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Excluir esse plano de fundo do seu smartphone?"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 0766d1a..4ec0ea1 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Decupare în centru"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Extindeți"</string>
     <string name="preview" msgid="1774602101743861071">"Previzualizați"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informații"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizați"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Setări…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Ștergeți"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Ștergeți această imagine de fundal de pe telefon?"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 1421a1b..264f88a 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Заполнить"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Растянуть"</string>
     <string name="preview" msgid="1774602101743861071">"Предварительный просмотр"</string>
+    <string name="tab_info" msgid="818614080690111416">"Информация"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Настройка"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Настройки…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Удалить"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Удалить эти обои с телефона?"</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 8022ce9..b7d767e 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"මැද කප්පාදු කිරීම"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"අදින්න"</string>
     <string name="preview" msgid="1774602101743861071">"පෙරදසුන"</string>
+    <string name="tab_info" msgid="818614080690111416">"තතු"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"අභිරුචිකරණය කරන්න"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"සැකසීම්…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"මකන්න"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"මෙම බිතුපත ඔබේ දුරකථනයෙන් මකන්නද?"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index d12ddd0..60a2bdb 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Vycentrovať a orezať"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Roztiahnuť"</string>
     <string name="preview" msgid="1774602101743861071">"Zobraziť ukážku"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informácie"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Prispôsobenie"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Nastavenia…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Odstrániť"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Chcete túto tapetu odstrániť z telefónu?"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index dae44d7..b2e0434 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Na sredini obrezano"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Raztegnjeno"</string>
     <string name="preview" msgid="1774602101743861071">"Predogled"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informacije"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Prilagajanje"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Nastavitve …"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Izbriši"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Ali želite to ozadje izbrisati iz telefona?"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index e35ad48..6b4b090 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Prerje qendrore"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"E tendosur"</string>
     <string name="preview" msgid="1774602101743861071">"Shiko paraprakisht"</string>
+    <string name="tab_info" msgid="818614080690111416">"Informacione"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Personalizo"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Cilësimet…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Fshi"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Do ta fshish këtë imazh sfondi nga telefoni?"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 6c26b0a..0cc71af 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Опсеци у центру"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Развуци"</string>
     <string name="preview" msgid="1774602101743861071">"Преглед"</string>
+    <string name="tab_info" msgid="818614080690111416">"Информације"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Прилагодите"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Подешавања…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Избриши"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Желите да избришете ову позадину са телефона?"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 54bc2cb..7fcbd57 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Centrera och beskär"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Dra ut"</string>
     <string name="preview" msgid="1774602101743861071">"Förhandsgranska"</string>
+    <string name="tab_info" msgid="818614080690111416">"Information"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Anpassa"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Inställningar…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Radera"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Vill du radera den här bakgrunden från mobilen?"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 17734e1..7221e1f 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Punguza katikati"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Panua"</string>
     <string name="preview" msgid="1774602101743861071">"Kagua kwanza"</string>
+    <string name="tab_info" msgid="818614080690111416">"Maelezo"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Weka mapendeleo"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Mipangilio…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Futa"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Je, ungependa kufuta mandhari haya kwenye simu yako?"</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 5be8064..75248b4 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"மையமாகச் செதுக்கு"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"திரையில் பொருந்தும்படி விரி"</string>
     <string name="preview" msgid="1774602101743861071">"மாதிரிக்காட்சி"</string>
+    <string name="tab_info" msgid="818614080690111416">"தகவல்"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"பிரத்தியேகமாக்கு"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"அமைப்புகள்…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"நீக்கு"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"மொபைலில் இருந்து இந்த வால்பேப்பரை நீக்கவா?"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 3db68b3..a5beb79 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"మధ్యన కత్తిరించు"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"విస్తరించు"</string>
     <string name="preview" msgid="1774602101743861071">"ప్రివ్యూ చూపు"</string>
+    <string name="tab_info" msgid="818614080690111416">"సమాచారం"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"అనుకూలీకరించండి"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"సెట్టింగ్‌లు…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"తొలగించు"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"మీ ఫోన్ నుండి ఈ వాల్‌పేపర్‌ను తొలగించాలా?"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 0aaa891..c86eb72 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"ครอบตัดกึ่งกลาง"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"ยืด"</string>
     <string name="preview" msgid="1774602101743861071">"แสดงพรีวิว"</string>
+    <string name="tab_info" msgid="818614080690111416">"ข้อมูล"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"ปรับแต่ง"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"การตั้งค่า…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"ลบ"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"ลบวอลเปเปอร์นี้ออกจากโทรศัพท์ไหม"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 7232a7e..6988601 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"I-crop sa gitna"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"I-stretch"</string>
     <string name="preview" msgid="1774602101743861071">"I-preview"</string>
+    <string name="tab_info" msgid="818614080690111416">"Impormasyon"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"I-customize"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Mga Setting…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"I-delete"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"I-delete ang wallpaper na ito sa iyong telepono?"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 1600f69..1d30e7e 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Ortalayarak kırp"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Genişlet"</string>
     <string name="preview" msgid="1774602101743861071">"Önizle"</string>
+    <string name="tab_info" msgid="818614080690111416">"Bilgi"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Özelleştir"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Ayarlar…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Sil"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Bu duvar kağıdı telefonunuzdan silinsin mi?"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 0d4fd12..1df810a 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Обрізати відносно центру"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Розтягнути"</string>
     <string name="preview" msgid="1774602101743861071">"Переглянути"</string>
+    <string name="tab_info" msgid="818614080690111416">"Інформація"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Налаштування"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Налаштування…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Видалити"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Видалити цей фоновий малюнок із телефона?"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index b4a5043..9d87add 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"مرکزی تراش"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"پھیلائیں"</string>
     <string name="preview" msgid="1774602101743861071">"پیش منظر"</string>
+    <string name="tab_info" msgid="818614080690111416">"معلومات"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"حسب ضرورت بنائیں"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"ترتیبات…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"حذف کریں"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"اس وال پیپر کو آپ کے فون سے حذف کریں؟"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index ede76e6..d0d316a 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Markazlab qirqish"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Yoyish"</string>
     <string name="preview" msgid="1774602101743861071">"Razm solish"</string>
+    <string name="tab_info" msgid="818614080690111416">"Axborot"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Sozlash"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Sozlamalar…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Oʻchirish"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Bu fon rasmi telefoningizdan oʻchirib tashlansinmi?"</string>
 </resources>
diff --git a/res/values-v29/styles.xml b/res/values-v29/styles.xml
new file mode 100644
index 0000000..6cae735
--- /dev/null
+++ b/res/values-v29/styles.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<resources>
+    <style name="PreviewCheckboxDeviceTheme" parent="@android:style/ThemeOverlay.DeviceDefault.Accent.DayNight">
+        <item name="android:colorControlActivated">?android:attr/colorAccent</item>
+        <item name="android:colorControlNormal">?android:attr/colorPrimary</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index f0c0176..b11245e 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Cắt giữa"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Kéo dài"</string>
     <string name="preview" msgid="1774602101743861071">"Xem trước"</string>
+    <string name="tab_info" msgid="818614080690111416">"Thông tin"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Tùy chỉnh"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Cài đặt…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Xóa"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Bạn muốn xóa hình nền này khỏi điện thoại của mình?"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 3795c39..5db994c 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"居中裁剪"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"拉伸"</string>
     <string name="preview" msgid="1774602101743861071">"预览"</string>
+    <string name="tab_info" msgid="818614080690111416">"信息"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"自定义"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"设置…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"删除"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"要从您的手机上删除此壁纸吗?"</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index f9ecc62..507758b 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"裁剪中間部分"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"延展"</string>
     <string name="preview" msgid="1774602101743861071">"預覽"</string>
+    <string name="tab_info" msgid="818614080690111416">"資訊"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"自訂"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"設定…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"刪除"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"要從手機中刪除此桌布嗎?"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index f6e67cf..ac79125 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"置中裁剪"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"延伸"</string>
     <string name="preview" msgid="1774602101743861071">"預覽"</string>
+    <string name="tab_info" msgid="818614080690111416">"資訊"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"自訂"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"設定…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"刪除"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"要從手機中刪除這張桌布嗎?"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 2db2a33..98625b6 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -80,4 +80,9 @@
     <string name="center_crop_wallpaper_position" msgid="1681980019815343348">"Nqampuna phakathi kwendawo"</string>
     <string name="stretch_wallpaper_position" msgid="5002680983147456935">"Nweba"</string>
     <string name="preview" msgid="1774602101743861071">"Buka kuqala"</string>
+    <string name="tab_info" msgid="818614080690111416">"Ulwazi"</string>
+    <string name="tab_customize" msgid="2533745409174959960">"Enza ngendlela oyifisayo"</string>
+    <string name="configure_wallpaper" msgid="849882179182976621">"Izilungiselelo…"</string>
+    <string name="delete_live_wallpaper" msgid="589212696102662329">"Susa"</string>
+    <string name="delete_wallpaper_confirmation" msgid="33790318361863778">"Landa lesi sithombe esingemuva kusukela kufoni yakho?"</string>
 </resources>
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..0201f96 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -300,4 +300,19 @@
 
     <!-- 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>
+
+    <!-- List item for configuring the current wallpaper [CHAR LIMIT=30] -->
+    <string name="configure_wallpaper">Settings…</string>
+
+    <!-- List item for deleting the current wallpaper [CHAR LIMIT=30] -->
+    <string name="delete_live_wallpaper">Delete</string>
+
+    <!-- Confirmation dialog. Shown after user selects to delete one wallpaper. [CHAR LIMIT=NONE] -->
+    <string name="delete_wallpaper_confirmation">Delete this wallpaper from your phone?</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 45b6d02..3ea8c33 100755
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:tools="http://schemas.android.com/tools" tools:targetApi="23">
+<resources>
 
     <!-- Main themes -->
     <style name="WallpaperTheme" parent="@android:style/Theme.DeviceDefault.Settings">
@@ -40,7 +40,10 @@
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
     </style>
 
-    <style name="WallpaperTheme.Preview" parent="@style/Theme.AppCompat.NoActionBar">
+    <style name="WallpaperTheme.Preview" parent="@android:style/Theme.DeviceDefault.Settings">
+        <item name="android:colorPrimary">@color/material_white_100</item>
+        <item name="colorPrimary">@color/material_white_100</item>
+        <item name="colorControlActivated">?attr/colorPrimary</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowDisablePreview">true</item>
@@ -51,10 +54,18 @@
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
 
+        <item name="actionBarSize">?android:attr/actionBarSize</item>
+        <item name="homeAsUpIndicator">@drawable/material_ic_arrow_back_black_24</item>
+
+        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <item name="dialogPreferredPadding">24dp</item>
+        <item name="colorControlHighlight">@color/ripple_material_dark</item>
+        <item name="toolbarNavigationButtonStyle">@style/Widget.AppCompat.Toolbar.Button.Navigation</item>
+
         <!-- Set status bar and navigation buttons to be translucent. -->
         <item name="android:colorPrimaryDark">@color/translucent_black</item>
-        <item name="android:statusBarColor">@color/translucent_black</item>
-        <item name="android:navigationBarColor">@color/translucent_black</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
 
         <!-- Apply borderless button style for the right ripple effect. -->
         <item name="borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
@@ -64,10 +75,20 @@
         </item>
     </style>
 
+    <style name="WallpaperTheme.DarkActionBar" parent="@style/WallpaperTheme.Preview">
+        <item name="colorControlNormal">?attr/colorPrimary</item>
+        <item name="colorButtonNormal">?attr/colorPrimary</item>
+        <item name="android:colorControlActivated">?attr/colorPrimary</item>
+        <item name="android:colorControlHighlight">?attr/colorPrimary</item>
+        <item name="actionMenuTextColor">?attr/colorPrimary</item>
+        <item name="android:actionMenuTextColor">?attr/colorPrimary</item>
+        <item name="android:actionMenuTextAppearance">@style/WallpaperPicker.Preview.TextAppearance.ActionBar</item>
+    </style>
+
     <!-- Toolbar -->
     <style name="TranslucentToolbarStyle" parent="@style/Widget.AppCompat.Toolbar">
         <item name="android:background">@android:color/transparent</item>
-        <item name="android:theme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
+        <item name="android:theme">@style/WallpaperTheme.DarkActionBar</item>
     </style>
 
     <style name="RegularToolbarStyle" parent="@style/Widget.AppCompat.Toolbar">
@@ -87,9 +108,7 @@
     </style>
 
     <!-- Individual components / Widgets -->
-    <style name="HeaderTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
-        <item name="android:textFontWeight">400</item>
-    </style>
+    <style name="HeaderTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"/>
 
     <style name="ButtonStyle" parent="@android:style/Widget.DeviceDefault.Button.Colored">
         <item name="android:padding">16dp</item>
@@ -102,10 +121,12 @@
         <item name="android:textColor">@android:color/white</item>
     </style>
 
-    <style name="ActionBarCheckboxStyle" parent="@android:style/Widget.DeviceDefault.CompoundButton.CheckBox"/>
+    <style name="ActionBarCheckboxStyle" parent="@android:style/Widget.DeviceDefault.CompoundButton.CheckBox">
+        <item name="android:textColor">?android:attr/colorPrimary</item>
+    </style>
 
     <style name="PreviewCheckboxDeviceTheme" parent="@android:style/Theme.DeviceDefault">
-        <item name="android:colorControlActivated">@*android:color/accent_device_default_light</item>
+        <item name="android:colorControlNormal">?android:attr/colorPrimary</item>
     </style>
 
     <style name="select_wallpaper_header">
@@ -115,6 +136,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>
@@ -125,6 +155,17 @@
         <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>
+
+    <style name="WallpaperPicker.Preview.TextAppearance.ActionBar"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Menu">
+        <item name="android:textAllCaps">false</item>
+        <item name="android:textColor">@color/material_white_100</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/asset/Asset.java b/src/com/android/wallpaper/asset/Asset.java
index c1d2252..498fad7 100755
--- a/src/com/android/wallpaper/asset/Asset.java
+++ b/src/com/android/wallpaper/asset/Asset.java
@@ -27,6 +27,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
 import android.os.AsyncTask;
+import android.view.View;
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
@@ -44,7 +45,7 @@
      */
     protected static Drawable getPlaceholderDrawable(
             Context context, ImageView imageView, int placeholderColor) {
-        Point imageViewDimensions = getImageViewDimensions(imageView);
+        Point imageViewDimensions = getViewDimensions(imageView);
         Bitmap placeholderBitmap =
                 Bitmap.createBitmap(imageViewDimensions.x, imageViewDimensions.y, Config.ARGB_8888);
         placeholderBitmap.eraseColor(placeholderColor);
@@ -55,13 +56,10 @@
      * Returns the visible height and width in pixels of the provided ImageView, or if it hasn't been
      * laid out yet, then gets the absolute value of the layout params.
      */
-    private static Point getImageViewDimensions(ImageView imageView) {
-        int width = imageView.getWidth() > 0
-                ? imageView.getWidth()
-                : Math.abs(imageView.getLayoutParams().width);
-        int height = imageView.getHeight() > 0
-                ? imageView.getHeight()
-                : Math.abs(imageView.getLayoutParams().height);
+    private static Point getViewDimensions(View view) {
+        int width = view.getWidth() > 0 ? view.getWidth() : Math.abs(view.getLayoutParams().width);
+        int height = view.getHeight() > 0 ? view.getHeight()
+                : Math.abs(view.getLayoutParams().height);
 
         return new Point(width, height);
     }
@@ -191,7 +189,7 @@
             final int transitionDurationMillis,
             @Nullable final DrawableLoadedListener drawableLoadedListener,
             int placeholderColor) {
-        Point imageViewDimensions = getImageViewDimensions(imageView);
+        Point imageViewDimensions = getViewDimensions(imageView);
 
         // Transition from a placeholder ColorDrawable to the decoded bitmap when the ImageView in
         // question is empty.
@@ -275,7 +273,7 @@
      * Custom AsyncTask which returns a copy of the given bitmap which is center cropped and scaled to
      * fit in the given ImageView.
      */
-    protected static class CenterCropBitmapTask extends AsyncTask<Void, Void, Bitmap> {
+    public static class CenterCropBitmapTask extends AsyncTask<Void, Void, Bitmap> {
 
         private Bitmap mBitmap;
         private BitmapReceiver mBitmapReceiver;
@@ -283,12 +281,12 @@
         private int mImageViewWidth;
         private int mImageViewHeight;
 
-        public CenterCropBitmapTask(Bitmap bitmap, ImageView imageView,
+        public CenterCropBitmapTask(Bitmap bitmap, View view,
                                     BitmapReceiver bitmapReceiver) {
             mBitmap = bitmap;
             mBitmapReceiver = bitmapReceiver;
 
-            Point imageViewDimensions = getImageViewDimensions(imageView);
+            Point imageViewDimensions = getViewDimensions(view);
 
             mImageViewWidth = imageViewDimensions.x;
             mImageViewHeight = imageViewDimensions.y;
@@ -307,7 +305,8 @@
                     (float) bitmapHeight / measuredHeight);
 
             Bitmap scaledBitmap = Bitmap.createScaledBitmap(
-                    mBitmap, Math.round(bitmapWidth / scale), Math.round(bitmapHeight / scale), true);
+                    mBitmap, Math.round(bitmapWidth / scale), Math.round(bitmapHeight / scale),
+                    true);
 
             int horizontalGutterPx = Math.max(0, (scaledBitmap.getWidth() - measuredWidth) / 2);
             int verticalGutterPx = Math.max(0, (scaledBitmap.getHeight() - measuredHeight) / 2);
diff --git a/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java b/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java
index 0214030..5df68c5 100755
--- a/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java
+++ b/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java
@@ -17,6 +17,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
@@ -35,9 +36,9 @@
 /**
  * Asset wrapping a drawable for a live wallpaper thumbnail.
  */
-public final class LiveWallpaperThumbAsset extends Asset {
-    private final Context mContext;
-    private final android.app.WallpaperInfo mInfo;
+public class LiveWallpaperThumbAsset extends Asset {
+    protected final Context mContext;
+    protected final android.app.WallpaperInfo mInfo;
 
     public LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info) {
         mContext = context.getApplicationContext();
@@ -48,7 +49,7 @@
     public void decodeBitmap(int targetWidth, int targetHeight,
                              BitmapReceiver receiver) {
         // No scaling is needed, as the thumbnail is already a thumbnail.
-        LoadThumbnailTask task = new LoadThumbnailTask(mInfo, receiver);
+        LoadThumbnailTask task = new LoadThumbnailTask(mContext, mInfo, receiver);
         task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
@@ -91,7 +92,7 @@
      * Returns the thumbnail drawable for the live wallpaper synchronously. Should not be called on
      * the main UI thread.
      */
-    Drawable getThumbnailDrawable() {
+    protected Drawable getThumbnailDrawable() {
         return mInfo.loadThumbnail(mContext.getPackageManager());
     }
 
@@ -147,18 +148,21 @@
      * AsyncTask subclass which loads the live wallpaper's thumbnail bitmap off the main UI thread.
      * Resolves with null if live wallpaper thumbnail is not a bitmap.
      */
-    private class LoadThumbnailTask extends AsyncTask<Void, Void, Bitmap> {
+    private static class LoadThumbnailTask extends AsyncTask<Void, Void, Bitmap> {
+        private final PackageManager mPackageManager;
         private android.app.WallpaperInfo mInfo;
         private BitmapReceiver mReceiver;
 
-        public LoadThumbnailTask(android.app.WallpaperInfo info, BitmapReceiver receiver) {
+        public LoadThumbnailTask(Context context, android.app.WallpaperInfo info,
+                BitmapReceiver receiver) {
             mInfo = info;
             mReceiver = receiver;
+            mPackageManager = context.getPackageManager();
         }
 
         @Override
         protected Bitmap doInBackground(Void... unused) {
-            Drawable thumb = mInfo.loadThumbnail(mContext.getPackageManager());
+            Drawable thumb = mInfo.loadThumbnail(mPackageManager);
 
             // Live wallpaper components may or may not specify a thumbnail drawable.
             if (thumb != null && thumb instanceof BitmapDrawable) {
diff --git a/src/com/android/wallpaper/asset/StreamableAsset.java b/src/com/android/wallpaper/asset/StreamableAsset.java
index 118581d..17f2522 100755
--- a/src/com/android/wallpaper/asset/StreamableAsset.java
+++ b/src/com/android/wallpaper/asset/StreamableAsset.java
@@ -260,12 +260,6 @@
                 mTargetWidth = tempHeight;
             }
 
-            InputStream inputStream = openInputStream();
-            // Input stream may be null if there was an error opening it.
-            if (inputStream == null) {
-                return null;
-            }
-
             BitmapFactory.Options options = new BitmapFactory.Options();
 
             Point rawDimensions = calculateRawDimensions();
@@ -277,6 +271,7 @@
                     rawDimensions.x, rawDimensions.y, mTargetWidth, mTargetHeight);
             options.inPreferredConfig = Config.HARDWARE;
 
+            InputStream inputStream = openInputStream();
             Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
             closeInputStream(
                     inputStream, "Error closing the input stream used to decode the full bitmap");
diff --git a/src/com/android/wallpaper/model/CurrentWallpaperInfoV16.java b/src/com/android/wallpaper/model/CurrentWallpaperInfoV16.java
deleted file mode 100755
index 3db46e7..0000000
--- a/src/com/android/wallpaper/model/CurrentWallpaperInfoV16.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2017 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.Activity;
-import android.content.Context;
-import android.os.Parcel;
-
-import com.android.wallpaper.asset.Asset;
-import com.android.wallpaper.asset.CurrentWallpaperAssetV16;
-import com.android.wallpaper.asset.FileAsset;
-import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.module.NoBackupImageWallpaper;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import androidx.annotation.DrawableRes;
-import androidx.annotation.StringRes;
-
-/**
- * Represents the wallpaper currently set to the device for API 16 through 23. Should not be used
- * to set a new wallpaper.
- */
-public class CurrentWallpaperInfoV16 extends WallpaperInfo {
-
-    public static final Creator<CurrentWallpaperInfoV16> CREATOR =
-            new Creator<CurrentWallpaperInfoV16>() {
-                @Override
-                public CurrentWallpaperInfoV16 createFromParcel(Parcel source) {
-                    return new CurrentWallpaperInfoV16(source);
-                }
-
-                @Override
-                public CurrentWallpaperInfoV16[] newArray(int size) {
-                    return new CurrentWallpaperInfoV16[size];
-                }
-            };
-    private List<String> mAttributions;
-    private Asset mAsset;
-    private String mActionUrl;
-    @StringRes
-    private int mActionLabelRes;
-    @DrawableRes
-    private int mActionIconRes;
-    private String mCollectionId;
-
-    public CurrentWallpaperInfoV16(List<String> attributions, String actionUrl,
-                                   @StringRes int actionLabelRes, @DrawableRes int actionIconRes,
-                                   String collectionId) {
-        mAttributions = attributions;
-        mActionUrl = actionUrl;
-        mActionLabelRes = actionLabelRes;
-        mActionIconRes = actionIconRes;
-        mCollectionId = collectionId;
-    }
-
-    private CurrentWallpaperInfoV16(Parcel in) {
-        mAttributions = new ArrayList<>();
-        in.readStringList(mAttributions);
-        mActionUrl = in.readString();
-        mCollectionId = in.readString();
-        mActionLabelRes = in.readInt();
-        mActionIconRes = in.readInt();
-    }
-
-    @Override
-    public List<String> getAttributions(Context context) {
-        return mAttributions;
-    }
-
-    @Override
-    public Asset getAsset(Context context) {
-        if (mAsset == null) {
-            boolean isNoBackupImageWallpaperSet = InjectorProvider.getInjector()
-                    .getLiveWallpaperStatusChecker(context).isNoBackupImageWallpaperSet();
-
-            mAsset = isNoBackupImageWallpaperSet
-                    ? new FileAsset(new File(context.getApplicationContext().getFilesDir(),
-                    NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH))
-                    : new CurrentWallpaperAssetV16(context);
-        }
-        return mAsset;
-    }
-
-    @Override
-    public Asset getThumbAsset(Context context) {
-        return getAsset(context);
-    }
-
-    @Override
-    public String getActionUrl(Context unused) {
-        return mActionUrl;
-    }
-
-    @Override
-    public int getActionIconRes(Context unused) {
-        return mActionIconRes != 0 ? mActionIconRes : WallpaperInfo.getDefaultActionIcon();
-    }
-
-    @Override
-    public int getActionLabelRes(Context unused) {
-        return mActionLabelRes != 0 ? mActionLabelRes : WallpaperInfo.getDefaultActionLabel();
-    }
-
-    @Override
-    public String getCollectionId(Context unused) {
-        return mCollectionId;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeStringList(mAttributions);
-        parcel.writeString(mActionUrl);
-        parcel.writeString(mCollectionId);
-        parcel.writeInt(mActionLabelRes);
-        parcel.writeInt(mActionIconRes);
-    }
-
-    @Override
-    public void showPreview(Activity srcActivity, InlinePreviewIntentFactory factory,
-                            int requestCode) {
-        srcActivity.startActivityForResult(factory.newIntent(srcActivity, this), requestCode);
-    }
-}
diff --git a/src/com/android/wallpaper/model/CurrentWallpaperInfoVN.java b/src/com/android/wallpaper/model/CurrentWallpaperInfoVN.java
index e44e717..bf54bfd 100755
--- a/src/com/android/wallpaper/model/CurrentWallpaperInfoVN.java
+++ b/src/com/android/wallpaper/model/CurrentWallpaperInfoVN.java
@@ -27,13 +27,10 @@
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.BuiltInWallpaperAsset;
 import com.android.wallpaper.asset.CurrentWallpaperAssetVN;
-import com.android.wallpaper.asset.FileAsset;
 import com.android.wallpaper.compat.WallpaperManagerCompat;
 import com.android.wallpaper.compat.WallpaperManagerCompat.WallpaperLocation;
 import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.module.NoBackupImageWallpaper;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -142,17 +139,6 @@
      * Constructs and returns an Asset instance representing the currently-set wallpaper asset.
      */
     private Asset createCurrentWallpaperAssetVN(Context context) {
-        // If the live wallpaper for rotating wallpapers is set, then provide a file asset
-        // representing that wallpaper.
-        boolean isNoBackupImageWallpaperSet = InjectorProvider.getInjector()
-                .getLiveWallpaperStatusChecker(context).isNoBackupImageWallpaperSet();
-        if (mWallpaperManagerFlag == WallpaperManagerCompat.FLAG_SYSTEM
-                && isNoBackupImageWallpaperSet) {
-            Context deviceProtectedContext = context.createDeviceProtectedStorageContext();
-            return new FileAsset(new File(deviceProtectedContext.getFilesDir(),
-                    NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH));
-        }
-
         WallpaperManagerCompat wallpaperManagerCompat = InjectorProvider.getInjector()
                 .getWallpaperManagerCompat(context);
 
diff --git a/src/com/android/wallpaper/model/EmptyCategory.java b/src/com/android/wallpaper/model/EmptyCategory.java
new file mode 100644
index 0000000..d37da90
--- /dev/null
+++ b/src/com/android/wallpaper/model/EmptyCategory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.model;
+
+import android.app.Activity;
+import android.content.Context;
+
+import com.android.wallpaper.asset.Asset;
+
+/**
+ * A placeholder {@link Category} with only id and title (and no content).
+ * Typically used to display partially loaded categories.
+ */
+public class EmptyCategory extends Category {
+
+    /**
+     * Constructs an EmptyCategory object.
+     *
+     * @see Category#Category(String, String, int)
+     */
+    public EmptyCategory(String title, String collectionId, int priority) {
+        super(title, collectionId, priority);
+    }
+
+    @Override
+    public void show(Activity srcActivity, PickerIntentFactory factory, int requestCode) {
+
+    }
+
+    @Override
+    public Asset getThumbnail(Context context) {
+        return null;
+    }
+}
diff --git a/src/com/android/wallpaper/model/InlinePreviewIntentFactory.java b/src/com/android/wallpaper/model/InlinePreviewIntentFactory.java
index ce29761..787c76a 100755
--- a/src/com/android/wallpaper/model/InlinePreviewIntentFactory.java
+++ b/src/com/android/wallpaper/model/InlinePreviewIntentFactory.java
@@ -23,6 +23,16 @@
  * wallpaper, if appropriate for that wallpaper.
  */
 public interface InlinePreviewIntentFactory {
+
+    /**
+     * @return whether it's possible to use the internal live picker, or {@code false} to use the
+     * the Framework Activity.
+     */
+    default boolean shouldUseInternalLivePicker(Context context) {
+        return false; // Disable always for now
+            //ContextCompat.checkSelfPermission(context, BIND_WALLPAPER) == PERMISSION_GRANTED;
+    }
+
     /**
      * Gets an intent to show the preview activity for the given wallpaper.
      *
@@ -30,5 +40,5 @@
      * @param wallpaper
      * @return Intent to show the inline preview activity.
      */
-    public Intent newIntent(Context ctx, WallpaperInfo wallpaper);
+    Intent newIntent(Context ctx, WallpaperInfo wallpaper);
 }
diff --git a/src/com/android/wallpaper/model/LiveWallpaperInfo.java b/src/com/android/wallpaper/model/LiveWallpaperInfo.java
index 5fc3cea..4337230 100755
--- a/src/com/android/wallpaper/model/LiveWallpaperInfo.java
+++ b/src/com/android/wallpaper/model/LiveWallpaperInfo.java
@@ -28,10 +28,14 @@
 import android.service.wallpaper.WallpaperService;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.LiveWallpaperThumbAsset;
 import com.android.wallpaper.compat.BuildCompat;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.LiveWallpaperInfoFactory;
 import com.android.wallpaper.util.ActivityUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -45,8 +49,6 @@
 import java.util.Iterator;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 /**
  * Represents a live wallpaper from the system.
  */
@@ -64,8 +66,8 @@
                 }
             };
     private static final String TAG = "LiveWallpaperInfo";
-    private android.app.WallpaperInfo mInfo;
-    private LiveWallpaperThumbAsset mThumbAsset;
+    protected android.app.WallpaperInfo mInfo;
+    protected LiveWallpaperThumbAsset mThumbAsset;
     private boolean mVisibleTitle;
 
     /**
@@ -100,7 +102,8 @@
                                              @Nullable List<String> excludedPackageNames) {
         List<ResolveInfo> resolveInfos = getAllOnDevice(context);
         List<WallpaperInfo> wallpaperInfos = new ArrayList<>();
-
+        LiveWallpaperInfoFactory factory =
+                InjectorProvider.getInjector().getLiveWallpaperInfoFactory(context);
         for (int i = 0; i < resolveInfos.size(); i++) {
             ResolveInfo resolveInfo = resolveInfos.get(i);
             android.app.WallpaperInfo wallpaperInfo;
@@ -119,7 +122,7 @@
                 continue;
             }
 
-            wallpaperInfos.add(new LiveWallpaperInfo(wallpaperInfo));
+            wallpaperInfos.add(factory.getLiveWallpaperInfo(wallpaperInfo));
         }
 
         return wallpaperInfos;
@@ -139,6 +142,8 @@
             resolveInfos = getAllOnDevice(context);
         }
         List<WallpaperInfo> wallpaperInfos = new ArrayList<>();
+        LiveWallpaperInfoFactory factory =
+                InjectorProvider.getInjector().getLiveWallpaperInfoFactory(context);
 
         for (int i = 0; i < resolveInfos.size(); i++) {
             ResolveInfo resolveInfo = resolveInfos.get(i);
@@ -162,7 +167,7 @@
                 continue;
             }
 
-            wallpaperInfos.add(new LiveWallpaperInfo(wallpaperInfo, shouldShowTitle));
+            wallpaperInfos.add(factory.getLiveWallpaperInfo(wallpaperInfo, shouldShowTitle));
         }
 
         return wallpaperInfos;
@@ -240,7 +245,7 @@
         return wallpaperInfos;
     }
 
-    static boolean isSystemApp(ApplicationInfo appInfo) {
+    private static boolean isSystemApp(ApplicationInfo appInfo) {
         return (appInfo.flags & (ApplicationInfo.FLAG_SYSTEM
                 | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0;
     }
@@ -257,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);
@@ -270,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;
     }
 
@@ -289,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;
@@ -305,9 +333,14 @@
     @Override
     public void showPreview(Activity srcActivity, InlinePreviewIntentFactory factory,
                             int requestCode) {
-        Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
-        preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, mInfo.getComponent());
-        ActivityUtils.startActivityForResultSafely(srcActivity, preview, requestCode);
+        //Only use internal live picker if available, otherwise, default to the Framework one
+        if (factory.shouldUseInternalLivePicker(srcActivity)) {
+            srcActivity.startActivityForResult(factory.newIntent(srcActivity, this), requestCode);
+        } else {
+            Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
+            preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, mInfo.getComponent());
+            ActivityUtils.startActivityForResultSafely(srcActivity, preview, requestCode);
+        }
     }
 
     @Override
diff --git a/src/com/android/wallpaper/model/WallpaperRotationInitializer.java b/src/com/android/wallpaper/model/WallpaperRotationInitializer.java
index ad9eef9..5797650 100755
--- a/src/com/android/wallpaper/model/WallpaperRotationInitializer.java
+++ b/src/com/android/wallpaper/model/WallpaperRotationInitializer.java
@@ -58,13 +58,6 @@
                                      Listener listener);
 
     /**
-     * Returns whether the live wallpaper needs to be set to the device in order to be able to start
-     * rotation or is already set but on home-only on N-MR2 or later, which means the user has the
-     * option to pick a new destination preference.
-     */
-    boolean isNoBackupImageWallpaperPreviewNeeded(Context appContext);
-
-    /**
      * Gets the current state of the possible wallpaper rotation represented by this object.
      */
     void fetchRotationInitializationState(Context context, RotationStateListener listener);
diff --git a/src/com/android/wallpaper/module/BaseWallpaperInjector.java b/src/com/android/wallpaper/module/BaseWallpaperInjector.java
index 15ed84b..de9ddb1 100755
--- a/src/com/android/wallpaper/module/BaseWallpaperInjector.java
+++ b/src/com/android/wallpaper/module/BaseWallpaperInjector.java
@@ -34,15 +34,14 @@
     private Requester mRequester;
     private WallpaperManagerCompat mWallpaperManagerCompat;
     private CurrentWallpaperInfoFactory mCurrentWallpaperFactory;
-    private LiveWallpaperStatusChecker mLiveWallpaperStatusChecker;
     private NetworkStatusNotifier mNetworkStatusNotifier;
     private AlarmManagerWrapper mAlarmManagerWrapper;
     private ExploreIntentChecker mExploreIntentChecker;
     private SystemFeatureChecker mSystemFeatureChecker;
-    private RotatingWallpaperComponentChecker mRotatingWallpaperComponentChecker;
     private FormFactorChecker mFormFactorChecker;
     private PackageStatusNotifier mPackageStatusNotifier;
     private LiveWallpaperInfoFactory mLiveWallpaperInfoFactory;
+    private DrawableLayerResolver mDrawableLayerResolver;
 
     @Override
     public synchronized BitmapCropper getBitmapCropper() {
@@ -110,15 +109,6 @@
     }
 
     @Override
-    public synchronized LiveWallpaperStatusChecker getLiveWallpaperStatusChecker(Context context) {
-        if (mLiveWallpaperStatusChecker == null) {
-            mLiveWallpaperStatusChecker =
-                    new DefaultLiveWallpaperStatusChecker(context.getApplicationContext());
-        }
-        return mLiveWallpaperStatusChecker;
-    }
-
-    @Override
     public synchronized NetworkStatusNotifier getNetworkStatusNotifier(Context context) {
         if (mNetworkStatusNotifier == null) {
             mNetworkStatusNotifier = new DefaultNetworkStatusNotifier(context.getApplicationContext());
@@ -160,14 +150,6 @@
     }
 
     @Override
-    public synchronized RotatingWallpaperComponentChecker getRotatingWallpaperComponentChecker() {
-        if (mRotatingWallpaperComponentChecker == null) {
-            mRotatingWallpaperComponentChecker = new DefaultRotatingWallpaperComponentChecker();
-        }
-        return mRotatingWallpaperComponentChecker;
-    }
-
-    @Override
     public synchronized FormFactorChecker getFormFactorChecker(Context context) {
         if (mFormFactorChecker == null) {
             mFormFactorChecker = new DefaultFormFactorChecker(context.getApplicationContext());
@@ -187,4 +169,12 @@
         }
         return mLiveWallpaperInfoFactory;
     }
+
+    @Override
+    public DrawableLayerResolver getDrawableLayerResolver() {
+        if (mDrawableLayerResolver == null) {
+            mDrawableLayerResolver = new DefaultDrawableLayerResolver();
+        }
+        return mDrawableLayerResolver;
+    }
 }
diff --git a/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java b/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java
index c74f9b2..dbe3722 100755
--- a/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java
+++ b/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java
@@ -17,16 +17,13 @@
 
 import android.content.Context;
 
-import com.android.wallpaper.compat.BuildCompat;
+import androidx.annotation.Nullable;
+
 import com.android.wallpaper.compat.WallpaperManagerCompat;
-import com.android.wallpaper.model.CurrentWallpaperInfoV16;
 import com.android.wallpaper.model.CurrentWallpaperInfoVN;
-import com.android.wallpaper.model.LiveWallpaperInfo;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
 
-import androidx.annotation.Nullable;
-
 /**
  * Default implementation of {@link CurrentWallpaperInfoFactory} which actually constructs
  * {@link WallpaperInfo} instances representing the wallpapers currently set to the device.
@@ -35,7 +32,6 @@
 
     private final Context mAppContext;
     private final WallpaperRefresher mWallpaperRefresher;
-    private final LiveWallpaperStatusChecker mLiveWallpaperStatusChecker;
     private final LiveWallpaperInfoFactory mLiveWallpaperInfoFactory;
 
     // Cached copies of the currently-set WallpaperInfo(s) and presentation mode.
@@ -49,8 +45,6 @@
         mAppContext = context.getApplicationContext();
         Injector injector = InjectorProvider.getInjector();
         mWallpaperRefresher = injector.getWallpaperRefresher(mAppContext);
-        mLiveWallpaperStatusChecker =
-                injector.getLiveWallpaperStatusChecker(mAppContext);
         mLiveWallpaperInfoFactory = injector.getLiveWallpaperInfoFactory(mAppContext);
     }
 
@@ -72,29 +66,16 @@
 
         mWallpaperRefresher.refresh(
                 (homeWallpaperMetadata, lockWallpaperMetadata, presentationMode) -> {
-
                     WallpaperInfo homeWallpaper;
-
-                    if (homeWallpaperMetadata.getWallpaperComponent() == null
-                            || mLiveWallpaperStatusChecker.isNoBackupImageWallpaperSet()) {
-                        // Image wallpaper
-                        if (BuildCompat.isAtLeastN()) {
-                            homeWallpaper = new CurrentWallpaperInfoVN(
-                                    homeWallpaperMetadata.getAttributions(),
-                                    homeWallpaperMetadata.getActionUrl(),
-                                    homeWallpaperMetadata.getActionLabelRes(),
-                                    homeWallpaperMetadata.getActionIconRes(),
-                                    homeWallpaperMetadata.getCollectionId(),
-                                    WallpaperManagerCompat.FLAG_SYSTEM);
-                        } else {
-                            homeWallpaper = new CurrentWallpaperInfoV16(
-                                    homeWallpaperMetadata.getAttributions(),
-                                    homeWallpaperMetadata.getActionUrl(),
-                                    homeWallpaperMetadata.getActionLabelRes(),
-                                    homeWallpaperMetadata.getActionIconRes(),
-                                    homeWallpaperMetadata.getCollectionId());
-                        }
-                    } else { // Live wallpaper
+                    if (homeWallpaperMetadata.getWallpaperComponent() == null) {
+                        homeWallpaper = new CurrentWallpaperInfoVN(
+                                homeWallpaperMetadata.getAttributions(),
+                                homeWallpaperMetadata.getActionUrl(),
+                                homeWallpaperMetadata.getActionLabelRes(),
+                                homeWallpaperMetadata.getActionIconRes(),
+                                homeWallpaperMetadata.getCollectionId(),
+                                WallpaperManagerCompat.FLAG_SYSTEM);
+                    } else {
                         homeWallpaper = mLiveWallpaperInfoFactory.getLiveWallpaperInfo(
                                 homeWallpaperMetadata.getWallpaperComponent());
                     }
diff --git a/src/com/android/wallpaper/module/DefaultDrawableLayerResolver.java b/src/com/android/wallpaper/module/DefaultDrawableLayerResolver.java
new file mode 100644
index 0000000..4e52ff8
--- /dev/null
+++ b/src/com/android/wallpaper/module/DefaultDrawableLayerResolver.java
@@ -0,0 +1,33 @@
+/*
+ * 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.module;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+
+/**
+ * Default implementation of {@link DrawableLayerResolver}
+ */
+public class DefaultDrawableLayerResolver implements DrawableLayerResolver {
+
+    /**
+     * Picks and returns the top most layer of the given {@link LayerDrawable}
+     */
+    @Override
+    public Drawable resolveLayer(LayerDrawable layerDrawable) {
+        return layerDrawable.getDrawable(layerDrawable.getNumberOfLayers() - 1);
+    }
+}
diff --git a/src/com/android/wallpaper/module/DefaultLiveWallpaperInfoFactory.java b/src/com/android/wallpaper/module/DefaultLiveWallpaperInfoFactory.java
index 4a06ce5..774ad89 100644
--- a/src/com/android/wallpaper/module/DefaultLiveWallpaperInfoFactory.java
+++ b/src/com/android/wallpaper/module/DefaultLiveWallpaperInfoFactory.java
@@ -1,3 +1,18 @@
+/*
+ * 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.module;
 
 import com.android.wallpaper.model.LiveWallpaperInfo;
@@ -13,4 +28,10 @@
     public WallpaperInfo getLiveWallpaperInfo(android.app.WallpaperInfo info) {
         return new LiveWallpaperInfo(info);
     }
+
+    @Override
+    public WallpaperInfo getLiveWallpaperInfo(android.app.WallpaperInfo info,
+            boolean shouldShowTitle) {
+        return new LiveWallpaperInfo(info, shouldShowTitle);
+    }
 }
diff --git a/src/com/android/wallpaper/module/DefaultLiveWallpaperStatusChecker.java b/src/com/android/wallpaper/module/DefaultLiveWallpaperStatusChecker.java
deleted file mode 100755
index 003a06b..0000000
--- a/src/com/android/wallpaper/module/DefaultLiveWallpaperStatusChecker.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 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.module;
-
-import android.annotation.SuppressLint;
-import android.app.WallpaperManager;
-import android.content.Context;
-
-/**
- * Default implementation of {@link LiveWallpaperStatusChecker}.
- */
-@SuppressLint("ServiceCast")
-public class DefaultLiveWallpaperStatusChecker implements LiveWallpaperStatusChecker {
-
-    private WallpaperManager mWallpaperManager;
-
-    public DefaultLiveWallpaperStatusChecker(Context context) {
-        // Retrieve WallpaperManager using Context#getSystemService instead of
-        // WallpaperManager#getInstance so it can be mocked out in test.
-        mWallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
-    }
-
-    @Override
-    public boolean isNoBackupImageWallpaperSet() {
-        android.app.WallpaperInfo liveWallpaper = mWallpaperManager.getWallpaperInfo();
-        return liveWallpaper != null
-                && liveWallpaper.getServiceName().equals(NoBackupImageWallpaper.class.getName());
-    }
-}
diff --git a/src/com/android/wallpaper/module/DefaultRotatingWallpaperComponentChecker.java b/src/com/android/wallpaper/module/DefaultRotatingWallpaperComponentChecker.java
deleted file mode 100755
index eddb6bf..0000000
--- a/src/com/android/wallpaper/module/DefaultRotatingWallpaperComponentChecker.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 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.module;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-
-/**
- * Default implementation of {@link RotatingWallpaperComponentChecker}.
- */
-public class DefaultRotatingWallpaperComponentChecker implements RotatingWallpaperComponentChecker {
-
-    private static boolean isLiveWallpaperSupported(Context context) {
-        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LIVE_WALLPAPER);
-    }
-
-    @Override
-    @RotatingWallpaperComponent
-    public int getCurrentRotatingWallpaperComponent(Context context) {
-        if (!isLiveWallpaperSupported(context)) {
-            return ROTATING_WALLPAPER_COMPONENT_STATIC;
-        }
-
-        // If presentation mode is ROTATING but the live wallpaper is not set, then "legacy" rotation
-        // from older APKs is in effect and the current rotating wallpaper component is a static WP.
-        Injector injector = InjectorProvider.getInjector();
-        WallpaperPreferences preferences = injector.getPreferences(context);
-        LiveWallpaperStatusChecker liveWallpaperStatusChecker = injector
-                .getLiveWallpaperStatusChecker(context);
-        if (preferences.getWallpaperPresentationMode()
-                == WallpaperPreferences.PRESENTATION_MODE_ROTATING
-                && !liveWallpaperStatusChecker.isNoBackupImageWallpaperSet()) {
-            return ROTATING_WALLPAPER_COMPONENT_STATIC;
-        }
-
-        return ROTATING_WALLPAPER_COMPONENT_LIVE;
-    }
-
-    @Override
-    @RotatingWallpaperComponent
-    public int getNextRotatingWallpaperComponent(Context context) {
-        if (!isLiveWallpaperSupported(context)) {
-            return ROTATING_WALLPAPER_COMPONENT_STATIC;
-        }
-
-        return ROTATING_WALLPAPER_COMPONENT_LIVE;
-    }
-
-    @Override
-    @RotatingWallpaperSupport
-    public int getRotatingWallpaperSupport(Context context) {
-        FormFactorChecker formFactorChecker =
-                InjectorProvider.getInjector().getFormFactorChecker(context);
-
-        if (formFactorChecker.getFormFactor() == FormFactorChecker.FORM_FACTOR_DESKTOP) {
-            return ROTATING_WALLPAPER_SUPPORT_SUPPORTED;
-        }
-
-        // While static daily rotation is supported on desktops, it isn't (yet?) supported on phones.
-        // For phones which don't support live wallpapers thus we disallow daily rotation altogether.
-        return isLiveWallpaperSupported(context) ? ROTATING_WALLPAPER_SUPPORT_SUPPORTED
-                : ROTATING_WALLPAPER_SUPPORT_NOT_SUPPORTED;
-    }
-}
diff --git a/src/com/android/wallpaper/module/DefaultWallpaperPersister.java b/src/com/android/wallpaper/module/DefaultWallpaperPersister.java
index e2c968c..27e4491 100755
--- a/src/com/android/wallpaper/module/DefaultWallpaperPersister.java
+++ b/src/com/android/wallpaper/module/DefaultWallpaperPersister.java
@@ -19,7 +19,6 @@
 import android.app.Activity;
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
@@ -32,6 +31,8 @@
 import android.view.Display;
 import android.view.WindowManager;
 
+import androidx.annotation.Nullable;
+
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.Asset.BitmapReceiver;
 import com.android.wallpaper.asset.Asset.DimensionsReceiver;
@@ -42,24 +43,16 @@
 import com.android.wallpaper.compat.WallpaperManagerCompat;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.module.BitmapCropper.Callback;
-import com.android.wallpaper.module.RotatingWallpaperComponentChecker.RotatingWallpaperComponent;
 import com.android.wallpaper.util.BitmapTransformer;
-import com.android.wallpaper.util.DiskBasedLogger;
-import com.android.wallpaper.util.FileMover;
 import com.android.wallpaper.util.ScreenSizeCalculator;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 /**
  * Concrete implementation of WallpaperPersister which actually sets wallpapers to the system via
  * the WallpaperManager.
@@ -71,11 +64,9 @@
 
     private final Context mAppContext; // The application's context.
     // Context that accesses files in device protected storage
-    private final Context mDeviceProtectedContext;
     private final WallpaperManager mWallpaperManager;
     private final WallpaperManagerCompat mWallpaperManagerCompat;
     private final WallpaperPreferences mWallpaperPreferences;
-    private final RotatingWallpaperComponentChecker mRotatingWallpaperComponentChecker;
     private final WallpaperChangedNotifier mWallpaperChangedNotifier;
 
     private WallpaperInfo mWallpaperInfoInPreview;
@@ -83,21 +74,19 @@
     @SuppressLint("ServiceCast")
     public DefaultWallpaperPersister(Context context) {
         mAppContext = context.getApplicationContext();
-        mDeviceProtectedContext = mAppContext.createDeviceProtectedStorageContext();
         // Retrieve WallpaperManager using Context#getSystemService instead of
         // WallpaperManager#getInstance so it can be mocked out in test.
         Injector injector = InjectorProvider.getInjector();
         mWallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
         mWallpaperManagerCompat = injector.getWallpaperManagerCompat(context);
         mWallpaperPreferences = injector.getPreferences(context);
-        mRotatingWallpaperComponentChecker = injector.getRotatingWallpaperComponentChecker();
         mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
     }
 
     @Override
     public void setIndividualWallpaper(final WallpaperInfo wallpaper, Asset asset,
-                                       @Nullable Rect cropRect, float scale, @Destination final int destination,
-                                       final SetWallpaperCallback callback) {
+            @Nullable Rect cropRect, float scale, @Destination final int destination,
+            final SetWallpaperCallback callback) {
         // Set wallpaper without downscaling directly from an input stream if there's no crop rect
         // specified by the caller and the asset is streamable.
         if (cropRect == null && asset instanceof StreamableAsset) {
@@ -149,7 +138,7 @@
 
     @Override
     public void setIndividualWallpaperWithPosition(Activity activity, WallpaperInfo wallpaper,
-                                                   @WallpaperPosition int wallpaperPosition, SetWallpaperCallback callback) {
+            @WallpaperPosition int wallpaperPosition, SetWallpaperCallback callback) {
         Display display = ((WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE))
                 .getDefaultDisplay();
         Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display);
@@ -164,9 +153,10 @@
                 }
 
                 switch (wallpaperPosition) {
-                    // Crop out screen-sized center portion of the source image if it's larger than the screen
-                    // in both dimensions. Otherwise, decode the entire bitmap and fill the space around it to
-                    // fill a new screen-sized bitmap with plain black pixels.
+                    // Crop out screen-sized center portion of the source image if it's larger
+                    // than the screen
+                    // in both dimensions. Otherwise, decode the entire bitmap and fill the space
+                    // around it to fill a new screen-sized bitmap with plain black pixels.
                     case WALLPAPER_POSITION_CENTER:
                         setIndividualWallpaperWithCenterPosition(
                                 wallpaper, asset, dimensions, screenSize, callback);
@@ -178,19 +168,22 @@
                                 wallpaper, asset, dimensions, screenSize, callback);
                         break;
 
-                    // Decode full bitmap sized for screen and stretch it to fill the screen dimensions.
+                    // Decode full bitmap sized for screen and stretch it to fill the screen
+                    // dimensions.
                     case WALLPAPER_POSITION_STRETCH:
                         asset.decodeBitmap(screenSize.x, screenSize.y, new BitmapReceiver() {
                             @Override
                             public void onBitmapDecoded(@Nullable Bitmap bitmap) {
-                                setIndividualWallpaperStretch(wallpaper, bitmap, screenSize /* stretchSize */,
+                                setIndividualWallpaperStretch(wallpaper, bitmap,
+                                        screenSize /* stretchSize */,
                                         WallpaperPersister.DEST_BOTH, callback);
                             }
                         });
                         break;
 
                     default:
-                        Log.e(TAG, "Unsupported wallpaper position option specified: " + wallpaperPosition);
+                        Log.e(TAG, "Unsupported wallpaper position option specified: "
+                                + wallpaperPosition);
                         callback.onError(null);
                 }
             }
@@ -208,7 +201,7 @@
      * @param callback   Callback used to notify original caller of wallpaper set operation result.
      */
     private void setIndividualWallpaperWithCenterPosition(WallpaperInfo wallpaper, Asset asset,
-                                                          Point dimensions, Point screenSize, SetWallpaperCallback callback) {
+            Point dimensions, Point screenSize, SetWallpaperCallback callback) {
         if (dimensions.x >= screenSize.x && dimensions.y >= screenSize.y) {
             Rect cropRect = new Rect(
                     (dimensions.x - screenSize.x) / 2,
@@ -218,7 +211,8 @@
             asset.decodeBitmapRegion(cropRect, screenSize.x, screenSize.y, new BitmapReceiver() {
                 @Override
                 public void onBitmapDecoded(@Nullable Bitmap bitmap) {
-                    setIndividualWallpaper(wallpaper, bitmap, WallpaperPersister.DEST_BOTH, callback);
+                    setIndividualWallpaper(wallpaper, bitmap, WallpaperPersister.DEST_BOTH,
+                            callback);
                 }
             });
         } else {
@@ -249,7 +243,7 @@
      * @param callback   Callback used to notify original caller of wallpaper set operation result.
      */
     private void setIndividualWallpaperWithCenterCropPosition(WallpaperInfo wallpaper, Asset asset,
-                                                              Point dimensions, Point screenSize, SetWallpaperCallback callback) {
+            Point dimensions, Point screenSize, SetWallpaperCallback callback) {
         float scale = Math.max((float) screenSize.x / dimensions.x,
                 (float) screenSize.y / dimensions.y);
 
@@ -276,7 +270,7 @@
      * @param callback      Called once the wallpaper was set or if an error occurred.
      */
     private void setIndividualWallpaper(WallpaperInfo wallpaper, Bitmap croppedBitmap,
-                                        @Destination int destination, SetWallpaperCallback callback) {
+            @Destination int destination, SetWallpaperCallback callback) {
         SetWallpaperTask setWallpaperTask =
                 new SetWallpaperTask(wallpaper, croppedBitmap, destination, callback);
         setWallpaperTask.execute();
@@ -287,15 +281,16 @@
      *
      * @param wallpaper     Wallpaper model object.
      * @param croppedBitmap Bitmap representing the individual wallpaper image.
-     * @param fillSize      Specifies the final bitmap size that should be set to WallpaperManager. This
-     *                      final bitmap will show the visible area of the provided bitmap after applying a mask with
-     *                      black background the source bitmap and centering. There may be black borders around the
-     *                      original bitmap if it's smaller than the fillSize in one or both dimensions.
+     * @param fillSize      Specifies the final bitmap size that should be set to WallpaperManager.
+     *                      This final bitmap will show the visible area of the provided bitmap
+     *                      after applying a mask with black background the source bitmap and
+     *                      centering. There may be black borders around the original bitmap if
+     *                      it's smaller than the fillSize in one or both dimensions.
      * @param destination   The destination - where to set the wallpaper to.
      * @param callback      Called once the wallpaper was set or if an error occurred.
      */
     private void setIndividualWallpaperFill(WallpaperInfo wallpaper, Bitmap croppedBitmap,
-                                            Point fillSize, @Destination int destination, SetWallpaperCallback callback) {
+            Point fillSize, @Destination int destination, SetWallpaperCallback callback) {
         SetWallpaperTask setWallpaperTask =
                 new SetWallpaperTask(wallpaper, croppedBitmap, destination, callback);
         setWallpaperTask.setFillSize(fillSize);
@@ -308,13 +303,14 @@
      *
      * @param wallpaper     Wallpaper model object.
      * @param croppedBitmap Bitmap representing the individual wallpaper image.
-     * @param stretchSize   Specifies the final size to which the the bitmap should be stretched prior
+     * @param stretchSize   Specifies the final size to which the bitmap should be stretched
+     *                      prior
      *                      to being set to the device.
      * @param destination   The destination - where to set the wallpaper to.
      * @param callback      Called once the wallpaper was set or if an error occurred.
      */
     private void setIndividualWallpaperStretch(WallpaperInfo wallpaper, Bitmap croppedBitmap,
-                                               Point stretchSize, @Destination int destination, SetWallpaperCallback callback) {
+            Point stretchSize, @Destination int destination, SetWallpaperCallback callback) {
         SetWallpaperTask setWallpaperTask =
                 new SetWallpaperTask(wallpaper, croppedBitmap, destination, callback);
         setWallpaperTask.setStretchSize(stretchSize);
@@ -330,7 +326,7 @@
      * @param callback    Called once the wallpaper was set or if an error occurred.
      */
     private void setIndividualWallpaper(WallpaperInfo wallpaper, InputStream inputStream,
-                                        @Destination int destination, SetWallpaperCallback callback) {
+            @Destination int destination, SetWallpaperCallback callback) {
         SetWallpaperTask setWallpaperTask =
                 new SetWallpaperTask(wallpaper, inputStream, destination, callback);
         setWallpaperTask.execute();
@@ -338,58 +334,31 @@
 
     @Override
     public boolean setWallpaperInRotation(Bitmap wallpaperBitmap, List<String> attributions,
-                                          int actionLabelRes, int actionIconRes,
-                                          String actionUrl, String collectionId) {
-        @RotatingWallpaperComponent int rotatingWallpaperComponent = mRotatingWallpaperComponentChecker
-                .getCurrentRotatingWallpaperComponent(mAppContext);
+            int actionLabelRes, int actionIconRes, String actionUrl, String collectionId) {
 
-        switch (rotatingWallpaperComponent) {
-            case RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_STATIC:
-                return setWallpaperInRotationStatic(wallpaperBitmap, attributions, actionUrl,
-                        actionLabelRes, actionIconRes, collectionId);
-            case RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_LIVE:
-                return setWallpaperInRotationLive(wallpaperBitmap, attributions, actionUrl,
-                        actionLabelRes, actionIconRes, collectionId);
-            default:
-                Log.e(TAG, "Unknown rotating wallpaper component: " + rotatingWallpaperComponent);
-                return false;
-        }
+        return setWallpaperInRotationStatic(wallpaperBitmap, attributions, actionUrl,
+                actionLabelRes, actionIconRes, collectionId);
     }
 
     @Override
     public int setWallpaperBitmapInNextRotation(Bitmap wallpaperBitmap) {
-        @RotatingWallpaperComponent int rotatingWallpaperComponent = mRotatingWallpaperComponentChecker
-                .getNextRotatingWallpaperComponent(mAppContext);
-
-        switch (rotatingWallpaperComponent) {
-            case RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_STATIC:
-                return setWallpaperBitmapInRotationStatic(wallpaperBitmap);
-            case RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_LIVE:
-                boolean isSuccess = setWallpaperBitmapInRotationLive(wallpaperBitmap, true /* isPreview */);
-                return isSuccess ? 1 : 0;
-            default:
-                Log.e(TAG, "Unknown rotating wallpaper component: " + rotatingWallpaperComponent);
-                return 0;
-        }
+        return setWallpaperBitmapInRotationStatic(wallpaperBitmap);
     }
 
     @Override
     public boolean finalizeWallpaperForNextRotation(List<String> attributions, String actionUrl,
-                                                    int actionLabelRes, int actionIconRes,
-                                                    String collectionId, int wallpaperId) {
-        @RotatingWallpaperComponent int rotatingWallpaperComponent =
-                mRotatingWallpaperComponentChecker.getNextRotatingWallpaperComponent(mAppContext);
+            int actionLabelRes, int actionIconRes, String collectionId, int wallpaperId) {
         return finalizeWallpaperForRotatingComponent(attributions, actionUrl, actionLabelRes,
-                actionIconRes, collectionId, wallpaperId, rotatingWallpaperComponent);
+                actionIconRes, collectionId, wallpaperId);
     }
 
     /**
-     * Sets wallpaper image and attributions when a static wallpaper is responsible for presenting the
+     * Sets wallpaper image and attributions when a static wallpaper is responsible for presenting
+     * the
      * current "daily wallpaper".
      */
     private boolean setWallpaperInRotationStatic(Bitmap wallpaperBitmap, List<String> attributions,
-                                                 String actionUrl, int actionLabelRes,
-                                                 int actionIconRes, String collectionId) {
+            String actionUrl, int actionLabelRes, int actionIconRes, String collectionId) {
         final int wallpaperId = setWallpaperBitmapInRotationStatic(wallpaperBitmap);
 
         if (wallpaperId == 0) {
@@ -397,8 +366,7 @@
         }
 
         return finalizeWallpaperForRotatingComponent(attributions, actionUrl, actionLabelRes,
-                actionIconRes, collectionId, wallpaperId,
-                RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_STATIC);
+                actionIconRes, collectionId, wallpaperId);
     }
 
     /**
@@ -413,83 +381,21 @@
             int actionLabelRes,
             int actionIconRes,
             String collectionId,
-            int wallpaperId,
-            @RotatingWallpaperComponent int rotatingWallpaperComponent) {
+            int wallpaperId) {
         mWallpaperPreferences.clearHomeWallpaperMetadata();
 
         boolean isLockWallpaperSet = isSeparateLockScreenWallpaperSet();
 
-        // Persist wallpaper IDs if the rotating wallpaper component is static and this device is
-        // running Android N or later.
-        if (rotatingWallpaperComponent
-                == RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_STATIC) {
-            if (BuildCompat.isAtLeastN()) {
-                mWallpaperPreferences.setHomeWallpaperManagerId(wallpaperId);
+        // Persist wallpaper IDs if the rotating wallpaper component
+        mWallpaperPreferences.setHomeWallpaperManagerId(wallpaperId);
 
-                // Only copy over wallpaper ID to lock wallpaper if no explicit lock wallpaper is set (so
-                // metadata isn't lost if a user explicitly sets a home-only wallpaper).
-                if (!isLockWallpaperSet) {
-                    mWallpaperPreferences.setLockWallpaperId(wallpaperId);
-                }
-            } else { // Pre-N but using static component
-                // Compute bitmap hash code after setting the wallpaper because JPEG compression has likely
-                // changed many pixels' color values. Forget the previously loaded wallpaper bitmap so that
-                // WallpaperManager doesn't return the old wallpaper drawable.
-                mWallpaperManager.forgetLoadedWallpaper();
-                Bitmap bitmap = ((BitmapDrawable) mWallpaperManagerCompat.getDrawable()).getBitmap();
-                long bitmapHash = BitmapUtils.generateHashCode(bitmap);
-
-                mWallpaperPreferences.setHomeWallpaperHashCode(bitmapHash);
-            }
-        } else { // Live wallpaper rotating component.
-
-            // Copy "preview" JPEG to "rotating" JPEG if the preview file exists.
-           File rotatingWallpaper;
-            try {
-                rotatingWallpaper = moveToDeviceProtectedStorage(
-                        NoBackupImageWallpaper.PREVIEW_WALLPAPER_FILE_PATH,
-                        NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH);
-            } catch (Exception e) {
-                DiskBasedLogger.e(
-                        TAG,
-                        "Unable to move preview to final file for rotating wallpaper " +
-                                "file (exception)" + e.toString(),
-                        mAppContext);
-                return false;
-            }
-            if (rotatingWallpaper == null) {
-                rotatingWallpaper = mDeviceProtectedContext.getFileStreamPath(
-                        NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH);
-            }
-            try {
-                FileInputStream fis = new FileInputStream(rotatingWallpaper.getAbsolutePath());
-                Bitmap bitmap = BitmapFactory.decodeStream(fis);
-                fis.close();
-
-                if (bitmap != null) {
-                    long bitmapHash = BitmapUtils.generateHashCode(bitmap);
-                    mWallpaperPreferences.setHomeWallpaperHashCode(bitmapHash);
-                } else {
-                    Log.e(TAG, "Unable to decode rotating wallpaper file");
-                    return false;
-                }
-            } catch (FileNotFoundException e) {
-                Log.e(TAG, "Rotating wallpaper file not found at path: "
-                        + rotatingWallpaper.getAbsolutePath());
-                e.printStackTrace();
-                return false;
-            } catch (IOException e) {
-                Log.e(TAG, "IOException when closing FileInputStream " + e);
-                return false;
-            }
-
-            mWallpaperChangedNotifier.notifyWallpaperChanged();
-
-            // Send a broadcast to {@link RotatingWallpaperChangedReceiver} in the :live_wallpaper
-            // process so the currently displayed wallpaper updates.
-            notifyLiveWallpaperBitmapChanged();
+        // Only copy over wallpaper ID to lock wallpaper if no explicit lock wallpaper is set
+        // (so metadata isn't lost if a user explicitly sets a home-only wallpaper).
+        if (!isLockWallpaperSet) {
+            mWallpaperPreferences.setLockWallpaperId(wallpaperId);
         }
 
+
         mWallpaperPreferences.setHomeWallpaperAttributions(attributions);
         mWallpaperPreferences.setHomeWallpaperActionUrl(actionUrl);
         mWallpaperPreferences.setHomeWallpaperActionLabelRes(actionLabelRes);
@@ -498,11 +404,9 @@
         mWallpaperPreferences.setHomeWallpaperBaseImageUrl(null);
         mWallpaperPreferences.setHomeWallpaperCollectionId(collectionId);
 
-        // Set metadata to lock screen also when the rotating wallpaper is a static one so if user sets
-        // a home screen-only wallpaper later, these attributions will still be available.
-        if (rotatingWallpaperComponent
-                == RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_STATIC
-                && !isLockWallpaperSet) {
+        // Set metadata to lock screen also when the rotating wallpaper so if user sets a home
+        // screen-only wallpaper later, these attributions will still be available.
+        if (!isLockWallpaperSet) {
             mWallpaperPreferences.setLockWallpaperAttributions(attributions);
             mWallpaperPreferences.setLockWallpaperActionUrl(actionUrl);
             mWallpaperPreferences.setLockWallpaperActionLabelRes(actionLabelRes);
@@ -514,26 +418,6 @@
     }
 
     /**
-     * Sets wallpaper image and attributions when a live wallpaper is responsible for presenting the
-     * current "daily wallpaper".
-     */
-    private boolean setWallpaperInRotationLive(Bitmap wallpaperBitmap, List<String> attributions,
-                                               String actionUrl, int actionLabelRes,
-                                               int actionIconRes, String collectionId) {
-
-        synchronized (RotatingWallpaperLockProvider.getInstance()) {
-            if (!setWallpaperBitmapInRotationLive(wallpaperBitmap, false /* isPreview */)) {
-                return false;
-            }
-
-            return finalizeWallpaperForRotatingComponent(attributions, actionUrl, actionLabelRes,
-                    actionIconRes, collectionId,
-                    0 /* wallpaperId */,
-                    RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_LIVE);
-        }
-    }
-
-    /**
      * Sets a wallpaper in rotation as a static wallpaper to the {@link WallpaperManager} with the
      * option allowBackup=false to save user data.
      *
@@ -553,84 +437,6 @@
     }
 
     /**
-     * Sets a wallpaper in rotation as a live wallpaper. Writes wallpaper bitmap to a file in internal
-     * storage and sends a broadcast to the live wallpaper notifying it that rotating wallpaper image
-     * data changed.
-     *
-     * @return whether the set wallpaper operation was successful.
-     */
-    private boolean setWallpaperBitmapInRotationLive(Bitmap wallpaperBitmap, boolean isPreview) {
-        File pendingFile;
-        try {
-            pendingFile = File.createTempFile("rotating_pending", ".jpg", mAppContext.getFilesDir());
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to create temp file for rotating wallpaper");
-            return false;
-        }
-
-        FileOutputStream fos;
-        try {
-            fos = mAppContext.openFileOutput(pendingFile.getName(), Context.MODE_PRIVATE);
-        } catch (FileNotFoundException e) {
-            Log.e(TAG, "Unable to open file output stream for pending rotating wallpaper file");
-            return false;
-        }
-
-        boolean compressedSuccessfully =
-                wallpaperBitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, fos);
-
-        // Close the file stream.
-        try {
-            fos.flush();
-            fos.close();
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to close FileOutputStream for pending rotating wallpaper file"
-                    + " (compress succeeded");
-            return false;
-        }
-
-        if (compressedSuccessfully) {
-            // Compressing/writing to disk succeeded, so move the pending file to the final location.
-            try {
-                if (isPreview) {
-                    if (!pendingFile.renameTo(mAppContext.getFileStreamPath(
-                            NoBackupImageWallpaper.PREVIEW_WALLPAPER_FILE_PATH))) {
-                        return false;
-                    }
-                } else {
-                    moveToDeviceProtectedStorage(pendingFile.getName(),
-                            NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH);
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Unable to rename pending to final file for rotating wallpaper file"
-                        + " (exception)" + e.toString());
-                return false;
-            }
-        } else {
-            Log.e(TAG, "Unable to compress the wallpaper bitmap");
-
-            // Delete the pending file since compressing/writing the image to disk failed.
-            try {
-                pendingFile.delete();
-            } catch (SecurityException e) {
-                Log.e(TAG, "Unable to delete pending rotating wallpaper file");
-                return false;
-            }
-
-            return false;
-        }
-
-        mWallpaperChangedNotifier.notifyWallpaperChanged();
-
-        // Send a broadcast to {@link RotatingWallpaperChangedReceiver} in the :live_wallpaper
-        // process so the currently displayed wallpaper updates if the live wallpaper is set to the
-        // device.
-        notifyLiveWallpaperBitmapChanged();
-
-        return true;
-    }
-
-    /**
      * Sets a wallpaper bitmap to the {@link WallpaperManagerCompat}.
      *
      * @return an integer wallpaper ID. This is an actual wallpaper ID on N and later versions of
@@ -638,7 +444,7 @@
      * operation was successful and zero if the operation encountered an error.
      */
     private int setBitmapToWallpaperManagerCompat(Bitmap wallpaperBitmap, boolean allowBackup,
-                                                  int whichWallpaper) {
+            int whichWallpaper) {
         ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();
         if (wallpaperBitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
             try {
@@ -668,9 +474,10 @@
     }
 
     private int setStreamToWallpaperManagerCompat(InputStream inputStream, boolean allowBackup,
-                                                  int whichWallpaper) {
+            int whichWallpaper) {
         try {
-            return mWallpaperManagerCompat.setStream(inputStream, null, allowBackup, whichWallpaper);
+            return mWallpaperManagerCompat.setStream(inputStream, null, allowBackup,
+                    whichWallpaper);
         } catch (IOException e) {
             return 0;
         }
@@ -688,7 +495,8 @@
                 mWallpaperInfoInPreview.getWallpaperComponent();
 
         // If there is no live wallpaper set on the WallpaperManager or it doesn't match the
-        // WallpaperInfo which was last previewed, then do nothing and nullify last previewed wallpaper.
+        // WallpaperInfo which was last previewed, then do nothing and nullify last previewed
+        // wallpaper.
         if (currentWallpaperComponent == null || previewedWallpaperComponent == null
                 || !currentWallpaperComponent.getPackageName()
                 .equals(previewedWallpaperComponent.getPackageName())) {
@@ -729,9 +537,9 @@
                 mWallpaperInfoInPreview.getWallpaperComponent();
 
         mWallpaperPreferences.clearHomeWallpaperMetadata();
-        // NOTE: We explicitly do not also clear the lock wallpaper metadata. Since the user may have
-        // set the live wallpaper on the home screen only, we leave the lock wallpaper metadata intact.
-        // If the user has set the live wallpaper for both home and lock screens, then the
+        // NOTE: We explicitly do not also clear the lock wallpaper metadata. Since the user may
+        // have set the live wallpaper on the home screen only, we leave the lock wallpaper metadata
+        // intact. If the user has set the live wallpaper for both home and lock screens, then the
         // WallpaperRefresher will pick up on that and update the preferences later.
         mWallpaperPreferences
                 .setHomeWallpaperAttributions(mWallpaperInfoInPreview.getAttributions(mAppContext));
@@ -744,36 +552,6 @@
         mWallpaperPreferences.clearDailyRotations();
     }
 
-    /**
-     * Notifies the :live_wallpaper process that the contents of the rotating live wallpaper bitmap
-     * changed.
-     */
-    private void notifyLiveWallpaperBitmapChanged() {
-        Intent intent = new Intent(mAppContext.getPackageName()
-                + NoBackupImageWallpaper.ACTION_ROTATING_WALLPAPER_CHANGED);
-        // Handled by a runtime-registered receiver in NoBackupImageWallpaper.
-        intent.setPackage(mAppContext.getPackageName());
-        mAppContext.sendBroadcast(intent);
-    }
-
-    /**
-     * Moves a file from the app's files directory to the device content protected storage
-     * directory.
-     * @param srcFileName Name of the source file (just the name, no path). It's expected to be
-     *                    located in {@link Context#getFilesDir()} for {@link #mAppContext}
-     * @param dstFileName Name of the destination file (just the name, no path), which will be
-     *                    located in {@link Context#getFilesDir()}
-     *                    for {@link #mDeviceProtectedContext}
-     * @return a {@link File} corresponding to the moved file in its new location, or null if
-     *      nothing was moved (because srcFileName didn't exist).
-     */
-    @Nullable
-    private File moveToDeviceProtectedStorage(String srcFileName, String dstFileName)
-            throws IOException {
-        return FileMover.moveFileBetweenContexts(mAppContext, srcFileName, mDeviceProtectedContext,
-                dstFileName);
-    }
-
     private class SetWallpaperTask extends AsyncTask<Void, Void, Boolean> {
 
         private final WallpaperInfo mWallpaper;
@@ -793,7 +571,7 @@
         private Point mStretchSize;
 
         SetWallpaperTask(WallpaperInfo wallpaper, Bitmap bitmap, @Destination int destination,
-                         WallpaperPersister.SetWallpaperCallback callback) {
+                WallpaperPersister.SetWallpaperCallback callback) {
             super();
             mWallpaper = wallpaper;
             mBitmap = bitmap;
@@ -806,7 +584,7 @@
          * will close the InputStream once it is done with it.
          */
         SetWallpaperTask(WallpaperInfo wallpaper, InputStream stream,
-                         @Destination int destination, WallpaperPersister.SetWallpaperCallback callback) {
+                @Destination int destination, WallpaperPersister.SetWallpaperCallback callback) {
             mWallpaper = wallpaper;
             mInputStream = stream;
             mDestination = destination;
@@ -815,16 +593,18 @@
 
         void setFillSize(Point fillSize) {
             if (mStretchSize != null) {
-                throw new IllegalArgumentException("Can't pass a fill size option if a stretch size is "
-                        + "already set.");
+                throw new IllegalArgumentException(
+                        "Can't pass a fill size option if a stretch size is "
+                                + "already set.");
             }
             mFillSize = fillSize;
         }
 
         void setStretchSize(Point stretchSize) {
             if (mFillSize != null) {
-                throw new IllegalArgumentException("Can't pass a stretch size option if a fill size is "
-                        + "already set.");
+                throw new IllegalArgumentException(
+                        "Can't pass a stretch size option if a fill size is "
+                                + "already set.");
             }
             mStretchSize = stretchSize;
         }
@@ -841,16 +621,9 @@
                         | WallpaperManagerCompat.FLAG_LOCK;
             }
 
-            // NOTE: The rotating wallpaper component must be determined here, _before_ actually setting
-            // the bitmap/stream on WallpaperManagerCompat, to ensure that the
-            // RotatingWallpaperComponentChecker is doing its check while rotation is still enabled.
-            // E.g., if "live wallpaper" is the component, then it needs to check while live wallpaper is
-            // still set as the active wallpaper on the device. Otherwise, the checker would see a static
-            // wallpaper is currently set and it would return the wrong value.
-            @RotatingWallpaperComponent int currentRotatingWallpaperComponent =
-                    mRotatingWallpaperComponentChecker.getCurrentRotatingWallpaperComponent(mAppContext);
 
-            boolean wasLockWallpaperSet = LockWallpaperStatusChecker.isLockWallpaperSet(mAppContext);
+            boolean wasLockWallpaperSet = LockWallpaperStatusChecker.isLockWallpaperSet(
+                    mAppContext);
 
             boolean allowBackup = mWallpaper.getBackupPermission() == WallpaperInfo.BACKUP_ALLOWED;
             final int wallpaperId;
@@ -860,15 +633,20 @@
                     mBitmap = BitmapTransformer.applyFillTransformation(mBitmap, mFillSize);
                 }
                 if (mStretchSize != null) {
-                    mBitmap = Bitmap.createScaledBitmap(mBitmap, mStretchSize.x, mStretchSize.y, true);
+                    mBitmap = Bitmap.createScaledBitmap(mBitmap, mStretchSize.x, mStretchSize.y,
+                            true);
                 }
 
-                wallpaperId = setBitmapToWallpaperManagerCompat(mBitmap, allowBackup, whichWallpaper);
+                wallpaperId = setBitmapToWallpaperManagerCompat(mBitmap, allowBackup,
+                        whichWallpaper);
             } else if (mInputStream != null) {
-                wallpaperId = setStreamToWallpaperManagerCompat(mInputStream, allowBackup, whichWallpaper);
+                wallpaperId = setStreamToWallpaperManagerCompat(mInputStream, allowBackup,
+                        whichWallpaper);
             } else {
-                Log.e(TAG, "Both the wallpaper bitmap and input stream are null so we're unable to set any "
-                        + "kind of wallpaper here.");
+                Log.e(TAG,
+                        "Both the wallpaper bitmap and input stream are null so we're unable to "
+                                + "set any "
+                                + "kind of wallpaper here.");
                 wallpaperId = 0;
             }
 
@@ -878,7 +656,7 @@
                         == WallpaperPreferences.PRESENTATION_MODE_ROTATING
                         && !wasLockWallpaperSet
                         && BuildCompat.isAtLeastN()) {
-                    copyRotatingWallpaperToLock(currentRotatingWallpaperComponent);
+                    copyRotatingWallpaperToLock();
                 }
                 setImageWallpaperMetadata(mDestination, wallpaperId);
                 return true;
@@ -914,12 +692,8 @@
          * Used to accommodate the case where a user had gone from a home+lock daily rotation to
          * selecting a static wallpaper on home-only. The image and metadata that was previously
          * rotating is now copied to the lock screen.
-         *
-         * @param currentRotatingWallpaperComponent The component in which rotating wallpapers were
-         *                                          presented.
          */
-        private void copyRotatingWallpaperToLock(
-                @RotatingWallpaperComponent int currentRotatingWallpaperComponent) {
+        private void copyRotatingWallpaperToLock() {
 
             mWallpaperPreferences.setLockWallpaperAttributions(
                     mWallpaperPreferences.getHomeWallpaperAttributions());
@@ -935,41 +709,28 @@
             // Set the lock wallpaper ID to what Android set it to, following its having
             // copied the system wallpaper over to the lock screen when we changed from
             // "both" to distinct system and lock screen wallpapers.
-            if (currentRotatingWallpaperComponent
-                    == RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_COMPONENT_STATIC) {
-                mWallpaperPreferences.setLockWallpaperId(
-                        mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK));
-            } else {
-                try {
-                    FileInputStream fileInputStream = mDeviceProtectedContext.openFileInput(
-                            NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH);
-                    int lockWallpaperId = setStreamToWallpaperManagerCompat(
-                            fileInputStream, false /* allowBackup */, WallpaperManagerCompat.FLAG_LOCK);
-                    fileInputStream.close();
-                    mWallpaperPreferences.setLockWallpaperId(lockWallpaperId);
-                } catch (FileNotFoundException e) {
-                    Log.e(TAG, "Couldn't copy over previously rotating wallpaper to lock screen.");
-                } catch (IOException e) {
-                    Log.e(TAG, "IOException when closing the file input stream " + e);
-                }
-            }
+            mWallpaperPreferences.setLockWallpaperId(
+                    mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK));
+
         }
 
         /**
-         * Sets the image wallpaper's metadata on SharedPreferences. This method is called after the set
-         * wallpaper operation is successful.
+         * Sets the image wallpaper's metadata on SharedPreferences. This method is called after the
+         * set wallpaper operation is successful.
          *
-         * @param destination Which destination of wallpaper the metadata corresponds to (home screen,
-         *                    lock screen, or both).
-         * @param wallpaperId The ID of the static wallpaper returned by WallpaperManager, which on N
-         *                    and later versions of Android uniquely identifies a wallpaper image.
+         * @param destination Which destination of wallpaper the metadata corresponds to (home
+         *                    screen, lock screen, or both).
+         * @param wallpaperId The ID of the static wallpaper returned by WallpaperManager, which
+         *                    on N and later versions of Android uniquely identifies a wallpaper
+         *                    image.
          */
         private void setImageWallpaperMetadata(@Destination int destination, int wallpaperId) {
             if (destination == DEST_HOME_SCREEN || destination == DEST_BOTH) {
                 mWallpaperPreferences.clearHomeWallpaperMetadata();
                 setImageWallpaperHomeMetadata(wallpaperId);
 
-                // Reset presentation mode to STATIC if an individual wallpaper is set to the home screen
+                // Reset presentation mode to STATIC if an individual wallpaper is set to the
+                // home screen
                 // because rotation always affects at least the home screen.
                 mWallpaperPreferences.setWallpaperPresentationMode(
                         WallpaperPreferences.PRESENTATION_MODE_STATIC);
@@ -988,10 +749,11 @@
                 mWallpaperPreferences.setHomeWallpaperManagerId(homeWallpaperId);
             }
 
-            // Compute bitmap hash code after setting the wallpaper because JPEG compression has likely
-            // changed many pixels' color values. Forget the previously loaded wallpaper bitmap so that
-            // WallpaperManager doesn't return the old wallpaper drawable. Do this on N+ devices in
-            // addition to saving the wallpaper ID for the purpose of backup & restore.
+            // Compute bitmap hash code after setting the wallpaper because JPEG compression has
+            // likely changed many pixels' color values. Forget the previously loaded wallpaper
+            // bitmap so that WallpaperManager doesn't return the old wallpaper drawable. Do this
+            // on N+ devices in addition to saving the wallpaper ID for the purpose of backup &
+            // restore.
             mWallpaperManager.forgetLoadedWallpaper();
             mBitmap = ((BitmapDrawable) mWallpaperManagerCompat.getDrawable()).getBitmap();
             long bitmapHash = BitmapUtils.generateHashCode(mBitmap);
@@ -1023,9 +785,10 @@
             mWallpaperPreferences.setLockWallpaperCollectionId(
                     mWallpaper.getCollectionId(mAppContext));
 
-            // Save the lock wallpaper image's hash code as well for the sake of backup & restore because
-            // WallpaperManager-generated IDs are specific to a physical device and cannot be used to
-            // identify a wallpaper image on another device after restore is complete.
+            // Save the lock wallpaper image's hash code as well for the sake of backup & restore
+            // because WallpaperManager-generated IDs are specific to a physical device and
+            // cannot be  used to identify a wallpaper image on another device after restore is
+            // complete.
             saveLockWallpaperHashCode();
         }
 
@@ -1051,7 +814,9 @@
                     try {
                         fileStream.close();
                     } catch (IOException e) {
-                        Log.e(TAG, "IO exception when closing the input stream for the lock screen WP.");
+                        Log.e(TAG,
+                                "IO exception when closing the input stream for the lock screen "
+                                        + "WP.");
                     }
                 }
             }
diff --git a/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java b/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java
index 319276b..b040ebb 100755
--- a/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java
+++ b/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java
@@ -24,6 +24,8 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.wallpaper.module.WallpaperPreferenceKeys.NoBackupKeys;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 
@@ -39,10 +41,12 @@
  */
 public class DefaultWallpaperPreferences implements WallpaperPreferences {
     public static final String PREFS_NAME = "wallpaper";
+    public static final String NO_BACKUP_PREFS_NAME = "wallpaper-nobackup";
 
     private static final String TAG = "DefaultWPPreferences";
 
     protected SharedPreferences mSharedPrefs;
+    protected SharedPreferences mNoBackupPrefs;
     protected Context mContext;
 
     // Keep a strong reference to this OnSharedPreferenceChangeListener to prevent the listener from
@@ -51,19 +55,106 @@
 
     public DefaultWallpaperPreferences(Context context) {
         mSharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+        mNoBackupPrefs = context.getSharedPreferences(NO_BACKUP_PREFS_NAME, Context.MODE_PRIVATE);
+        if (mNoBackupPrefs.getAll().isEmpty() && !mSharedPrefs.getAll().isEmpty()) {
+            upgradePrefs();
+        }
         mContext = context.getApplicationContext();
 
         // Register a prefs changed listener so that all prefs changes trigger a backup event.
         final BackupManager backupManager = new BackupManager(context);
-        mSharedPrefsChangedListener = new OnSharedPreferenceChangeListener() {
-            @Override
-            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-                backupManager.dataChanged();
-            }
-        };
+        mSharedPrefsChangedListener = (sharedPreferences, key) -> backupManager.dataChanged();
         mSharedPrefs.registerOnSharedPreferenceChangeListener(mSharedPrefsChangedListener);
     }
 
+    /**
+     * Move {@link NoBackupKeys} preferences that might have been in mSharedPrefs from previous
+     * versions of the app into mNoBackupPrefs.
+     */
+    private void upgradePrefs() {
+        SharedPreferences.Editor editor = mNoBackupPrefs.edit();
+        if (mSharedPrefs.contains(
+                NoBackupKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL)) {
+            editor.putString(NoBackupKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL,
+                    mSharedPrefs.getString(NoBackupKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL, null));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_HOME_WALLPAPER_MANAGER_ID)) {
+            editor.putInt(NoBackupKeys.KEY_HOME_WALLPAPER_MANAGER_ID,
+                    mSharedPrefs.getInt(NoBackupKeys.KEY_HOME_WALLPAPER_MANAGER_ID, 0));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_HOME_WALLPAPER_REMOTE_ID)) {
+            editor.putString(NoBackupKeys.KEY_HOME_WALLPAPER_REMOTE_ID,
+                    mSharedPrefs.getString(NoBackupKeys.KEY_HOME_WALLPAPER_REMOTE_ID, null));
+        }
+        if (mSharedPrefs.contains(
+                NoBackupKeys.KEY_HOME_WALLPAPER_BACKING_FILE)) {
+            editor.putString(NoBackupKeys.KEY_HOME_WALLPAPER_BACKING_FILE,
+                    mSharedPrefs.getString(NoBackupKeys.KEY_HOME_WALLPAPER_BACKING_FILE, null));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_LOCK_WALLPAPER_MANAGER_ID)) {
+            editor.putInt(NoBackupKeys.KEY_LOCK_WALLPAPER_MANAGER_ID,
+                    mSharedPrefs.getInt(NoBackupKeys.KEY_LOCK_WALLPAPER_MANAGER_ID, 0));
+        }
+        if (mSharedPrefs.contains(
+                NoBackupKeys.KEY_LOCK_WALLPAPER_BACKING_FILE)) {
+            editor.putString(NoBackupKeys.KEY_LOCK_WALLPAPER_BACKING_FILE,
+                    mSharedPrefs.getString(NoBackupKeys.KEY_LOCK_WALLPAPER_BACKING_FILE, null));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS)) {
+            editor.putString(NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS,
+                    mSharedPrefs.getString(NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS, null));
+        }
+        if (mSharedPrefs.contains(
+                NoBackupKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP)) {
+            editor.putLong(NoBackupKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP,
+                    mSharedPrefs.getLong(NoBackupKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP, -1));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_LAST_DAILY_LOG_TIMESTAMP)) {
+            editor.putLong(NoBackupKeys.KEY_LAST_DAILY_LOG_TIMESTAMP,
+                    mSharedPrefs.getLong(NoBackupKeys.KEY_LAST_DAILY_LOG_TIMESTAMP, 0));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_LAST_APP_ACTIVE_TIMESTAMP)) {
+            editor.putLong(NoBackupKeys.KEY_LAST_APP_ACTIVE_TIMESTAMP,
+                    mSharedPrefs.getLong(NoBackupKeys.KEY_LAST_APP_ACTIVE_TIMESTAMP, 0));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_LAST_ROTATION_STATUS)) {
+            editor.putInt(NoBackupKeys.KEY_LAST_ROTATION_STATUS,
+                    mSharedPrefs.getInt(NoBackupKeys.KEY_LAST_ROTATION_STATUS, -1));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_LAST_ROTATION_STATUS_TIMESTAMP)) {
+            editor.putLong(NoBackupKeys.KEY_LAST_ROTATION_STATUS_TIMESTAMP,
+                    mSharedPrefs.getLong(NoBackupKeys.KEY_LAST_ROTATION_STATUS_TIMESTAMP, 0));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_LAST_SYNC_TIMESTAMP)) {
+            editor.putLong(NoBackupKeys.KEY_LAST_SYNC_TIMESTAMP,
+                    mSharedPrefs.getLong(NoBackupKeys.KEY_LAST_SYNC_TIMESTAMP, 0));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_PENDING_WALLPAPER_SET_STATUS)) {
+            editor.putInt(NoBackupKeys.KEY_PENDING_WALLPAPER_SET_STATUS,
+                    mSharedPrefs.getInt(NoBackupKeys.KEY_PENDING_WALLPAPER_SET_STATUS,
+                            WALLPAPER_SET_NOT_PENDING));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS)) {
+            editor.putInt(NoBackupKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS,
+                    mSharedPrefs.getInt(NoBackupKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS,
+                            DAILY_WALLPAPER_UPDATE_NOT_PENDING));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED)) {
+            editor.putInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED,
+                    mSharedPrefs.getInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED, 0));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED)) {
+            editor.putInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED,
+                    mSharedPrefs.getInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED, 0));
+        }
+        if (mSharedPrefs.contains(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME)) {
+            editor.putString(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME,
+                    mSharedPrefs.getString(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, null));
+        }
+
+        editor.apply();
+    }
+
     private int getResIdPersistedByName(String key, String type) {
         String resName = mSharedPrefs.getString(key, null);
         if (resName == null) {
@@ -106,13 +197,16 @@
     public void setHomeWallpaperAttributions(List<String> attributions) {
         SharedPreferences.Editor editor = mSharedPrefs.edit();
         if (attributions.size() > 0) {
-            editor.putString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ATTRIB_1, attributions.get(0));
+            editor.putString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ATTRIB_1,
+                    attributions.get(0));
         }
         if (attributions.size() > 1) {
-            editor.putString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ATTRIB_2, attributions.get(1));
+            editor.putString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ATTRIB_2,
+                    attributions.get(1));
         }
         if (attributions.size() > 2) {
-            editor.putString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ATTRIB_3, attributions.get(2));
+            editor.putString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ATTRIB_3,
+                    attributions.get(2));
         }
         editor.apply();
     }
@@ -155,19 +249,22 @@
 
     @Override
     public String getHomeWallpaperBaseImageUrl() {
-        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL, null);
+        return mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL, null);
     }
 
     @Override
     public void setHomeWallpaperBaseImageUrl(String baseImageUrl) {
-        mSharedPrefs.edit().putString(
-                WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL, baseImageUrl).apply();
+        mNoBackupPrefs.edit().putString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL, baseImageUrl)
+                .apply();
     }
 
     @Override
     @Nullable
     public String getHomeWallpaperCollectionId() {
-        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_COLLECTION_ID, null);
+        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_COLLECTION_ID,
+                null);
     }
 
     @Override
@@ -179,14 +276,14 @@
     @Override
     @Nullable
     public String getHomeWallpaperBackingFileName() {
-        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_BACKING_FILE,
-                null);
+        return mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_BACKING_FILE, null);
     }
 
     @Override
     public void setHomeWallpaperBackingFileName(String fileName) {
-        mSharedPrefs.edit().putString(
-                WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_BACKING_FILE, fileName).apply();
+        mNoBackupPrefs.edit().putString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_BACKING_FILE, fileName).apply();
     }
 
     @Override
@@ -213,47 +310,56 @@
                 .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ACTION_URL)
                 .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ACTION_LABEL_RES)
                 .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_ACTION_ICON_RES)
-                .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL)
                 .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_HASH_CODE)
-                .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_MANAGER_ID)
-                .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME)
-                .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_REMOTE_ID)
-                .remove(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_BACKING_FILE)
+                .apply();
+
+        mNoBackupPrefs.edit()
+                .remove(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME)
+                .remove(NoBackupKeys.KEY_HOME_WALLPAPER_MANAGER_ID)
+                .remove(NoBackupKeys.KEY_HOME_WALLPAPER_REMOTE_ID)
+                .remove(NoBackupKeys.KEY_HOME_WALLPAPER_BASE_IMAGE_URL)
+                .remove(NoBackupKeys.KEY_HOME_WALLPAPER_BACKING_FILE)
                 .apply();
     }
 
     @Override
     public String getHomeWallpaperPackageName() {
-        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, null);
+        return mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, null);
     }
 
     @Override
     public void setHomeWallpaperPackageName(String packageName) {
-        mSharedPrefs.edit().putString(
-                WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, packageName).apply();
+        mNoBackupPrefs.edit().putString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, packageName)
+                .apply();
     }
 
     @Override
     public int getHomeWallpaperManagerId() {
-        return mSharedPrefs.getInt(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_MANAGER_ID, 0);
+        return mNoBackupPrefs.getInt(
+                NoBackupKeys.KEY_HOME_WALLPAPER_MANAGER_ID, 0);
     }
 
     @Override
     public void setHomeWallpaperManagerId(int homeWallpaperId) {
-        mSharedPrefs.edit().putInt(
-                WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_MANAGER_ID, homeWallpaperId).apply();
+        mNoBackupPrefs.edit().putInt(
+                NoBackupKeys.KEY_HOME_WALLPAPER_MANAGER_ID, homeWallpaperId)
+                .apply();
     }
 
     @Nullable
     @Override
     public String getHomeWallpaperRemoteId() {
-        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_REMOTE_ID, null);
+        return mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_REMOTE_ID, null);
     }
 
     @Override
     public void setHomeWallpaperRemoteId(@Nullable String wallpaperRemoteId) {
-        mSharedPrefs.edit().putString(
-                WallpaperPreferenceKeys.KEY_HOME_WALLPAPER_REMOTE_ID, wallpaperRemoteId).apply();
+        mNoBackupPrefs.edit().putString(
+                NoBackupKeys.KEY_HOME_WALLPAPER_REMOTE_ID, wallpaperRemoteId)
+                .apply();
     }
 
     @Override
@@ -269,13 +375,16 @@
     public void setLockWallpaperAttributions(List<String> attributions) {
         SharedPreferences.Editor editor = mSharedPrefs.edit();
         if (attributions.size() > 0) {
-            editor.putString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ATTRIB_1, attributions.get(0));
+            editor.putString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ATTRIB_1,
+                    attributions.get(0));
         }
         if (attributions.size() > 1) {
-            editor.putString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ATTRIB_2, attributions.get(1));
+            editor.putString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ATTRIB_2,
+                    attributions.get(1));
         }
         if (attributions.size() > 2) {
-            editor.putString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ATTRIB_3, attributions.get(2));
+            editor.putString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ATTRIB_3,
+                    attributions.get(2));
         }
         editor.apply();
     }
@@ -319,7 +428,8 @@
     @Override
     @Nullable
     public String getLockWallpaperCollectionId() {
-        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_COLLECTION_ID, null);
+        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_COLLECTION_ID,
+                null);
     }
 
     @Override
@@ -331,25 +441,27 @@
     @Override
     @Nullable
     public String getLockWallpaperBackingFileName() {
-        return mSharedPrefs.getString(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_BACKING_FILE,
-                null);
+        return mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_LOCK_WALLPAPER_BACKING_FILE, null);
     }
 
     @Override
     public void setLockWallpaperBackingFileName(String fileName) {
-        mSharedPrefs.edit().putString(
-                WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_BACKING_FILE, fileName).apply();
+        mNoBackupPrefs.edit().putString(
+                NoBackupKeys.KEY_LOCK_WALLPAPER_BACKING_FILE, fileName).apply();
     }
 
     @Override
     public int getLockWallpaperId() {
-        return mSharedPrefs.getInt(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_MANAGER_ID, 0);
+        return mNoBackupPrefs.getInt(
+                NoBackupKeys.KEY_LOCK_WALLPAPER_MANAGER_ID, 0);
     }
 
     @Override
     public void setLockWallpaperId(int lockWallpaperId) {
-        mSharedPrefs.edit().putInt(
-                WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_MANAGER_ID, lockWallpaperId).apply();
+        mNoBackupPrefs.edit().putInt(
+                NoBackupKeys.KEY_LOCK_WALLPAPER_MANAGER_ID, lockWallpaperId)
+                .apply();
     }
 
     @Override
@@ -378,21 +490,25 @@
                 .remove(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ACTION_LABEL_RES)
                 .remove(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_ACTION_ICON_RES)
                 .remove(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_HASH_CODE)
-                .remove(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_MANAGER_ID)
-                .remove(WallpaperPreferenceKeys.KEY_LOCK_WALLPAPER_BACKING_FILE)
+                .apply();
+
+        mNoBackupPrefs.edit()
+                .remove(NoBackupKeys.KEY_LOCK_WALLPAPER_MANAGER_ID)
+                .remove(NoBackupKeys.KEY_LOCK_WALLPAPER_BACKING_FILE)
                 .apply();
     }
 
     @Override
     public void addDailyRotation(long timestamp) {
-        String jsonString = mSharedPrefs.getString(
-                WallpaperPreferenceKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
+        String jsonString = mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
         try {
             JSONArray jsonArray = new JSONArray(jsonString);
             jsonArray.put(timestamp);
 
-            mSharedPrefs.edit()
-                    .putString(WallpaperPreferenceKeys.KEY_DAILY_ROTATION_TIMESTAMPS, jsonArray.toString())
+            mNoBackupPrefs.edit()
+                    .putString(NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS,
+                            jsonArray.toString())
                     .apply();
         } catch (JSONException e) {
             Log.e(TAG, "Failed to add a daily rotation timestamp due to a JSON parse exception");
@@ -401,8 +517,8 @@
 
     @Override
     public long getLastDailyRotationTimestamp() {
-        String jsonString = mSharedPrefs.getString(
-                WallpaperPreferenceKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
+        String jsonString = mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
 
         try {
             JSONArray jsonArray = new JSONArray(jsonString);
@@ -428,15 +544,15 @@
         oneWeekAgo.add(Calendar.WEEK_OF_YEAR, -1);
         long oneWeekAgoTimestamp = oneWeekAgo.getTimeInMillis();
 
-        // Return null if daily rotation wasn't enabled (timestamp value of -1) or was enabled earlier
-        // than one week ago.
+        // Return null if daily rotation wasn't enabled (timestamp value of -1) or was enabled
+        // less than one week ago.
         if (enabledTimestamp == -1 || enabledTimestamp > oneWeekAgoTimestamp) {
             return null;
         }
 
         List<Long> timestamps = new ArrayList<>();
-        String jsonString = mSharedPrefs.getString(
-                WallpaperPreferenceKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
+        String jsonString = mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
 
         try {
             JSONArray jsonArray = new JSONArray(jsonString);
@@ -451,8 +567,9 @@
             }
 
             jsonArray = new JSONArray(timestamps);
-            mSharedPrefs.edit()
-                    .putString(WallpaperPreferenceKeys.KEY_DAILY_ROTATION_TIMESTAMPS, jsonArray.toString())
+            mNoBackupPrefs.edit()
+                    .putString(NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS,
+                            jsonArray.toString())
                     .apply();
         } catch (JSONException e) {
             Log.e(TAG, "Failed to get daily rotation timestamps due to a JSON parse exception");
@@ -479,21 +596,21 @@
         midnightToday.set(Calendar.MINUTE, 0);
         long midnightTodayTimestamp = midnightToday.getTimeInMillis();
 
-        // Return null if daily rotation wasn't enabled (timestamp value of -1) or was enabled earlier
-        // than midnight yesterday.
+        // Return null if daily rotation wasn't enabled (timestamp value of -1) or was enabled
+        // less than midnight yesterday.
         if (enabledTimestamp == -1 || enabledTimestamp > midnightYesterdayTimestamp) {
             return null;
         }
 
         List<Long> timestamps = new ArrayList<>();
-        String jsonString = mSharedPrefs.getString(
-                WallpaperPreferenceKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
+        String jsonString = mNoBackupPrefs.getString(
+                NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
 
         try {
             JSONArray jsonArray = new JSONArray(jsonString);
 
-            // Filter the timestamps (which cover up to one week of data) to only include those between
-            // midnight yesterday and midnight today.
+            // Filter the timestamps (which cover up to one week of data) to only include those
+            // between midnight yesterday and midnight today.
             for (int i = 0; i < jsonArray.length(); i++) {
                 long timestamp = jsonArray.getLong(i);
                 if (timestamp >= midnightYesterdayTimestamp && timestamp < midnightTodayTimestamp) {
@@ -510,160 +627,175 @@
 
     @Override
     public long getDailyWallpaperEnabledTimestamp() {
-        return mSharedPrefs.getLong(WallpaperPreferenceKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP, -1);
+        return mNoBackupPrefs.getLong(
+                NoBackupKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP, -1);
     }
 
     @Override
     public void setDailyWallpaperEnabledTimestamp(long timestamp) {
-        mSharedPrefs.edit()
-                .putLong(WallpaperPreferenceKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP, timestamp)
+        mNoBackupPrefs.edit()
+                .putLong(NoBackupKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP,
+                        timestamp)
                 .apply();
     }
 
     @Override
     public void clearDailyRotations() {
-        mSharedPrefs.edit()
-                .remove(WallpaperPreferenceKeys.KEY_DAILY_ROTATION_TIMESTAMPS)
-                .remove(WallpaperPreferenceKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP)
+        mNoBackupPrefs.edit()
+                .remove(NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS)
+                .remove(NoBackupKeys.KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP)
                 .apply();
     }
 
     @Override
     public long getLastDailyLogTimestamp() {
-        return mSharedPrefs.getLong(WallpaperPreferenceKeys.KEY_LAST_DAILY_LOG_TIMESTAMP, 0);
+        return mNoBackupPrefs.getLong(
+                NoBackupKeys.KEY_LAST_DAILY_LOG_TIMESTAMP, 0);
     }
 
     @Override
     public void setLastDailyLogTimestamp(long timestamp) {
-        mSharedPrefs.edit()
-                .putLong(WallpaperPreferenceKeys.KEY_LAST_DAILY_LOG_TIMESTAMP, timestamp)
+        mNoBackupPrefs.edit()
+                .putLong(NoBackupKeys.KEY_LAST_DAILY_LOG_TIMESTAMP, timestamp)
                 .apply();
     }
 
     @Override
     public long getLastAppActiveTimestamp() {
-        return mSharedPrefs.getLong(WallpaperPreferenceKeys.KEY_LAST_APP_ACTIVE_TIMESTAMP, 0);
+        return mNoBackupPrefs.getLong(
+                NoBackupKeys.KEY_LAST_APP_ACTIVE_TIMESTAMP, 0);
     }
 
     @Override
     public void setLastAppActiveTimestamp(long timestamp) {
-        mSharedPrefs.edit()
-                .putLong(WallpaperPreferenceKeys.KEY_LAST_APP_ACTIVE_TIMESTAMP, timestamp)
+        mNoBackupPrefs.edit()
+                .putLong(NoBackupKeys.KEY_LAST_APP_ACTIVE_TIMESTAMP, timestamp)
                 .apply();
     }
 
     @Override
     public void setDailyWallpaperRotationStatus(int status, long timestamp) {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_LAST_ROTATION_STATUS, status)
-                .putLong(WallpaperPreferenceKeys.KEY_LAST_ROTATION_STATUS_TIMESTAMP, timestamp)
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_LAST_ROTATION_STATUS, status)
+                .putLong(NoBackupKeys.KEY_LAST_ROTATION_STATUS_TIMESTAMP,
+                        timestamp)
                 .apply();
     }
 
     @Override
     public int getDailyWallpaperLastRotationStatus() {
-        return mSharedPrefs.getInt(WallpaperPreferenceKeys.KEY_LAST_ROTATION_STATUS, -1);
+        return mNoBackupPrefs.getInt(NoBackupKeys.KEY_LAST_ROTATION_STATUS, -1);
     }
 
     @Override
     public long getDailyWallpaperLastRotationStatusTimestamp() {
-        return mSharedPrefs.getLong(WallpaperPreferenceKeys.KEY_LAST_ROTATION_STATUS_TIMESTAMP, 0);
+        return mNoBackupPrefs.getLong(
+                NoBackupKeys.KEY_LAST_ROTATION_STATUS_TIMESTAMP, 0);
     }
 
     @Override
     public long getLastSyncTimestamp() {
-        return mSharedPrefs.getLong(WallpaperPreferenceKeys.KEY_LAST_SYNC_TIMESTAMP, 0);
+        return mNoBackupPrefs.getLong(NoBackupKeys.KEY_LAST_SYNC_TIMESTAMP, 0);
     }
 
     @Override
     public void setLastSyncTimestamp(long timestamp) {
-        // Write synchronously via commit() to ensure this timetsamp gets written to disk immediately.
-        mSharedPrefs.edit()
-                .putLong(WallpaperPreferenceKeys.KEY_LAST_SYNC_TIMESTAMP, timestamp)
+        // Write synchronously via commit() to ensure this timetsamp gets written to disk
+        // immediately.
+        mNoBackupPrefs.edit()
+                .putLong(NoBackupKeys.KEY_LAST_SYNC_TIMESTAMP, timestamp)
                 .commit();
     }
 
     @Override
     public void setPendingWallpaperSetStatusSync(@PendingWallpaperSetStatus int setStatus) {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_PENDING_WALLPAPER_SET_STATUS, setStatus)
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_PENDING_WALLPAPER_SET_STATUS,
+                        setStatus)
                 .commit();
     }
 
     @Override
     public int getPendingWallpaperSetStatus() {
         //noinspection ResourceType
-        return mSharedPrefs.getInt(
-                WallpaperPreferenceKeys.KEY_PENDING_WALLPAPER_SET_STATUS, WALLPAPER_SET_NOT_PENDING);
+        return mNoBackupPrefs.getInt(
+                NoBackupKeys.KEY_PENDING_WALLPAPER_SET_STATUS,
+                WALLPAPER_SET_NOT_PENDING);
     }
 
     @Override
     public void setPendingWallpaperSetStatus(@PendingWallpaperSetStatus int setStatus) {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_PENDING_WALLPAPER_SET_STATUS, setStatus)
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_PENDING_WALLPAPER_SET_STATUS,
+                        setStatus)
                 .apply();
     }
 
     @Override
     public void setPendingDailyWallpaperUpdateStatusSync(
             @PendingDailyWallpaperUpdateStatus int updateStatus) {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS, updateStatus)
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS,
+                        updateStatus)
                 .commit();
     }
 
     @Override
     public int getPendingDailyWallpaperUpdateStatus() {
         //noinspection ResourceType
-        return mSharedPrefs.getInt(WallpaperPreferenceKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS,
+        return mNoBackupPrefs.getInt(
+                NoBackupKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS,
                 DAILY_WALLPAPER_UPDATE_NOT_PENDING);
     }
 
     @Override
     public void setPendingDailyWallpaperUpdateStatus(
             @PendingDailyWallpaperUpdateStatus int updateStatus) {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS, updateStatus)
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS,
+                        updateStatus)
                 .apply();
     }
 
     @Override
     public void incrementNumDaysDailyRotationFailed() {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED,
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED,
                         getNumDaysDailyRotationFailed() + 1)
                 .apply();
     }
 
     @Override
     public int getNumDaysDailyRotationFailed() {
-        return mSharedPrefs.getInt(WallpaperPreferenceKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED, 0);
+        return mNoBackupPrefs.getInt(
+                NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED, 0);
     }
 
     @Override
     public void resetNumDaysDailyRotationFailed() {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED, 0)
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_FAILED, 0)
                 .apply();
     }
 
     @Override
     public void incrementNumDaysDailyRotationNotAttempted() {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED,
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED,
                         getNumDaysDailyRotationNotAttempted() + 1)
                 .apply();
     }
 
     @Override
     public int getNumDaysDailyRotationNotAttempted() {
-        return mSharedPrefs.getInt(WallpaperPreferenceKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED, 0);
+        return mNoBackupPrefs.getInt(
+                NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED, 0);
     }
 
     @Override
     public void resetNumDaysDailyRotationNotAttempted() {
-        mSharedPrefs.edit()
-                .putInt(WallpaperPreferenceKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED, 0)
+        mNoBackupPrefs.edit()
+                .putInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED, 0)
                 .apply();
     }
 }
diff --git a/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java b/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java
index c97d2b9..d85e04e 100755
--- a/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java
+++ b/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java
@@ -32,7 +32,6 @@
 import com.android.wallpaper.model.WallpaperMetadata;
 
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -50,9 +49,6 @@
     private final Context mAppContext;
     private final WallpaperPreferences mWallpaperPreferences;
     private final WallpaperManager mWallpaperManager;
-    private final LiveWallpaperStatusChecker mLiveWallpaperStatusChecker;
-    private final UserEventLogger mUserEventLogger;
-    private final Context mDeviceProtectedContext;
 
     /**
      * @param context The application's context.
@@ -62,13 +58,10 @@
 
         Injector injector = InjectorProvider.getInjector();
         mWallpaperPreferences = injector.getPreferences(mAppContext);
-        mLiveWallpaperStatusChecker = injector.getLiveWallpaperStatusChecker(mAppContext);
-        mUserEventLogger = injector.getUserEventLogger(mAppContext);
 
         // Retrieve WallpaperManager using Context#getSystemService instead of
         // WallpaperManager#getInstance so it can be mocked out in test.
         mWallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
-        mDeviceProtectedContext = mAppContext.createDeviceProtectedStorageContext();
     }
 
     @Override
@@ -203,8 +196,7 @@
          * current system wallpaper.
          */
         private boolean isHomeScreenMetadataCurrent() {
-            return (mWallpaperManager.getWallpaperInfo() == null
-                    || mLiveWallpaperStatusChecker.isNoBackupImageWallpaperSet())
+            return (mWallpaperManager.getWallpaperInfo() == null)
                     ? isHomeScreenImageWallpaperCurrent()
                     : isHomeScreenLiveWallpaperCurrent();
         }
@@ -221,36 +213,6 @@
 
         private long getCurrentHomeWallpaperHashCode() {
             if (mCurrentHomeWallpaperHashCode == 0) {
-                if (mLiveWallpaperStatusChecker.isNoBackupImageWallpaperSet()) {
-
-                    synchronized (RotatingWallpaperLockProvider.getInstance()) {
-                        Bitmap bitmap = null;
-                        try {
-                            FileInputStream fis =
-                                    mDeviceProtectedContext.openFileInput(
-                                            NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH);
-                            bitmap = BitmapFactory.decodeStream(fis);
-                            fis.close();
-                        } catch (FileNotFoundException e) {
-                            Log.e(TAG, "Rotating wallpaper file not found at path: "
-                                    + mDeviceProtectedContext.getFileStreamPath(
-                                            NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH),
-                                    e);
-                        } catch (IOException e) {
-                            Log.e(TAG, "IOException when closing FileInputStream " + e);
-                        }
-
-                        if (bitmap != null) {
-                            mCurrentHomeWallpaperHashCode = BitmapUtils.generateHashCode(bitmap);
-                            mUserEventLogger.logDailyWallpaperDecodes(true);
-                        } else {
-                            // If an error occurred decoding the stream then we should just assume the current
-                            // home wallpaper remained intact.
-                            mCurrentHomeWallpaperHashCode = mWallpaperPreferences.getHomeWallpaperHashCode();
-                            mUserEventLogger.logDailyWallpaperDecodes(false);
-                        }
-                    }
-                } else {
                     BitmapDrawable wallpaperDrawable = (BitmapDrawable) mWallpaperManagerCompat.getDrawable();
                     Bitmap wallpaperBitmap = wallpaperDrawable.getBitmap();
                     mCurrentHomeWallpaperHashCode = BitmapUtils.generateHashCode(wallpaperBitmap);
@@ -258,7 +220,6 @@
                     // Manually request that WallpaperManager loses its reference to the current wallpaper
                     // bitmap, which can occupy a large memory allocation for the lifetime of the app.
                     mWallpaperManager.forgetLoadedWallpaper();
-                }
             }
             return mCurrentHomeWallpaperHashCode;
         }
diff --git a/src/com/android/wallpaper/module/LiveWallpaperStatusChecker.java b/src/com/android/wallpaper/module/DrawableLayerResolver.java
old mode 100755
new mode 100644
similarity index 61%
rename from src/com/android/wallpaper/module/LiveWallpaperStatusChecker.java
rename to src/com/android/wallpaper/module/DrawableLayerResolver.java
index 67d87f8..5c61433
--- a/src/com/android/wallpaper/module/LiveWallpaperStatusChecker.java
+++ b/src/com/android/wallpaper/module/DrawableLayerResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -15,13 +15,16 @@
  */
 package com.android.wallpaper.module;
 
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+
 /**
- * Reads whether the application's live wallpaper service is set to the device.
+ * Interface for a class that can be used to arbitrarily select a Layer from a LayerDrawable
  */
-public interface LiveWallpaperStatusChecker {
+public interface DrawableLayerResolver {
 
     /**
-     * Returns whether the live wallpaper for daily wallpapers is set to the device.
+     * Picks a layer from a given {@link LayerDrawable}
      */
-    boolean isNoBackupImageWallpaperSet();
+    Drawable resolveLayer(LayerDrawable layerDrawable);
 }
diff --git a/src/com/android/wallpaper/module/Injector.java b/src/com/android/wallpaper/module/Injector.java
index 6402496..ca64b9d 100755
--- a/src/com/android/wallpaper/module/Injector.java
+++ b/src/com/android/wallpaper/module/Injector.java
@@ -17,6 +17,8 @@
 
 import android.content.Context;
 
+import androidx.fragment.app.Fragment;
+
 import com.android.wallpaper.compat.WallpaperManagerCompat;
 import com.android.wallpaper.model.CategoryProvider;
 import com.android.wallpaper.model.WallpaperInfo;
@@ -25,8 +27,6 @@
 import com.android.wallpaper.picker.PreviewFragment.PreviewMode;
 import com.android.wallpaper.picker.individual.IndividualPickerFragment;
 
-import androidx.fragment.app.Fragment;
-
 /**
  * Interface for a provider of "injected dependencies." (NOTE: The term "injector" is somewhat of a
  * misnomer; this is more aptly a service registry as part of a service locator design pattern.)
@@ -44,8 +44,6 @@
 
     FormFactorChecker getFormFactorChecker(Context context);
 
-    LiveWallpaperStatusChecker getLiveWallpaperStatusChecker(Context context);
-
     LoggingOptInStatusProvider getLoggingOptInStatusProvider(Context context);
 
     NetworkStatusNotifier getNetworkStatusNotifier(Context context);
@@ -56,8 +54,6 @@
 
     Requester getRequester(Context context);
 
-    RotatingWallpaperComponentChecker getRotatingWallpaperComponentChecker();
-
     SystemFeatureChecker getSystemFeatureChecker();
 
     UserEventLogger getUserEventLogger(Context context);
@@ -83,4 +79,6 @@
     IndividualPickerFragment getIndividualPickerFragment(String collectionId);
 
     LiveWallpaperInfoFactory getLiveWallpaperInfoFactory(Context context);
+
+    DrawableLayerResolver getDrawableLayerResolver();
 }
diff --git a/src/com/android/wallpaper/module/LiveWallpaperInfoFactory.java b/src/com/android/wallpaper/module/LiveWallpaperInfoFactory.java
index 0c9bfee..838bbb6 100644
--- a/src/com/android/wallpaper/module/LiveWallpaperInfoFactory.java
+++ b/src/com/android/wallpaper/module/LiveWallpaperInfoFactory.java
@@ -1,6 +1,20 @@
+/*
+ * 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.module;
 
-import com.android.wallpaper.model.LiveWallpaperInfo;
 import com.android.wallpaper.model.WallpaperInfo;
 
 /**
@@ -8,5 +22,7 @@
  */
 public interface LiveWallpaperInfoFactory {
 
-    public WallpaperInfo getLiveWallpaperInfo(android.app.WallpaperInfo info);
+    WallpaperInfo getLiveWallpaperInfo(android.app.WallpaperInfo info);
+
+    WallpaperInfo getLiveWallpaperInfo(android.app.WallpaperInfo info, boolean shouldShowTitle);
 }
diff --git a/src/com/android/wallpaper/module/NoBackupImageWallpaper.java b/src/com/android/wallpaper/module/NoBackupImageWallpaper.java
deleted file mode 100755
index a76d881..0000000
--- a/src/com/android/wallpaper/module/NoBackupImageWallpaper.java
+++ /dev/null
@@ -1,1018 +0,0 @@
-/*
- * Copyright (C) 2017 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.module;
-
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE;
-
-import android.annotation.SuppressLint;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.opengl.GLES20;
-import android.opengl.GLUtils;
-import android.os.AsyncTask;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.os.Handler;
-import android.renderscript.Matrix4f;
-import android.service.wallpaper.WallpaperService;
-import android.util.Log;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.SurfaceHolder;
-import android.view.WindowManager;
-
-import com.android.wallpaper.util.ScreenSizeCalculator;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-
-import androidx.annotation.RequiresApi;
-
-/**
- * Live wallpaper service which simply renders a wallpaper from internal storage. Designed as a
- * workaround to WallpaperManager not having an allowBackup=false option on pre-N builds of Android.
- * <p>
- * Adapted from {@code com.android.systemui.ImageWallpaper}.
- */
-@SuppressLint("ServiceCast")
-public class NoBackupImageWallpaper extends WallpaperService {
-
-    public static final String ACTION_ROTATING_WALLPAPER_CHANGED =
-            ".ACTION_ROTATING_WALLPAPER_CHANGED";
-    public static final String PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED =
-            ".NOTIFY_ROTATING_WALLPAPER_CHANGED";
-    public static final String PREVIEW_WALLPAPER_FILE_PATH = "preview_wallpaper.jpg";
-    public static final String ROTATING_WALLPAPER_FILE_PATH = "rotating_wallpaper.jpg";
-
-    private static final String TAG = "NoBackupImageWallpaper";
-    private static final String GL_LOG_TAG = "ImageWallpaperGL";
-    private static final boolean DEBUG = false;
-    private static final boolean FIXED_SIZED_SURFACE = false;
-
-    private final Handler mHandler = new Handler();
-
-    private int mOpenGlContextCounter;
-    private WallpaperManager mWallpaperManager;
-    private DrawableEngine mEngine;
-    private boolean mIsHardwareAccelerated;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        mOpenGlContextCounter = 0;
-        mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
-
-        // By default, use OpenGL for drawing the static wallpaper image.
-        mIsHardwareAccelerated = true;
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        if (mEngine != null) {
-            mEngine.trimMemory(level);
-        }
-    }
-
-    @Override
-    public Engine onCreateEngine() {
-        mEngine = new DrawableEngine();
-        return mEngine;
-    }
-
-    private class DrawableEngine extends Engine {
-        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
-        static final int EGL_OPENGL_ES2_BIT = 4;
-        private static final String S_SIMPLE_VS =
-                "attribute vec4 position;\n"
-                        + "attribute vec2 texCoords;\n"
-                        + "varying vec2 outTexCoords;\n"
-                        + "uniform mat4 projection;\n"
-                        + "\nvoid main(void) {\n"
-                        + "    outTexCoords = texCoords;\n"
-                        + "    gl_Position = projection * position;\n"
-                        + "}\n\n";
-        private static final String S_SIMPLE_FS =
-                "precision mediump float;\n\n"
-                        + "varying vec2 outTexCoords;\n"
-                        + "uniform sampler2D texture;\n"
-                        + "\nvoid main(void) {\n"
-                        + "    gl_FragColor = texture2D(texture, outTexCoords);\n"
-                        + "}\n\n";
-        private static final int FLOAT_SIZE_BYTES = 4;
-        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-        Bitmap mBackground;
-        WallpaperColors mCachedWallpaperColors;
-        int mBackgroundWidth = -1, mBackgroundHeight = -1;
-        int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
-        int mLastRotation = -1;
-        float mXOffset = 0.5f;
-        float mYOffset = 0.5f;
-        float mScale = 1f;
-        boolean mVisible = true;
-        boolean mOffsetsChanged;
-        int mLastXTranslation;
-        int mLastYTranslation;
-        private Display mDefaultDisplay;
-        private EGL10 mEgl;
-        private EGLDisplay mEglDisplay;
-        private EGLConfig mEglConfig;
-        private EGLContext mEglContext;
-        private EGLSurface mEglSurface;
-        private int mTexture;
-        private int mProgram;
-        private boolean mIsOpenGlTextureLoaded;
-        private int mRotationAtLastSurfaceSizeUpdate = -1;
-        private int mDisplayWidthAtLastSurfaceSizeUpdate = -1;
-        private int mDisplayHeightAtLastSurfaceSizeUpdate = -1;
-
-        private int mLastRequestedWidth = -1;
-        private int mLastRequestedHeight = -1;
-        private AsyncTask<Void, Void, Bitmap> mLoader;
-        private boolean mNeedsDrawAfterLoadingWallpaper;
-        private boolean mSurfaceValid;
-
-        private BroadcastReceiver mReceiver;
-
-        public DrawableEngine() {
-            super();
-        }
-
-        public void trimMemory(int level) {
-            if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
-                    && mBackground != null) {
-                if (DEBUG) {
-                    Log.d(TAG, "trimMemory");
-                }
-                mBackground.recycle();
-                mBackground = null;
-                mBackgroundWidth = -1;
-                mBackgroundHeight = -1;
-            }
-        }
-
-        @Override
-        public void onCreate(SurfaceHolder surfaceHolder) {
-            if (DEBUG) {
-                Log.d(TAG, "onCreate");
-            }
-
-            super.onCreate(surfaceHolder);
-
-            mIsOpenGlTextureLoaded = false;
-
-            mDefaultDisplay = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
-                    .getDefaultDisplay();
-
-            updateSurfaceSize(surfaceHolder, mDefaultDisplay, false /* forDraw */);
-
-            // Enable offset notifications to pan wallpaper for parallax effect.
-            setOffsetNotificationsEnabled(true);
-
-            // If not a preview, then register a local broadcast receiver for listening to changes in the
-            // rotating wallpaper file.
-            if (!isPreview()) {
-                IntentFilter filter = new IntentFilter();
-                filter.addAction(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED);
-
-                mReceiver = new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        if (DEBUG) {
-                            Log.i(TAG, "Broadcast received with intent: " + intent);
-                        }
-
-                        String action = intent.getAction();
-                        if (action.equals(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED)) {
-                            DrawableEngine.this.invalidateAndRedrawWallpaper();
-                        }
-                    }
-                };
-
-                registerReceiver(mReceiver, filter, getPackageName()
-                        + PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED, null /* handler */);
-            }
-        }
-
-        @Override
-        public void onDestroy() {
-            super.onDestroy();
-            mBackground = null;
-            mWallpaperManager.forgetLoadedWallpaper();
-
-            if (!isPreview() && mReceiver != null) {
-                unregisterReceiver(mReceiver);
-            }
-        }
-
-        boolean updateSurfaceSize(SurfaceHolder surfaceHolder, Display display, boolean forDraw) {
-            boolean hasWallpaper = true;
-            Point displaySize = ScreenSizeCalculator.getInstance().getScreenSize(display);
-
-            // Load background image dimensions, if we haven't saved them yet
-            if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
-                // Need to load the image to get dimensions
-                loadWallpaper(forDraw);
-                if (DEBUG) {
-                    Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
-                }
-                hasWallpaper = false;
-            }
-
-            // Force the wallpaper to cover the screen in both dimensions
-            int surfaceWidth = Math.max(displaySize.x, mBackgroundWidth);
-            int surfaceHeight = Math.max(displaySize.y, mBackgroundHeight);
-
-            if (FIXED_SIZED_SURFACE) {
-                // Used a fixed size surface, because we are special.  We can do
-                // this because we know the current design of window animations doesn't
-                // cause this to break.
-                surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
-                mLastRequestedWidth = surfaceWidth;
-                mLastRequestedHeight = surfaceHeight;
-            } else {
-                surfaceHolder.setSizeFromLayout();
-            }
-            return hasWallpaper;
-        }
-
-        @Override
-        public void onVisibilityChanged(boolean visible) {
-            if (DEBUG) {
-                Log.d(TAG, "onVisibilityChanged: mVisible, visible=" + mVisible + ", " + visible);
-            }
-
-            if (mVisible != visible) {
-                if (DEBUG) {
-                    Log.d(TAG, "Visibility changed to visible=" + visible);
-                }
-                mVisible = visible;
-                drawFrame(false /* forceRedraw */);
-            }
-        }
-
-        @Override
-        public void onTouchEvent(MotionEvent event) {
-            super.onTouchEvent(event);
-        }
-
-        @Override
-        public void onOffsetsChanged(float xOffset, float yOffset,
-                                     float xOffsetStep, float yOffsetStep,
-                                     int xPixels, int yPixels) {
-            if (DEBUG) {
-                Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset
-                        + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep
-                        + ", xPixels=" + xPixels + ", yPixels=" + yPixels);
-            }
-
-            if (mXOffset != xOffset || mYOffset != yOffset) {
-                if (DEBUG) {
-                    Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ").");
-                }
-                mXOffset = xOffset;
-                mYOffset = yOffset;
-                mOffsetsChanged = true;
-            }
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    drawFrame(false /* forceRedraw */);
-                }
-            });
-        }
-
-        @Override
-        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
-            }
-
-            super.onSurfaceChanged(holder, format, width, height);
-
-            // Retrieve buffer in new size.
-            if (mEgl != null) {
-                mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
-            }
-            drawFrame(false /* forceRedraw */);
-        }
-
-        @Override
-        public void onSurfaceDestroyed(SurfaceHolder holder) {
-            super.onSurfaceDestroyed(holder);
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceDestroyed");
-            }
-            mLastSurfaceWidth = mLastSurfaceHeight = -1;
-            mSurfaceValid = false;
-
-            if (mIsHardwareAccelerated) {
-                finishGL(mTexture, mProgram);
-            }
-        }
-
-        @Override
-        public void onSurfaceCreated(SurfaceHolder holder) {
-            super.onSurfaceCreated(holder);
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceCreated");
-            }
-            mLastSurfaceWidth = mLastSurfaceHeight = -1;
-            mSurfaceValid = true;
-
-            if (mIsHardwareAccelerated) {
-                if (!initGL(holder)) {
-                    // Fall back to canvas drawing if initializing OpenGL failed.
-                    mIsHardwareAccelerated = false;
-                    mEgl = null;
-                }
-            }
-        }
-
-        @Override
-        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
-            if (DEBUG) {
-                Log.d(TAG, "onSurfaceRedrawNeeded");
-            }
-            super.onSurfaceRedrawNeeded(holder);
-
-            drawFrame(true /* forceRedraw */);
-        }
-
-        @RequiresApi(VERSION_CODES.O_MR1)
-        @Override
-        public WallpaperColors onComputeColors() {
-            // It's OK to return null here.
-            return mCachedWallpaperColors;
-        }
-
-        /**
-         * Invalidates the currently-drawn wallpaper image, causing the engine to reload the image from
-         * disk and draw the new wallpaper image.
-         */
-        public void invalidateAndRedrawWallpaper() {
-            // If a wallpaper load was already in flight, cancel it and restart a load in order to decode
-            // the new image.
-            if (mLoader != null) {
-                mLoader.cancel(true /* mayInterruptIfRunning */);
-                mLoader = null;
-            }
-
-            loadWallpaper(true /* needsDraw */);
-        }
-
-        void drawFrame(boolean forceRedraw) {
-            if (!mSurfaceValid) {
-                return;
-            }
-
-            Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(mDefaultDisplay);
-            int newRotation = mDefaultDisplay.getRotation();
-
-            // Sometimes a wallpaper is not large enough to cover the screen in one dimension.
-            // Call updateSurfaceSize -- it will only actually do the update if the dimensions
-            // should change
-            if (newRotation != mLastRotation) {
-                // Update surface size (if necessary)
-                if (!updateSurfaceSize(getSurfaceHolder(), mDefaultDisplay, true /* forDraw */)) {
-                    return;
-                }
-                mRotationAtLastSurfaceSizeUpdate = newRotation;
-                mDisplayWidthAtLastSurfaceSizeUpdate = screenSize.x;
-                mDisplayHeightAtLastSurfaceSizeUpdate = screenSize.y;
-            }
-            SurfaceHolder sh = getSurfaceHolder();
-            final Rect frame = sh.getSurfaceFrame();
-            final int dw = frame.width();
-            final int dh = frame.height();
-            boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
-                    || dh != mLastSurfaceHeight;
-
-            boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation
-                    || forceRedraw;
-            if (!redrawNeeded && !mOffsetsChanged) {
-                if (DEBUG) {
-                    Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
-                            + "and offsets have not changed.");
-                }
-                return;
-            }
-            mLastRotation = newRotation;
-
-            // Load bitmap if its null and we're not using hardware acceleration.
-            if ((mIsHardwareAccelerated && !mIsOpenGlTextureLoaded) // Using OpenGL but texture not loaded
-                    || (!mIsHardwareAccelerated && mBackground == null)) { // Draw with Canvas but no bitmap
-                if (DEBUG) {
-                    Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = "
-                            + mBackground + ", " + ((mBackground == null) ? 0 : mBackground.getWidth()) + ", "
-                            + ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " + dw + ", " + dh);
-                }
-                loadWallpaper(true /* needDraw */);
-                if (DEBUG) {
-                    Log.d(TAG, "Reloading, resuming draw later");
-                }
-                return;
-            }
-
-            // Center the scaled image
-            mScale = Math.max(1f, Math.max(dw / (float) mBackgroundWidth,
-                    dh / (float) mBackgroundHeight));
-            final int availw = dw - (int) (mBackgroundWidth * mScale);
-            final int availh = dh - (int) (mBackgroundHeight * mScale);
-            int xPixels = availw / 2;
-            int yPixels = availh / 2;
-
-            // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
-            // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
-            // will remain unchanged
-            final int availwUnscaled = dw - mBackgroundWidth;
-            final int availhUnscaled = dh - mBackgroundHeight;
-            if (availwUnscaled < 0) {
-                xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
-            }
-            if (availhUnscaled < 0) {
-                yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
-            }
-
-            mOffsetsChanged = false;
-            if (surfaceDimensionsChanged) {
-                mLastSurfaceWidth = dw;
-                mLastSurfaceHeight = dh;
-            }
-            if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
-                if (DEBUG) {
-                    Log.d(TAG, "Suppressed drawFrame since the image has not "
-                            + "actually moved an integral number of pixels.");
-                }
-                return;
-            }
-            mLastXTranslation = xPixels;
-            mLastYTranslation = yPixels;
-
-            if (DEBUG) {
-                Log.d(TAG, "Redrawing wallpaper");
-            }
-
-            if (mIsHardwareAccelerated) {
-                if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
-                    drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
-                } else {
-                    // If OpenGL drawing was successful, then we can safely discard a reference to the
-                    // wallpaper bitmap to save memory (since a copy has already been loaded into an OpenGL
-                    // texture).
-                    mBackground = null;
-                }
-            } else {
-                drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
-            }
-        }
-
-        /**
-         * Loads the wallpaper on background thread and schedules updating the surface frame,
-         * and if {@param needsDraw} is set also draws a frame.
-         * <p>
-         * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
-         * the active request).
-         * <p>
-         * If {@param needsReset} is set also clears the cache in WallpaperManager first.
-         */
-        private void loadWallpaper(boolean needsDraw) {
-            mNeedsDrawAfterLoadingWallpaper |= needsDraw;
-            if (mLoader != null) {
-                if (DEBUG) {
-                    Log.d(TAG, "Skipping loadWallpaper, already in flight ");
-                }
-                return;
-            }
-            mLoader = new AsyncTask<Void, Void, Bitmap>() {
-                @Override
-                protected Bitmap doInBackground(Void... params) {
-                    Throwable exception = null;
-                    try {
-                        // Decode bitmap of rotating image wallpaper.
-                        String wallpaperFilePath = isPreview()
-                                ? PREVIEW_WALLPAPER_FILE_PATH : ROTATING_WALLPAPER_FILE_PATH;
-                        Context context = isPreview() ? getApplicationContext()
-                                : getApplicationContext().createDeviceProtectedStorageContext();
-                        FileInputStream fileInputStream = context.openFileInput(wallpaperFilePath);
-                        Bitmap bitmap = BitmapFactory.decodeStream(fileInputStream);
-                        fileInputStream.close();
-                        return bitmap;
-                    } catch (RuntimeException | FileNotFoundException | OutOfMemoryError e) {
-                        Log.i(TAG, "couldn't decode stream: ", e);
-                        exception = e;
-                    } catch (IOException e) {
-                        Log.i(TAG, "couldn't close stream: ", e);
-                        exception = e;
-                    }
-
-                    if (isCancelled()) {
-                        return null;
-                    }
-
-                    if (exception != null) {
-                        // Note that if we do fail at this, and the default wallpaper can't
-                        // be loaded, we will go into a cycle.  Don't do a build where the
-                        // default wallpaper can't be loaded.
-                        Log.w(TAG, "Unable to load wallpaper!", exception);
-                        try {
-                            return ((BitmapDrawable) getFallbackDrawable()).getBitmap();
-                        } catch (OutOfMemoryError ex) {
-                            // now we're really screwed.
-                            Log.w(TAG, "Unable reset to default wallpaper!", ex);
-                        }
-
-                        if (isCancelled()) {
-                            return null;
-                        }
-                    }
-                    return null;
-                }
-
-                @Override
-                protected void onPostExecute(Bitmap b) {
-                    mBackground = null;
-                    mBackgroundWidth = -1;
-                    mBackgroundHeight = -1;
-
-                    if (b != null) {
-                        mBackground = b;
-                        mBackgroundWidth = mBackground.getWidth();
-                        mBackgroundHeight = mBackground.getHeight();
-
-                        if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) {
-                            mCachedWallpaperColors = WallpaperColors.fromBitmap(mBackground);
-                            notifyColorsChanged();
-                        }
-                    }
-
-                    if (DEBUG) {
-                        Log.d(TAG, "Wallpaper loaded: " + mBackground);
-                    }
-                    updateSurfaceSize(getSurfaceHolder(), mDefaultDisplay,
-                            false /* forDraw */);
-                    if (mTexture != 0 && mEgl != null) {
-                        deleteTexture(mTexture);
-                    }
-                    // If background is absent (due to an error decoding the bitmap) then don't try to load
-                    // a texture.
-                    if (mEgl != null && mBackground != null) {
-                        mTexture = loadTexture(mBackground);
-                    }
-                    if (mNeedsDrawAfterLoadingWallpaper) {
-                        drawFrame(true /* forceRedraw */);
-                    }
-
-                    mLoader = null;
-                    mNeedsDrawAfterLoadingWallpaper = false;
-                }
-            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-        }
-
-        private Drawable getFallbackDrawable() {
-            Drawable drawable;
-            try {
-                drawable = mWallpaperManager.getDrawable();
-            } catch (java.lang.Exception e) {
-                // Work around Samsung bug where SecurityException is thrown if device is still using its
-                // default wallpaper, and around Android 7.0 bug where SELinux issues can cause a perfectly
-                // valid access of the current wallpaper to cause a failed Binder transaction manifest here
-                // as a RuntimeException.
-                drawable = mWallpaperManager.getBuiltInDrawable();
-            }
-            return drawable;
-        }
-
-        @Override
-        protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
-            super.dump(prefix, fd, out, args);
-
-            out.print(prefix);
-            out.println("ImageWallpaper.DrawableEngine:");
-            out.print(prefix);
-            out.print(" mBackground=");
-            out.print(mBackground);
-            out.print(" mBackgroundWidth=");
-            out.print(mBackgroundWidth);
-            out.print(" mBackgroundHeight=");
-            out.println(mBackgroundHeight);
-
-            out.print(prefix);
-            out.print(" mLastRotation=");
-            out.print(mLastRotation);
-            out.print(" mLastSurfaceWidth=");
-            out.print(mLastSurfaceWidth);
-            out.print(" mLastSurfaceHeight=");
-            out.println(mLastSurfaceHeight);
-
-            out.print(prefix);
-            out.print(" mXOffset=");
-            out.print(mXOffset);
-            out.print(" mYOffset=");
-            out.println(mYOffset);
-
-            out.print(prefix);
-            out.print(" mVisible=");
-            out.print(mVisible);
-            out.print(" mOffsetsChanged=");
-            out.println(mOffsetsChanged);
-
-            out.print(prefix);
-            out.print(" mLastXTranslation=");
-            out.print(mLastXTranslation);
-            out.print(" mLastYTranslation=");
-            out.print(mLastYTranslation);
-            out.print(" mScale=");
-            out.println(mScale);
-
-            out.print(prefix);
-            out.print(" mLastRequestedWidth=");
-            out.print(mLastRequestedWidth);
-            out.print(" mLastRequestedHeight=");
-            out.println(mLastRequestedHeight);
-
-            out.print(prefix);
-            out.println(" DisplayInfo at last updateSurfaceSize:");
-            out.print(prefix);
-            out.print("  rotation=");
-            out.print(mRotationAtLastSurfaceSizeUpdate);
-            out.print("  width=");
-            out.print(mDisplayWidthAtLastSurfaceSizeUpdate);
-            out.print("  height=");
-            out.println(mDisplayHeightAtLastSurfaceSizeUpdate);
-        }
-
-        private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
-            Canvas c = sh.lockCanvas();
-            if (c != null) {
-                try {
-                    if (DEBUG) {
-                        Log.d(TAG, "Redrawing: left=" + left + ", top=" + top);
-                    }
-
-                    final float right = left + mBackgroundWidth * mScale;
-                    final float bottom = top + mBackgroundHeight * mScale;
-                    if (w < 0 || h < 0) {
-                        c.save();
-                        c.clipOutRect(left, top, right, bottom);
-                        c.drawColor(0xff000000);
-                        c.restore();
-                    }
-                    if (mBackground != null) {
-                        RectF dest = new RectF(left, top, right, bottom);
-                        // add a filter bitmap?
-                        c.drawBitmap(mBackground, null, dest, null);
-                    }
-                } finally {
-                    sh.unlockCanvasAndPost(c);
-                }
-            }
-        }
-
-        private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
-
-            mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
-
-            final float right = left + mBackgroundWidth * mScale;
-            final float bottom = top + mBackgroundHeight * mScale;
-
-            final Rect frame = sh.getSurfaceFrame();
-            final Matrix4f ortho = new Matrix4f();
-            ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
-
-            final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
-
-            final int attribPosition = GLES20.glGetAttribLocation(mProgram, "position");
-            final int attribTexCoords = GLES20.glGetAttribLocation(mProgram, "texCoords");
-            final int uniformTexture = GLES20.glGetUniformLocation(mProgram, "texture");
-            final int uniformProjection = GLES20.glGetUniformLocation(mProgram, "projection");
-
-            checkGlError();
-
-            GLES20.glViewport(0, 0, frame.width(), frame.height());
-            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture);
-
-            GLES20.glUseProgram(mProgram);
-            GLES20.glEnableVertexAttribArray(attribPosition);
-            GLES20.glEnableVertexAttribArray(attribTexCoords);
-            GLES20.glUniform1i(uniformTexture, 0);
-            GLES20.glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
-
-            checkGlError();
-
-            if (w > 0 || h > 0) {
-                GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-            }
-
-            // drawQuad
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-            GLES20.glVertexAttribPointer(attribPosition, 3, GLES20.GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-            GLES20.glVertexAttribPointer(attribTexCoords, 3, GLES20.GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
-            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-
-            boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
-            checkEglError();
-
-            return status;
-        }
-
-        private FloatBuffer createMesh(int left, int top, float right, float bottom) {
-            final float[] verticesData = {
-                    // X, Y, Z, U, V
-                    left, bottom, 0.0f, 0.0f, 1.0f,
-                    right, bottom, 0.0f, 1.0f, 1.0f,
-                    left, top, 0.0f, 0.0f, 0.0f,
-                    right, top, 0.0f, 1.0f, 0.0f,
-            };
-
-            final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
-            final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
-                    ByteOrder.nativeOrder()).asFloatBuffer();
-            triangleVertices.put(verticesData).position(0);
-            return triangleVertices;
-        }
-
-        private int loadTexture(Bitmap bitmap) {
-            mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
-
-            int[] textures = new int[1];
-
-            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
-            GLES20.glGenTextures(1, textures, 0);
-            checkGlError();
-
-            int texture = textures[0];
-            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
-            checkGlError();
-
-            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
-            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
-
-            GLES20.glTexParameteri(
-                    GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
-            GLES20.glTexParameteri(
-                    GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
-
-            GLUtils.texImage2D(
-                    GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap, GLES20.GL_UNSIGNED_BYTE, 0);
-            checkGlError();
-
-            mIsOpenGlTextureLoaded = true;
-
-            return texture;
-        }
-
-        private int buildProgram(String vertex, String fragment) {
-            int vertexShader = buildShader(vertex, GLES20.GL_VERTEX_SHADER);
-            if (vertexShader == 0) {
-                return 0;
-            }
-
-            int fragmentShader = buildShader(fragment, GLES20.GL_FRAGMENT_SHADER);
-            if (fragmentShader == 0) {
-                return 0;
-            }
-
-            int program = GLES20.glCreateProgram();
-            GLES20.glAttachShader(program, vertexShader);
-            GLES20.glAttachShader(program, fragmentShader);
-            GLES20.glLinkProgram(program);
-            checkGlError();
-
-            GLES20.glDeleteShader(vertexShader);
-            GLES20.glDeleteShader(fragmentShader);
-
-            int[] status = new int[1];
-            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
-            if (status[0] != GLES20.GL_TRUE) {
-                String error = GLES20.glGetProgramInfoLog(program);
-                Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
-                GLES20.glDeleteProgram(program);
-                return 0;
-            }
-
-            return program;
-        }
-
-        private int buildShader(String source, int type) {
-            int shader = GLES20.glCreateShader(type);
-
-            GLES20.glShaderSource(shader, source);
-            checkGlError();
-
-            GLES20.glCompileShader(shader);
-            checkGlError();
-
-            int[] status = new int[1];
-            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);
-            if (status[0] != GLES20.GL_TRUE) {
-                String error = GLES20.glGetShaderInfoLog(shader);
-                Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
-                GLES20.glDeleteShader(shader);
-                return 0;
-            }
-
-            return shader;
-        }
-
-        private void checkEglError() {
-            int error = mEgl.eglGetError();
-            if (error != EGL10.EGL_SUCCESS) {
-                Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
-            }
-        }
-
-        private void checkGlError() {
-            int error = GLES20.glGetError();
-            if (error != GLES20.GL_NO_ERROR) {
-                Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
-            }
-        }
-
-        private void deleteTexture(int texture) {
-            int[] textures = new int[1];
-            textures[0] = texture;
-
-            mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
-            GLES20.glDeleteTextures(1, textures, 0);
-            mTexture = 0;
-        }
-
-        private void finishGL(int texture, int program) {
-            if (mEgl == null) {
-                return;
-            }
-
-            mOpenGlContextCounter--;
-
-            mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
-            deleteTexture(mTexture);
-            GLES20.glDeleteProgram(program);
-            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
-            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-            if (mOpenGlContextCounter == 0) {
-                mEgl.eglTerminate(mEglDisplay);
-            }
-
-            mEgl = null;
-        }
-
-        private boolean initGL(SurfaceHolder surfaceHolder) {
-            mEgl = (EGL10) EGLContext.getEGL();
-
-            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
-                throw new RuntimeException("eglGetDisplay failed "
-                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            int[] version = new int[2];
-            if (!mEgl.eglInitialize(mEglDisplay, version)) {
-                throw new RuntimeException("eglInitialize failed "
-                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            mOpenGlContextCounter++;
-
-            mEglConfig = chooseEglConfig();
-            if (mEglConfig == null) {
-                throw new RuntimeException("eglConfig not initialized");
-            }
-
-            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
-            if (mEglContext == EGL_NO_CONTEXT) {
-                throw new RuntimeException("createContext failed "
-                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            int attribs[] = {
-                    EGL10.EGL_WIDTH, 1,
-                    EGL10.EGL_HEIGHT, 1,
-                    EGL10.EGL_NONE
-            };
-            EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
-            mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
-
-            int[] maxSize = new int[1];
-            Rect frame = surfaceHolder.getSurfaceFrame();
-            GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);
-
-            mEgl.eglMakeCurrent(
-                    mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
-
-            if (frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
-                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-                mEgl.eglTerminate(mEglDisplay);
-                Log.e(GL_LOG_TAG, "requested  texture size " + frame.width() + "x" + frame.height()
-                        + " exceeds the support maximum of " + maxSize[0] + "x" + maxSize[0]);
-                return false;
-            }
-
-            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
-            if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
-                int error = mEgl.eglGetError();
-                if (error == EGL10.EGL_BAD_NATIVE_WINDOW || error == EGL10.EGL_BAD_ALLOC) {
-                    Log.e(GL_LOG_TAG, "createWindowSurface returned " + GLUtils.getEGLErrorString(error)
-                            + ".");
-                    return false;
-                }
-                throw new RuntimeException("createWindowSurface failed "
-                        + GLUtils.getEGLErrorString(error));
-            }
-
-            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-                throw new RuntimeException("eglMakeCurrent failed "
-                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            mProgram = buildProgram(S_SIMPLE_VS, S_SIMPLE_FS);
-            if (mBackground != null) {
-                mTexture = loadTexture(mBackground);
-            }
-
-            return true;
-        }
-
-
-        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
-            int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
-            return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attribList);
-        }
-
-        private EGLConfig chooseEglConfig() {
-            int[] configsCount = new int[1];
-            EGLConfig[] configs = new EGLConfig[1];
-            int[] configSpec = getConfig();
-            if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
-                throw new IllegalArgumentException("eglChooseConfig failed "
-                        + GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            } else if (configsCount[0] > 0) {
-                return configs[0];
-            }
-            return null;
-        }
-
-        private int[] getConfig() {
-            return new int[]{
-                    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-                    EGL10.EGL_RED_SIZE, 8,
-                    EGL10.EGL_GREEN_SIZE, 8,
-                    EGL10.EGL_BLUE_SIZE, 8,
-                    EGL10.EGL_ALPHA_SIZE, 0,
-                    EGL10.EGL_DEPTH_SIZE, 0,
-                    EGL10.EGL_STENCIL_SIZE, 0,
-                    EGL10.EGL_CONFIG_CAVEAT, EGL10.EGL_NONE,
-                    EGL10.EGL_NONE
-            };
-        }
-    }
-}
diff --git a/src/com/android/wallpaper/module/RotatingWallpaperComponentChecker.java b/src/com/android/wallpaper/module/RotatingWallpaperComponentChecker.java
deleted file mode 100755
index 8a2908a..0000000
--- a/src/com/android/wallpaper/module/RotatingWallpaperComponentChecker.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2017 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.module;
-
-import android.content.Context;
-
-import androidx.annotation.IntDef;
-
-/**
- * Checks what component is responsible for presenting the rotating wallpaper image under the
- * device's launcher or keyguard for current and future rotations.
- */
-public interface RotatingWallpaperComponentChecker {
-
-    int ROTATING_WALLPAPER_COMPONENT_LIVE = 1;
-    int ROTATING_WALLPAPER_COMPONENT_STATIC = 2;
-
-    int ROTATING_WALLPAPER_SUPPORT_NOT_SUPPORTED = 0;
-    int ROTATING_WALLPAPER_SUPPORT_SUPPORTED = 1;
-
-    /**
-     * Returns the rotating wallpaper component for the current wallpaper rotation.
-     */
-    @RotatingWallpaperComponent
-    int getCurrentRotatingWallpaperComponent(Context context);
-
-    /**
-     * Returns the rotating wallpaper component for future wallpaper rotations.
-     */
-    @RotatingWallpaperComponent
-    int getNextRotatingWallpaperComponent(Context context);
-
-    /**
-     * Returns the support level for rotating wallpaper.
-     */
-    @RotatingWallpaperSupport
-    int getRotatingWallpaperSupport(Context context);
-
-    /**
-     * Possible components for presenting the rotating wallpaper image.
-     */
-    @IntDef({
-            ROTATING_WALLPAPER_COMPONENT_LIVE,
-            ROTATING_WALLPAPER_COMPONENT_STATIC})
-    @interface RotatingWallpaperComponent {
-    }
-
-    /**
-     * Possible support levels for rotating wallpaper.
-     */
-    @IntDef({
-            ROTATING_WALLPAPER_SUPPORT_NOT_SUPPORTED,
-            ROTATING_WALLPAPER_SUPPORT_SUPPORTED})
-    @interface RotatingWallpaperSupport {
-    }
-}
diff --git a/src/com/android/wallpaper/module/RotationWallpaperMoveReceiver.java b/src/com/android/wallpaper/module/RotationWallpaperMoveReceiver.java
deleted file mode 100644
index 975b0db..0000000
--- a/src/com/android/wallpaper/module/RotationWallpaperMoveReceiver.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.android.wallpaper.module;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.wallpaper.compat.BuildCompat;
-import com.android.wallpaper.util.DiskBasedLogger;
-import com.android.wallpaper.util.FileMover;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Receiver to run when the app was updated or on first boot to migrate previously existing rotating
- * wallpaper file to device protected storage so it can be read in direct-boot.
- * This is basically a no-op if there's no file in
- * {@link NoBackupImageWallpaper#ROTATING_WALLPAPER_FILE_PATH}.
- */
-public class RotationWallpaperMoveReceiver extends BroadcastReceiver {
-
-    private static final String TAG = "RotationWallpaperMoveReceiver";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        // This receiver is a no-op on pre-N Android and should only respond to a
-        // MY_PACKAGE_REPLACED intent.
-        if (intent.getAction() == null
-                || !(intent.getAction().equals(Intent.ACTION_MY_PACKAGE_REPLACED)
-                    || intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED))
-                || !BuildCompat.isAtLeastN()) {
-            DiskBasedLogger.e(
-                    TAG,
-                    "Unexpected action or Android version!",
-                    context);
-            throw new IllegalStateException(
-                    "Unexpected broadcast action or unsupported Android version");
-        }
-
-        // This is a no-op if there is no rotating wallpaper file in the files directory.
-        if (!context.getFileStreamPath(
-                NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH).exists()) {
-            return;
-        }
-
-        PendingResult broadcastResult = goAsync();
-        new Thread(() -> {
-            Context appContext = context.getApplicationContext();
-            Context deviceProtectedContext = appContext.createDeviceProtectedStorageContext();
-            try {
-                File movedFile = FileMover.moveFileBetweenContexts(appContext,
-                        NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH,
-                        deviceProtectedContext,
-                        NoBackupImageWallpaper.ROTATING_WALLPAPER_FILE_PATH);
-
-                if (movedFile != null) {
-                    // Notify NoBackupImageWallpaper of the change in case that's the currently
-                    // set wallpaper
-                    Intent intent1 = new Intent(appContext.getPackageName()
-                            + NoBackupImageWallpaper.ACTION_ROTATING_WALLPAPER_CHANGED);
-                    // Handled by a runtime-registered receiver in NoBackupImageWallpaper.
-                    intent1.setPackage(appContext.getPackageName());
-                    appContext.sendBroadcast(intent1);
-                }
-            } catch (IOException e) {
-                DiskBasedLogger.e(
-                        TAG,
-                        "Failed to move rotating wallpaper file to device protected storage: "
-                                + e.getMessage(),
-                        appContext);
-            } finally {
-                broadcastResult.finish();
-            }
-        }).start();
-
-    }
-}
diff --git a/src/com/android/wallpaper/module/RotationWallpaperUpdateReceiver.java b/src/com/android/wallpaper/module/RotationWallpaperUpdateReceiver.java
new file mode 100644
index 0000000..4fbc20d
--- /dev/null
+++ b/src/com/android/wallpaper/module/RotationWallpaperUpdateReceiver.java
@@ -0,0 +1,111 @@
+/*
+ * 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.module;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import com.android.wallpaper.compat.BuildCompat;
+import com.android.wallpaper.util.DiskBasedLogger;
+import com.android.wallpaper.util.FileMover;
+
+import java.io.File;
+
+/**
+ * Receiver to run when the app was updated or on first boot to switch from live rotating wallpaper
+ * to static rotating wallpaper.
+ */
+public class RotationWallpaperUpdateReceiver extends BroadcastReceiver {
+
+
+    // Earlier versions of rotating wallpaper save the current rotation image as a file.
+    // We can infer from the extistance of this file whether or not user had rotating live wallpaper
+    private static final String ROTATING_WALLPAPER_FILE_PATH = "rotating_wallpaper.jpg";
+    private static final String TAG = "RotationWallpaperUpdateReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // This receiver is a no-op on pre-N Android and should only respond to a
+        // MY_PACKAGE_REPLACED intent.
+        if (intent.getAction() == null
+                || !(intent.getAction().equals(Intent.ACTION_MY_PACKAGE_REPLACED)
+                || intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED))
+                || !BuildCompat.isAtLeastN()) {
+            DiskBasedLogger.e(
+                    TAG,
+                    "Unexpected action or Android version!",
+                    context);
+            throw new IllegalStateException(
+                    "Unexpected broadcast action or unsupported Android version");
+        }
+
+        PendingResult broadcastResult = goAsync();
+        new Thread(() -> {
+            Context appContext = context.getApplicationContext();
+            Context deviceProtectedContext = appContext.createDeviceProtectedStorageContext();
+
+            if (context.getFileStreamPath(ROTATING_WALLPAPER_FILE_PATH).exists()) {
+                moveFileToProtectedStorage(appContext, deviceProtectedContext);
+            }
+
+            File wallpaperFile = deviceProtectedContext.getFileStreamPath(
+                    ROTATING_WALLPAPER_FILE_PATH);
+            if (wallpaperFile.exists()) {
+                switchToStaticWallpaper(appContext, wallpaperFile);
+            }
+            broadcastResult.finish();
+        }).start();
+    }
+
+    private void moveFileToProtectedStorage(Context context, Context deviceProtectedContext) {
+        try {
+            FileMover.moveFileBetweenContexts(context, ROTATING_WALLPAPER_FILE_PATH,
+                    deviceProtectedContext, ROTATING_WALLPAPER_FILE_PATH);
+        } catch (Exception ex) {
+            DiskBasedLogger.e(
+                    TAG,
+                    "Failed to move rotating wallpaper file to device protected storage: "
+                            + ex.getMessage(),
+                    context);
+        }
+    }
+
+    private void switchToStaticWallpaper(Context appContext, File wallpaperFile) {
+        try {
+            Injector injector = InjectorProvider.getInjector();
+            WallpaperPreferences wallpaperPreferences = injector.getPreferences(appContext);
+            if (wallpaperPreferences.getWallpaperPresentationMode()
+                    != WallpaperPreferences.PRESENTATION_MODE_ROTATING) {
+                return;
+            }
+            Bitmap bitmap = BitmapFactory.decodeFile(wallpaperFile.getAbsolutePath());
+
+            injector.getWallpaperPersister(appContext).setWallpaperInRotation(bitmap,
+                    wallpaperPreferences.getHomeWallpaperAttributions(),
+                    wallpaperPreferences.getHomeWallpaperActionLabelRes(),
+                    wallpaperPreferences.getHomeWallpaperActionIconRes(),
+                    wallpaperPreferences.getHomeWallpaperActionUrl(),
+                    wallpaperPreferences.getHomeWallpaperCollectionId());
+            wallpaperFile.delete();
+
+        } catch (Exception ex) {
+            DiskBasedLogger.e(TAG, "Unable to set static wallpaper", appContext);
+        }
+    }
+}
diff --git a/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java b/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java
index f63506f..e4c6588 100755
--- a/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java
+++ b/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java
@@ -28,12 +28,7 @@
     public static final String KEY_HOME_WALLPAPER_ACTION_LABEL_RES = "home_wallpaper_action_label";
     public static final String KEY_HOME_WALLPAPER_ACTION_ICON_RES = "home_wallpaper_action_icon";
     public static final String KEY_HOME_WALLPAPER_COLLECTION_ID = "home_wallpaper_collection_id";
-    public static final String KEY_HOME_WALLPAPER_BASE_IMAGE_URL = "home_wallpaper_base_image_url";
     public static final String KEY_HOME_WALLPAPER_HASH_CODE = "home_wallpaper_hash_code";
-    public static final String KEY_HOME_WALLPAPER_MANAGER_ID = "home_wallpaper_id";
-    public static final String KEY_HOME_WALLPAPER_PACKAGE_NAME = "home_wallpaper_package_name";
-    public static final String KEY_HOME_WALLPAPER_REMOTE_ID = "home_wallpaper_remote_id";
-    public static final String KEY_HOME_WALLPAPER_BACKING_FILE = "home_wallpaper_backing_file";
 
     public static final String KEY_LOCK_WALLPAPER_ATTRIB_1 = "lock_wallpaper_attribution_line_1";
     public static final String KEY_LOCK_WALLPAPER_ATTRIB_2 = "lock_wallpaper_attribution_line_2";
@@ -43,24 +38,35 @@
     public static final String KEY_LOCK_WALLPAPER_ACTION_ICON_RES = "lock_wallpaper_action_icon";
     public static final String KEY_LOCK_WALLPAPER_HASH_CODE = "lock_wallpaper_hash_code";
     public static final String KEY_LOCK_WALLPAPER_COLLECTION_ID = "lock_wallpaper_collection_id";
-    public static final String KEY_LOCK_WALLPAPER_MANAGER_ID = "lock_wallpaper_id";
-    public static final String KEY_LOCK_WALLPAPER_BACKING_FILE = "lock_wallpaper_backing_file";
 
-    public static final String KEY_DAILY_ROTATION_TIMESTAMPS = "daily_rotation_timestamps";
-    public static final String KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP =
-            "daily_wallpaper_enabled_timestamp";
-
-    public static final String KEY_LAST_DAILY_LOG_TIMESTAMP = "last_daily_log_timestamp";
-    public static final String KEY_LAST_APP_ACTIVE_TIMESTAMP = "last_app_active_timestamp";
-    public static final String KEY_LAST_ROTATION_STATUS = "last_rotation_status";
-    public static final String KEY_LAST_ROTATION_STATUS_TIMESTAMP = "last_rotation_status_timestamp";
-    public static final String KEY_LAST_SYNC_TIMESTAMP = "last_sync_timestamp";
-
-    public static final String KEY_PENDING_WALLPAPER_SET_STATUS = "pending_wallpaper_set_status";
-    public static final String KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS =
-            "pending_daily_wallpaper_update_status";
-
-    public static final String KEY_NUM_DAYS_DAILY_ROTATION_FAILED = "num_days_daily_rotation_failed";
-    public static final String KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED =
-            "num_days_daily_rotation_not_attempted";
+    /**
+     * Preferences with these keys should not be backed up
+     */
+    public interface NoBackupKeys {
+        public static final String KEY_HOME_WALLPAPER_BASE_IMAGE_URL =
+                "home_wallpaper_base_image_url";
+        public static final String KEY_HOME_WALLPAPER_MANAGER_ID = "home_wallpaper_id";
+        public static final String KEY_HOME_WALLPAPER_REMOTE_ID = "home_wallpaper_remote_id";
+        public static final String KEY_HOME_WALLPAPER_BACKING_FILE = "home_wallpaper_backing_file";
+        public static final String KEY_LOCK_WALLPAPER_MANAGER_ID = "lock_wallpaper_id";
+        public static final String KEY_LOCK_WALLPAPER_BACKING_FILE = "lock_wallpaper_backing_file";
+        public static final String KEY_DAILY_ROTATION_TIMESTAMPS = "daily_rotation_timestamps";
+        public static final String KEY_DAILY_WALLPAPER_ENABLED_TIMESTAMP =
+                "daily_wallpaper_enabled_timestamp";
+        public static final String KEY_LAST_DAILY_LOG_TIMESTAMP = "last_daily_log_timestamp";
+        public static final String KEY_LAST_APP_ACTIVE_TIMESTAMP = "last_app_active_timestamp";
+        public static final String KEY_LAST_ROTATION_STATUS = "last_rotation_status";
+        public static final String KEY_LAST_ROTATION_STATUS_TIMESTAMP =
+                "last_rotation_status_timestamp";
+        public static final String KEY_LAST_SYNC_TIMESTAMP = "last_sync_timestamp";
+        public static final String KEY_PENDING_WALLPAPER_SET_STATUS =
+                "pending_wallpaper_set_status";
+        public static final String KEY_PENDING_DAILY_WALLPAPER_UPDATE_STATUS =
+                "pending_daily_wallpaper_update_status";
+        public static final String KEY_NUM_DAYS_DAILY_ROTATION_FAILED =
+                "num_days_daily_rotation_failed";
+        public static final String KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED =
+                "num_days_daily_rotation_not_attempted";
+        public static final String KEY_HOME_WALLPAPER_PACKAGE_NAME = "home_wallpaper_package_name";
+    }
 }
diff --git a/src/com/android/wallpaper/module/WallpaperSetter.java b/src/com/android/wallpaper/module/WallpaperSetter.java
index 830d914..a33a5f1 100644
--- a/src/com/android/wallpaper/module/WallpaperSetter.java
+++ b/src/com/android/wallpaper/module/WallpaperSetter.java
@@ -72,8 +72,9 @@
      * @param callback optional callback to be notified when the wallpaper is set.
      */
     public void setCurrentWallpaper(Activity containerActivity, WallpaperInfo wallpaper,
-            Asset wallpaperAsset, @Destination final int destination, float wallpaperScale,
-            @Nullable Rect cropRect, @Nullable SetWallpaperCallback callback) {
+            @Nullable Asset wallpaperAsset, @Destination final int destination,
+            float wallpaperScale, @Nullable Rect cropRect,
+            @Nullable SetWallpaperCallback callback) {
         if (wallpaper instanceof LiveWallpaperInfo) {
             setCurrentLiveWallpaper(containerActivity, (LiveWallpaperInfo) wallpaper, destination,
                     callback);
diff --git a/src/com/android/wallpaper/picker/CategoryFragment.java b/src/com/android/wallpaper/picker/CategoryFragment.java
index 0e3c36d..f460a39 100755
--- a/src/com/android/wallpaper/picker/CategoryFragment.java
+++ b/src/com/android/wallpaper/picker/CategoryFragment.java
@@ -220,6 +220,7 @@
         }
         // Not add existing category to category list
         if (mCategories.indexOf(category) >= 0) {
+            updateCategory(category);
             return;
         }
 
@@ -341,7 +342,8 @@
     }
 
     private int getNumColumns() {
-        return TileSizeCalculator.getNumCategoryColumns(getActivity());
+        Activity activity = getActivity();
+        return activity == null ? 0 : TileSizeCalculator.getNumCategoryColumns(activity);
     }
 
     /**
diff --git a/src/com/android/wallpaper/picker/ImagePreviewFragment.java b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
new file mode 100755
index 0000000..a6babe3
--- /dev/null
+++ b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
@@ -0,0 +1,370 @@
+/*
+ * 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 android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.module.WallpaperPersister.Destination;
+import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
+import com.android.wallpaper.util.ScreenSizeCalculator;
+import com.android.wallpaper.util.WallpaperCropUtils;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.MemoryCategory;
+import com.davemorrissey.labs.subscaleview.ImageSource;
+import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+
+/**
+ * Fragment which displays the UI for previewing an individual static wallpaper and its attribution
+ * information.
+ */
+public class ImagePreviewFragment extends PreviewFragment {
+
+    private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f;
+
+    private SubsamplingScaleImageView mFullResImageView;
+    private Asset mWallpaperAsset;
+    private Point mDefaultCropSurfaceSize;
+    private Point mScreenSize;
+    private Point mRawWallpaperSize; // Native size of wallpaper image.
+    private ImageView mLowResImageView;
+
+    private InfoPageController mInfoPageController;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mWallpaperAsset = mWallpaper.getAsset(requireContext().getApplicationContext());
+    }
+
+    @Override
+    protected int getLayoutResId() {
+        return R.layout.fragment_image_preview;
+    }
+
+
+    protected int getBottomSheetResId() {
+        return R.id.bottom_sheet;
+    }
+
+    @Override
+    protected int getLoadingIndicatorResId() {
+        return R.id.loading_indicator;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+
+        Activity activity = requireActivity();
+
+        mFullResImageView = view.findViewById(R.id.full_res_image);
+
+        mInfoPageController = new InfoPageController(view.findViewById(R.id.page_info),
+                mPreviewMode);
+
+        mLowResImageView = view.findViewById(R.id.low_res_image);
+
+        // Trim some memory from Glide to make room for the full-size image in this fragment.
+        Glide.get(activity).setMemoryCategory(MemoryCategory.LOW);
+
+        mDefaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
+                getResources(), activity.getWindowManager().getDefaultDisplay());
+        mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
+                activity.getWindowManager().getDefaultDisplay());
+
+        // Load a low-res placeholder image if there's a thumbnail available from the asset that can
+        // be shown to the user more quickly than the full-sized image.
+        if (mWallpaperAsset.hasLowResDataSource()) {
+            mWallpaperAsset.loadLowResDrawable(activity, mLowResImageView, Color.BLACK,
+                    new WallpaperPreviewBitmapTransformation(activity.getApplicationContext(),
+                            isRtl()));
+        }
+
+        mWallpaperAsset.decodeRawDimensions(getActivity(), dimensions -> {
+            // Don't continue loading the wallpaper if the Fragment is detached.
+            if (getActivity() == null) {
+                return;
+            }
+
+            // Return early and show a dialog if dimensions are null (signaling a decoding error).
+            if (dimensions == null) {
+                showLoadWallpaperErrorDialog();
+                return;
+            }
+
+            mRawWallpaperSize = dimensions;
+            setUpExploreIntent(ImagePreviewFragment.this::initFullResView);
+        });
+
+        setUpLoadingIndicator();
+
+        return view;
+    }
+
+    @Override
+    protected void setUpBottomSheetView(ViewGroup bottomSheet) {
+        // Nothing needed here.
+    }
+
+    @Override
+    protected boolean isLoaded() {
+        return mFullResImageView != null && mFullResImageView.hasImage();
+    }
+
+    @Override
+    public void onClickOk() {
+        FragmentActivity activity = getActivity();
+        if (activity != null) {
+            activity.finish();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mLoadingProgressBar != null) {
+            mLoadingProgressBar.hide();
+        }
+        mFullResImageView.recycle();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
+        outState.putInt(KEY_BOTTOM_SHEET_STATE, bottomSheetBehavior.getState());
+    }
+
+    @Override
+    protected void setBottomSheetContentAlpha(float alpha) {
+        mInfoPageController.setContentAlpha(alpha);
+    }
+
+    @Override
+    protected CharSequence getExploreButtonLabel(Context context) {
+        return context.getString(mWallpaper.getActionLabelRes(context));
+    }
+
+    /**
+     * Initializes MosaicView by initializing tiling, setting a fallback page bitmap, and
+     * initializing a zoom-scroll observer and click listener.
+     */
+    private void initFullResView() {
+        mFullResImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
+
+        // Set a solid black "page bitmap" so MosaicView draws a black background while waiting
+        // for the image to load or a transparent one if a thumbnail already loaded.
+        Bitmap blackBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+        int color = (mLowResImageView.getDrawable() == null) ? Color.BLACK : Color.TRANSPARENT;
+        blackBitmap.setPixel(0, 0, color);
+        mFullResImageView.setImage(ImageSource.bitmap(blackBitmap));
+
+        // Then set a fallback "page bitmap" to cover the whole MosaicView, which is an actual
+        // (lower res) version of the image to be displayed.
+        Point targetPageBitmapSize = new Point(mRawWallpaperSize);
+        mWallpaperAsset.decodeBitmap(targetPageBitmapSize.x, targetPageBitmapSize.y,
+                pageBitmap -> {
+                    // Check that the activity is still around since the decoding task started.
+                    if (getActivity() == null) {
+                        return;
+                    }
+
+                    // Some of these may be null depending on if the Fragment is paused, stopped,
+                    // or destroyed.
+                    if (mLoadingProgressBar != null) {
+                        mLoadingProgressBar.hide();
+                    }
+                    // The page bitmap may be null if there was a decoding error, so show an
+                    // error dialog.
+                    if (pageBitmap == null) {
+                        showLoadWallpaperErrorDialog();
+                        return;
+                    }
+                    if (mFullResImageView != null) {
+                        // Set page bitmap.
+                        mFullResImageView.setImage(ImageSource.bitmap(pageBitmap));
+
+                        setDefaultWallpaperZoomAndScroll();
+                        crossFadeInMosaicView();
+                    }
+                    getActivity().invalidateOptionsMenu();
+
+                    populateInfoPage(mInfoPageController);
+                });
+    }
+
+    /**
+     * Makes the MosaicView visible with an alpha fade-in animation while fading out the loading
+     * indicator.
+     */
+    private void crossFadeInMosaicView() {
+        long shortAnimationDuration = getResources().getInteger(
+                android.R.integer.config_shortAnimTime);
+
+        mFullResImageView.setAlpha(0f);
+        mFullResImageView.animate()
+                .alpha(1f)
+                .setDuration(shortAnimationDuration)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        // Clear the thumbnail bitmap reference to save memory since it's no longer
+                        // visible.
+                        if (mLowResImageView != null) {
+                            mLowResImageView.setImageBitmap(null);
+                        }
+                    }
+                });
+
+        mLoadingProgressBar.animate()
+                .alpha(0f)
+                .setDuration(shortAnimationDuration)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        if (mLoadingProgressBar != null) {
+                            mLoadingProgressBar.hide();
+                        }
+                    }
+                });
+    }
+
+    /**
+     * Sets the default wallpaper zoom and scroll position based on a "crop surface"
+     * (with extra width to account for parallax) superimposed on the screen. Shows as much of the
+     * wallpaper as possible on the crop surface and align screen to crop surface such that the
+     * default preview matches what would be seen by the user in the left-most home screen.
+     *
+     * <p>This method is called once in the Fragment lifecycle after the wallpaper asset has loaded
+     * and rendered to the layout.
+     */
+    private void setDefaultWallpaperZoomAndScroll() {
+        // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface.
+        float defaultWallpaperZoom =
+                WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mDefaultCropSurfaceSize);
+        float minWallpaperZoom =
+                WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mScreenSize);
+
+        Point screenToCropSurfacePosition = WallpaperCropUtils.calculateCenterPosition(
+                mDefaultCropSurfaceSize, mScreenSize, true /* alignStart */, isRtl());
+        Point zoomedWallpaperSize = new Point(
+                Math.round(mRawWallpaperSize.x * defaultWallpaperZoom),
+                Math.round(mRawWallpaperSize.y * defaultWallpaperZoom));
+        Point cropSurfaceToWallpaperPosition = WallpaperCropUtils.calculateCenterPosition(
+                zoomedWallpaperSize, mDefaultCropSurfaceSize, false /* alignStart */, isRtl());
+
+        // Set min wallpaper zoom and max zoom on MosaicView widget.
+        mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom));
+        mFullResImageView.setMinScale(minWallpaperZoom);
+
+        // Set center to composite positioning between scaled wallpaper and screen.
+        PointF centerPosition = new PointF(
+                mRawWallpaperSize.x / 2f,
+                mRawWallpaperSize.y / 2f);
+        centerPosition.offset(-(screenToCropSurfacePosition.x + cropSurfaceToWallpaperPosition.x),
+                -(screenToCropSurfacePosition.y + cropSurfaceToWallpaperPosition.y));
+
+        mFullResImageView.setScaleAndCenter(minWallpaperZoom, centerPosition);
+    }
+
+    private Rect calculateCropRect() {
+        // Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom).
+        float wallpaperZoom = mFullResImageView.getScale();
+        int scaledWallpaperWidth = (int) (mRawWallpaperSize.x * wallpaperZoom);
+        int scaledWallpaperHeight = (int) (mRawWallpaperSize.y * wallpaperZoom);
+        Rect rect = new Rect();
+        mFullResImageView.visibleFileRect(rect);
+        int scrollX = (int) (rect.left * wallpaperZoom);
+        int scrollY = (int) (rect.top * wallpaperZoom);
+
+        rect.set(0, 0, scaledWallpaperWidth, scaledWallpaperHeight);
+
+        Display defaultDisplay =  requireActivity().getWindowManager().getDefaultDisplay();
+        Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(defaultDisplay);
+        // Crop rect should start off as the visible screen and then include extra width and height
+        // if available within wallpaper at the current zoom.
+        Rect cropRect = new Rect(scrollX, scrollY, scrollX + screenSize.x, scrollY + screenSize.y);
+
+        Point defaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
+                getResources(), defaultDisplay);
+        int extraWidth = defaultCropSurfaceSize.x - screenSize.x;
+        int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - screenSize.y) / 2f);
+
+        // Try to increase size of screenRect to include extra width depending on the layout
+        // direction.
+        if (isRtl()) {
+            cropRect.left = Math.max(cropRect.left - extraWidth, rect.left);
+        } else {
+            cropRect.right = Math.min(cropRect.right + extraWidth, rect.right);
+        }
+
+        // Try to increase the size of the cropRect to to include extra height.
+        int availableExtraHeightTop = cropRect.top - Math.max(
+                rect.top,
+                cropRect.top - extraHeightTopAndBottom);
+        int availableExtraHeightBottom = Math.min(
+                rect.bottom,
+                cropRect.bottom + extraHeightTopAndBottom) - cropRect.bottom;
+
+        int availableExtraHeightTopAndBottom =
+                Math.min(availableExtraHeightTop, availableExtraHeightBottom);
+        cropRect.top -= availableExtraHeightTopAndBottom;
+        cropRect.bottom += availableExtraHeightTopAndBottom;
+
+        return cropRect;
+    }
+
+    @Override
+    protected void setCurrentWallpaper(@Destination int destination) {
+        mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, mWallpaperAsset,
+                destination, mFullResImageView.getScale(), calculateCropRect(),
+                new SetWallpaperCallback() {
+                    @Override
+                    public void onSuccess() {
+                        finishActivityWithResultOk();
+                    }
+
+                    @Override
+                    public void onError(@Nullable Throwable throwable) {
+                        showSetWallpaperErrorDialog(destination);
+                    }
+                });
+    }
+}
diff --git a/src/com/android/wallpaper/picker/LivePreviewFragment.java b/src/com/android/wallpaper/picker/LivePreviewFragment.java
new file mode 100644
index 0000000..d577fa7
--- /dev/null
+++ b/src/com/android/wallpaper/picker/LivePreviewFragment.java
@@ -0,0 +1,597 @@
+/*
+ * 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 android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.WallpaperColors;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+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.service.wallpaper.WallpaperSettingsActivity;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.AnimationUtils;
+
+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.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
+     */
+    protected WallpaperConnection mWallpaperConnection;
+
+    private Intent mWallpaperIntent;
+    private Intent mDeleteIntent;
+    private Intent mSettingsIntent;
+
+    private List<Pair<String, View>> mPages;
+    private ViewPager mViewPager;
+    private TabLayout mTabLayout;
+    private SliceView mSettingsSliceView;
+    private LiveData<Slice> mSettingsLiveData;
+    private View mLoadingScrim;
+    private InfoPageController mInfoPageController;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        android.app.WallpaperInfo info = mWallpaper.getWallpaperComponent();
+        mWallpaperIntent = getWallpaperIntent(info);
+        setUpExploreIntent(null);
+
+        android.app.WallpaperInfo currentWallpaper =
+                WallpaperManager.getInstance(requireContext()).getWallpaperInfo();
+        String deleteAction = getDeleteAction(info, currentWallpaper);
+
+        if (!TextUtils.isEmpty(deleteAction)) {
+            mDeleteIntent = new Intent(deleteAction);
+            mDeleteIntent.setPackage(info.getPackageName());
+            mDeleteIntent.putExtra(EXTRA_LIVE_WALLPAPER_INFO, info);
+        }
+
+        String settingsActivity = getSettingsActivity(info);
+        if (settingsActivity != null) {
+            mSettingsIntent = new Intent();
+            mSettingsIntent.setComponent(new ComponentName(info.getPackageName(),
+                    settingsActivity));
+            mSettingsIntent.putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true);
+            PackageManager pm = requireContext().getPackageManager();
+            ActivityInfo activityInfo = mSettingsIntent.resolveActivityInfo(pm, 0);
+            if (activityInfo == null) {
+                Log.i(TAG, "Couldn't find wallpaper settings activity: " + settingsActivity);
+                mSettingsIntent = null;
+            }
+        }
+    }
+
+    @Nullable
+    protected String getSettingsActivity(WallpaperInfo info) {
+        return info.getSettingsActivity();
+    }
+
+    protected Intent getWallpaperIntent(WallpaperInfo info) {
+        return new Intent(WallpaperService.SERVICE_INTERFACE)
+                .setClassName(info.getPackageName(), info.getServiceName());
+    }
+
+    @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);
+        setUpLoadingIndicator();
+
+        mWallpaperConnection = new WallpaperConnection(mWallpaperIntent, activity,
+                getWallpaperConnectionListener());
+        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);
+    }
+
+    protected WallpaperConnectionListener getWallpaperConnectionListener() {
+        return null;
+    }
+
+    @Override
+    protected boolean isLoaded() {
+        return mWallpaperConnection != null && mWallpaperConnection.isEngineReady();
+    }
+
+    private void initInfoPage() {
+        View pageInfo = InfoPageController.createView(getLayoutInflater());
+        mInfoPageController = new InfoPageController(pageInfo, mPreviewMode);
+        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);
+
+        pageSettings.findViewById(R.id.preview_settings_pane_set_wallpaper_button)
+                .setOnClickListener(this::onSetWallpaperClicked);
+
+        mPages.add(Pair.create(getResources().getString(R.string.tab_customize), pageSettings));
+    }
+
+    @Override
+    protected CharSequence getExploreButtonLabel(Context context) {
+        CharSequence exploreLabel = ((LiveWallpaperInfo) mWallpaper).getActionDescription(context);
+        if (TextUtils.isEmpty(exploreLabel)) {
+            exploreLabel = context.getString(mWallpaper.getActionLabelRes(context));
+        }
+        return exploreLabel;
+    }
+
+    @SuppressLint("NewApi") //Already checking with isAtLeastQ
+    protected 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 int getLoadingIndicatorResId() {
+        return R.id.loading_indicator;
+    }
+
+    @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) {
+        mInfoPageController.setContentAlpha(alpha);
+    }
+
+
+    @Nullable
+    protected String getDeleteAction(android.app.WallpaperInfo wallpaperInfo,
+            @Nullable android.app.WallpaperInfo currentInfo) {
+        ServiceInfo serviceInfo = wallpaperInfo.getServiceInfo();
+        if (!isPackagePreInstalled(serviceInfo.applicationInfo)) {
+            Log.d(TAG, "This wallpaper is not pre-installed: " + serviceInfo.name);
+            return null;
+        }
+
+        ServiceInfo currentService = currentInfo == null ? null : currentInfo.getServiceInfo();
+        // 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);
+        }
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        menu.findItem(R.id.configure).setVisible(mSettingsIntent != null);
+        menu.findItem(R.id.delete_wallpaper).setVisible(mDeleteIntent != null);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        int id = item.getItemId();
+        if (id == R.id.configure) {
+            if (getActivity() != null) {
+                startActivity(mSettingsIntent);
+                return true;
+            }
+        } else if (id == R.id.delete_wallpaper) {
+            showDeleteConfirmDialog();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void showDeleteConfirmDialog() {
+        final AlertDialog alertDialog = new AlertDialog.Builder(
+                new ContextThemeWrapper(getContext(), getDeviceDefaultTheme()))
+                .setMessage(R.string.delete_wallpaper_confirmation)
+                .setPositiveButton(R.string.delete_live_wallpaper,
+                        (dialog, which) -> deleteLiveWallpaper())
+                .setNegativeButton(android.R.string.cancel, null /* listener */)
+                .create();
+        alertDialog.show();
+    }
+
+    private void deleteLiveWallpaper() {
+        if (mDeleteIntent != null) {
+            requireContext().startService(mDeleteIntent);
+            finishActivityWithResultOk();
+        }
+    }
+
+    private boolean isPackagePreInstalled(ApplicationInfo info) {
+        if (info != null && (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Interface to be notified of connect/disconnect events from {@link WallpaperConnection}
+     */
+    public interface WallpaperConnectionListener {
+        /**
+         * Called after the Wallpaper service has been bound.
+         */
+        void onConnected();
+
+        /**
+         * Called after the Wallpaper engine has been terminated and the service has been unbound.
+         */
+        void onDisconnected();
+    }
+
+    protected class WallpaperConnection extends IWallpaperConnection.Stub
+            implements ServiceConnection {
+
+        private final Activity mActivity;
+        private final Intent mIntent;
+        private final WallpaperConnectionListener mListener;
+        private IWallpaperService mService;
+        private IWallpaperEngine mEngine;
+        private boolean mConnected;
+        private boolean mIsVisible;
+        private boolean mIsEngineVisible;
+        private boolean mEngineReady;
+
+        WallpaperConnection(Intent intent, Activity activity,
+                @Nullable WallpaperConnectionListener listener) {
+            mActivity = activity;
+            mIntent = intent;
+            mListener = listener;
+        }
+
+        public boolean connect() {
+            synchronized (this) {
+                if (!mActivity.bindService(mIntent, this,
+                        Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) {
+                    return false;
+                }
+
+                mConnected = true;
+            }
+            if (mListener != null) {
+                mListener.onConnected();
+            }
+            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;
+            }
+            if (mListener != null) {
+                mListener.onDisconnected();
+            }
+        }
+
+        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 IWallpaperEngine getEngine() {
+            return mEngine;
+        }
+
+        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 (mLoadingProgressBar != null) {
+                                mLoadingProgressBar.hide();
+                            }
+                            mLoadingScrim.setVisibility(View.INVISIBLE);
+                            populateInfoPage(mInfoPageController);
+                        });
+            });
+            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/PreviewActivity.java b/src/com/android/wallpaper/picker/PreviewActivity.java
index 457de69..6785afe 100755
--- a/src/com/android/wallpaper/picker/PreviewActivity.java
+++ b/src/com/android/wallpaper/picker/PreviewActivity.java
@@ -18,15 +18,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.view.View;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
 
 import com.android.wallpaper.R;
 import com.android.wallpaper.model.InlinePreviewIntentFactory;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.module.InjectorProvider;
 
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-
 /**
  * Activity that displays a preview of a specific wallpaper and provides the ability to set the
  * wallpaper as the user's current wallpaper.
@@ -46,11 +47,10 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_preview);
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
+        getWindow().getDecorView().setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
 
         FragmentManager fm = getSupportFragmentManager();
         Fragment fragment = fm.findFragmentById(R.id.fragment_container);
@@ -70,6 +70,7 @@
         }
     }
 
+
     /**
      * Implementation that provides an intent to start a PreviewActivity.
      */
diff --git a/src/com/android/wallpaper/picker/PreviewFragment.java b/src/com/android/wallpaper/picker/PreviewFragment.java
index c698c8c..d0d8202 100755
--- a/src/com/android/wallpaper/picker/PreviewFragment.java
+++ b/src/com/android/wallpaper/picker/PreviewFragment.java
@@ -18,77 +18,54 @@
 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
+import android.content.res.ColorStateList;
 import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
+import android.graphics.Insets;
 import android.graphics.drawable.GradientDrawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
-import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
-import android.view.Surface;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.view.Window;
+import android.view.WindowInsets;
 import android.widget.Button;
 import android.widget.CheckBox;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import androidx.annotation.CallSuper;
+import androidx.annotation.IdRes;
 import androidx.annotation.IntDef;
+import androidx.annotation.LayoutRes;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.widget.Toolbar;
 import androidx.core.view.ViewCompat;
+import androidx.core.widget.ContentLoadingProgressBar;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 
 import com.android.wallpaper.R;
-import com.android.wallpaper.asset.Asset;
-import com.android.wallpaper.asset.Asset.BitmapReceiver;
-import com.android.wallpaper.asset.Asset.DimensionsReceiver;
-import com.android.wallpaper.compat.BuildCompat;
 import com.android.wallpaper.model.LiveWallpaperInfo;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.module.ExploreIntentChecker;
 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.WallpaperPersister.SetWallpaperCallback;
 import com.android.wallpaper.module.WallpaperPreferences;
 import com.android.wallpaper.module.WallpaperSetter;
-import com.android.wallpaper.util.ScreenSizeCalculator;
-import com.android.wallpaper.util.WallpaperCropUtils;
-import com.android.wallpaper.widget.MaterialProgressDrawable;
 
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.MemoryCategory;
-import com.davemorrissey.labs.subscaleview.ImageSource;
-import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
 import com.google.android.material.bottomsheet.BottomSheetBehavior;
 import com.google.android.material.bottomsheet.BottomSheetBehavior.State;
 
@@ -96,23 +73,23 @@
 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 class PreviewFragment extends Fragment implements
+public abstract class PreviewFragment extends Fragment implements
         SetWallpaperDialogFragment.Listener, SetWallpaperErrorDialogFragment.Listener,
         LoadWallpaperErrorDialogFragment.Listener {
 
     /**
-     * User can view wallpaper and attributions in full screen, but "Set wallpaper" button is hidden.
+     * User can view wallpaper and attributions in full screen, but "Set wallpaper" button is
+     * hidden.
      */
-    public static final int MODE_VIEW_ONLY = 0;
+    static final int MODE_VIEW_ONLY = 0;
 
     /**
      * User can view wallpaper and attributions in full screen and click "Set wallpaper" to set the
      * wallpaper with pan and crop position to the device.
      */
-    public static final int MODE_CROP_AND_SET_WALLPAPER = 1;
+    static final int MODE_CROP_AND_SET_WALLPAPER = 1;
 
     /**
      * Possible preview modes for the fragment.
@@ -126,52 +103,57 @@
     public static final String ARG_WALLPAPER = "wallpaper";
     public static final String ARG_PREVIEW_MODE = "preview_mode";
     public static final String ARG_TESTING_MODE_ENABLED = "testing_mode_enabled";
+
+    /**
+     * Creates and returns new instance of {@link ImagePreviewFragment} with the provided wallpaper
+     * set as an argument.
+     */
+    public static PreviewFragment newInstance(
+            WallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean testingModeEnabled) {
+
+        boolean isLive = wallpaperInfo instanceof LiveWallpaperInfo;
+
+        Bundle args = new Bundle();
+        args.putParcelable(ARG_WALLPAPER, wallpaperInfo);
+        args.putInt(ARG_PREVIEW_MODE, mode);
+        args.putBoolean(ARG_TESTING_MODE_ENABLED, testingModeEnabled);
+
+        PreviewFragment fragment = isLive ? new LivePreviewFragment() : new ImagePreviewFragment();
+        fragment.setArguments(args);
+        return fragment;
+    }
+
     private static final String TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT =
             "load_wallpaper_error_dialog";
     private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
             "set_wallpaper_error_dialog";
     private static final int UNUSED_REQUEST_CODE = 1;
-    private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f;
     private static final String TAG = "PreviewFragment";
-    private static final float PAGE_BITMAP_MAX_HEAP_RATIO = 0.25f;
-    private static final String KEY_BOTTOM_SHEET_STATE = "key_bottom_sheet_state";
+    static final String KEY_BOTTOM_SHEET_STATE = "key_bottom_sheet_state";
 
     @PreviewMode
-    private int mPreviewMode;
+    protected int mPreviewMode;
 
     /**
      * When true, enables a test mode of operation -- in which certain UI features are disabled to
      * allow for UI tests to run correctly. Works around issue in ProgressDialog currently where the
      * dialog constantly keeps the UI thread alive and blocks a test forever.
      */
-    private boolean mTestingModeEnabled;
+    protected boolean mTestingModeEnabled;
 
-    protected SubsamplingScaleImageView mFullResImageView;
     protected WallpaperInfo mWallpaper;
-    private Asset mWallpaperAsset;
-    private WallpaperSetter mWallpaperSetter;
-    private UserEventLogger mUserEventLogger;
-    private LinearLayout mBottomSheet;
-    private TextView mAttributionTitle;
-    private TextView mAttributionSubtitle1;
-    private TextView mAttributionSubtitle2;
-    private Button mAttributionExploreButton;
-    private int mCurrentScreenOrientation;
-    private Point mDefaultCropSurfaceSize;
-    private Point mScreenSize;
-    private Point mRawWallpaperSize; // Native size of wallpaper image.
-    private ImageView mLoadingIndicator;
-    private MaterialProgressDrawable mProgressDrawable;
-    private ImageView mLowResImageView;
-    private Button mSetWallpaperButton;
-    private View mSpacer;
-    private CheckBox mPreview;
+    protected WallpaperSetter mWallpaperSetter;
+    protected UserEventLogger mUserEventLogger;
+    protected ViewGroup mBottomSheet;
+    protected ContentLoadingProgressBar mLoadingProgressBar;
+
+    protected CheckBox mPreview;
 
     @SuppressWarnings("RestrictTo")
     @State
-    private int mBottomSheetInitialState;
+    protected int mBottomSheetInitialState;
 
-    private Intent mExploreIntent;
+    protected Intent mExploreIntent;
 
     /**
      * Staged error dialog fragments that were unable to be shown when the hosting activity didn't
@@ -180,23 +162,7 @@
     private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
     private LoadWallpaperErrorDialogFragment mStagedLoadWallpaperErrorDialogFragment;
 
-    /**
-     * Creates and returns new instance of {@link PreviewFragment} with the provided wallpaper set as
-     * an argument.
-     */
-    public static PreviewFragment newInstance(
-            WallpaperInfo 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);
-
-        PreviewFragment fragment = new PreviewFragment();
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    private static int getAttrColor(Context context, int attr) {
+    protected static int getAttrColor(Context context, int attr) {
         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
         int colorAccent = ta.getColor(0, 0);
         ta.recycle();
@@ -208,12 +174,12 @@
         super.onCreate(savedInstanceState);
 
         Activity activity = getActivity();
-        Context appContext = activity.getApplicationContext();
+        Context appContext = getContext().getApplicationContext();
         Injector injector = InjectorProvider.getInjector();
 
         mUserEventLogger = injector.getUserEventLogger(appContext);
         mWallpaper = getArguments().getParcelable(ARG_WALLPAPER);
-        mWallpaperAsset = mWallpaper.getAsset(appContext);
+
         //noinspection ResourceType
         mPreviewMode = getArguments().getInt(ARG_PREVIEW_MODE);
         mTestingModeEnabled = getArguments().getBoolean(ARG_TESTING_MODE_ENABLED);
@@ -222,45 +188,27 @@
 
         setHasOptionsMenu(true);
 
-        // Allow the layout to draw fullscreen even behind the status bar, so we can set as the status
-        // bar color a color that has a custom translucency in the theme.
-        Window window = activity.getWindow();
-        window.getDecorView().setSystemUiVisibility(
-                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-
-        List<String> attributions = mWallpaper.getAttributions(activity);
+        List<String> attributions = getAttributions(activity);
         if (attributions.size() > 0 && attributions.get(0) != null) {
             activity.setTitle(attributions.get(0));
         }
     }
 
     @Override
+    @CallSuper
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
-        View view = inflater.inflate(R.layout.fragment_preview, container, false);
+        View view = inflater.inflate(getLayoutResId(), container, false);
 
         // Set toolbar as the action bar.
         Toolbar toolbar = view.findViewById(R.id.toolbar);
-        AppCompatActivity activity = (AppCompatActivity) getActivity();
+        AppCompatActivity activity = (AppCompatActivity) requireActivity();
         activity.setSupportActionBar(toolbar);
         activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
         activity.getSupportActionBar().setDisplayShowTitleEnabled(false);
 
-        // Use updated fancy arrow icon for O+.
-        if (BuildCompat.isAtLeastO()) {
-            Drawable navigationIcon = getResources().getDrawable(
-                    R.drawable.material_ic_arrow_back_black_24);
-
-            // This Drawable's state is shared across the app, so make a copy of it before applying a
-            // color tint as not to affect other clients elsewhere in the app.
-            navigationIcon = navigationIcon.getConstantState().newDrawable().mutate();
-            navigationIcon.setColorFilter(
-                    getResources().getColor(R.color.material_white_100), Mode.SRC_IN);
-            navigationIcon.setAutoMirrored(true);
-            toolbar.setNavigationIcon(navigationIcon);
-        }
+        toolbar.getNavigationIcon().setTint(getAttrColor(activity, android.R.attr.colorPrimary));
+        toolbar.getNavigationIcon().setAutoMirrored(true);
 
         ViewCompat.setPaddingRelative(toolbar,
         /* start */ getResources().getDimensionPixelSize(
@@ -270,18 +218,11 @@
                         R.dimen.preview_toolbar_set_wallpaper_button_end_padding),
         /* bottom */ 0);
 
-        mFullResImageView = view.findViewById(R.id.full_res_image);
-        mLoadingIndicator = view.findViewById(R.id.loading_indicator);
+        mLoadingProgressBar = view.findViewById(getLoadingIndicatorResId());
+        mLoadingProgressBar.show();
 
-        mBottomSheet = view.findViewById(R.id.bottom_sheet);
-        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);
-        mAttributionExploreButton = view.findViewById(
-                R.id.preview_attribution_pane_explore_button);
-        mLowResImageView = view.findViewById(R.id.low_res_image);
-        mSetWallpaperButton = view.findViewById(R.id.preview_attribution_pane_set_wallpaper_button);
-        mSpacer = view.findViewById(R.id.spacer);
+        mBottomSheet = view.findViewById(getBottomSheetResId());
+        setUpBottomSheetView(mBottomSheet);
 
         // Workaround as we don't have access to bottomDialogCornerRadius, mBottomSheet radii are
         // set to dialogCornerRadius by default.
@@ -294,93 +235,78 @@
         bottomSheetBackground.setCornerRadii(radii);
         mBottomSheet.setBackground(bottomSheetBackground);
 
-        // Trim some memory from Glide to make room for the full-size image in this fragment.
-        Glide.get(getActivity()).setMemoryCategory(MemoryCategory.LOW);
-
-        mDefaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
-                getResources(), getActivity().getWindowManager().getDefaultDisplay());
-        mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
-                getActivity().getWindowManager().getDefaultDisplay());
-
-        // Load a low-res placeholder image if there's a thumbnail available from the asset that can be
-        // shown to the user more quickly than the full-sized image.
-        if (mWallpaperAsset.hasLowResDataSource()) {
-            mWallpaperAsset.loadLowResDrawable(getActivity(), mLowResImageView, Color.BLACK,
-                    new WallpaperPreviewBitmapTransformation(getActivity().getApplicationContext(),
-                            isRtl()));
-        }
-
-        mWallpaperAsset.decodeRawDimensions(getActivity(), new DimensionsReceiver() {
-            @Override
-            public void onDimensionsDecoded(Point dimensions) {
-                // Don't continue loading the wallpaper if the Fragment is detached.
-                Activity activity = getActivity();
-                if (activity == null) {
-                    return;
-                }
-
-                // Return early and show a dialog if dimensions are null (signaling a decoding error).
-                if (dimensions == null) {
-                    showLoadWallpaperErrorDialog();
-                    return;
-                }
-
-                mRawWallpaperSize = dimensions;
-                ExploreIntentChecker intentChecker =
-                        InjectorProvider.getInjector().getExploreIntentChecker(activity);
-                String actionUrl = mWallpaper.getActionUrl(activity);
-                if (actionUrl != null && !actionUrl.isEmpty()) {
-                    Uri exploreUri = Uri.parse(mWallpaper.getActionUrl(activity));
-
-                    intentChecker.fetchValidActionViewIntent(exploreUri, exploreIntent -> {
-                        if (getActivity() == null) {
-                            return;
-                        }
-
-                        mExploreIntent = exploreIntent;
-                        initFullResView();
-                    });
-                } else {
-                    initFullResView();
-                }
-            }
-        });
-
-        // Configure loading indicator with a MaterialProgressDrawable.
-        mProgressDrawable = new MaterialProgressDrawable(getActivity().getApplicationContext(),
-                mLoadingIndicator);
-        mProgressDrawable.setAlpha(255);
-        mProgressDrawable.setBackgroundColor(getResources().getColor(R.color.material_white_100,
-                getContext().getTheme()));
-        mProgressDrawable.setColorSchemeColors(getAttrColor(
-                new ContextThemeWrapper(getContext(), getDeviceDefaultTheme()),
-                android.R.attr.colorAccent));
-        mProgressDrawable.updateSizes(MaterialProgressDrawable.LARGE);
-        mLoadingIndicator.setImageDrawable(mProgressDrawable);
-
-        // We don't want to show the spinner every time we load an image if it loads quickly; instead,
-        // only start showing the spinner if loading the image has taken longer than half of a second.
-        mLoadingIndicator.postDelayed(() -> {
-            if (mFullResImageView != null && !mFullResImageView.hasImage()
-                    && !mTestingModeEnabled) {
-                mLoadingIndicator.setVisibility(View.VISIBLE);
-                mLoadingIndicator.setAlpha(1f);
-                if (mProgressDrawable != null) {
-                    mProgressDrawable.start();
-                }
-            }
-        }, 500);
-
-
-        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();
 
+        view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
+            toolbar.setPadding(toolbar.getPaddingLeft(),
+                    toolbar.getPaddingTop() + windowInsets.getSystemWindowInsetTop(),
+                    toolbar.getPaddingRight(), toolbar.getBottom());
+            mBottomSheet.setPadding(mBottomSheet.getPaddingLeft(),
+                    mBottomSheet.getPaddingTop(), mBottomSheet.getPaddingRight(),
+                    mBottomSheet.getPaddingBottom() + windowInsets.getSystemWindowInsetBottom());
+            WindowInsets.Builder builder = new WindowInsets.Builder(windowInsets);
+            builder.setSystemWindowInsets(Insets.of(windowInsets.getSystemWindowInsetLeft(),
+                    0, windowInsets.getStableInsetRight(), 0));
+            return builder.build();
+        });
+
         return view;
     }
 
+    protected void populateInfoPage(InfoPageController infoPage) {
+        Context context = requireContext();
+
+        BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
+
+        List<String> attributions = getAttributions(context);
+        boolean showMetadata = shouldShowMetadataInPreview();
+        CharSequence exploreLabel = getExploreButtonLabel(context);
+
+        infoPage.populate(attributions, showMetadata, this::onSetWallpaperClicked,
+                exploreLabel,
+                (showMetadata && mExploreIntent != null) ? this::onExploreClicked : null);
+
+        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);
+            infoPage.setContentAlpha(1f);
+        } else {
+            setPreviewChecked(true);
+            infoPage.setContentAlpha(0f);
+        }
+
+        bottomSheetBehavior.setState(mBottomSheetInitialState);
+    }
+
+    protected List<String> getAttributions(Context context) {
+        return mWallpaper.getAttributions(context);
+    }
+
+    protected boolean shouldShowMetadataInPreview() {
+        android.app.WallpaperInfo wallpaperComponent = mWallpaper.getWallpaperComponent();
+        return wallpaperComponent == null || wallpaperComponent.getShowMetadataInPreview();
+    }
+
+    @Nullable
+    protected abstract CharSequence getExploreButtonLabel(Context context);
+
+    @LayoutRes
+    protected abstract int getLayoutResId();
+
+    protected abstract void setUpBottomSheetView(ViewGroup bottomSheet);
+
+    @IdRes
+    protected abstract int getBottomSheetResId();
+
+    @IdRes
+    protected abstract int getLoadingIndicatorResId();
+
     protected int getDeviceDefaultTheme() {
         return android.R.style.Theme_DeviceDefault;
     }
@@ -398,12 +324,12 @@
         // allow committing fragment transactions.
         if (mStagedLoadWallpaperErrorDialogFragment != null) {
             mStagedLoadWallpaperErrorDialogFragment.show(
-                    getFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
+                    requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
             mStagedLoadWallpaperErrorDialogFragment = null;
         }
         if (mStagedSetWallpaperErrorDialogFragment != null) {
             mStagedSetWallpaperErrorDialogFragment.show(
-                    getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
+                    requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
             mStagedSetWallpaperErrorDialogFragment = null;
         }
     }
@@ -428,20 +354,20 @@
             // the IndividualPreviewActivity, the "My photos" selection (by way of
             // TopLevelPickerActivity), or from a system "crop and set wallpaper" intent.
             // Therefore, handle the Up button as a global Back.
-            getActivity().onBackPressed();
+            requireActivity().onBackPressed();
             return true;
         }
 
         return false;
     }
 
-    protected void setupPreviewMenu(Menu menu) {
+    private void setupPreviewMenu(Menu menu) {
         mPreview = (CheckBox) menu.findItem(R.id.preview).getActionView();
         mPreview.setChecked(mBottomSheetInitialState == STATE_COLLAPSED);
         mPreview.setOnClickListener(this::setPreviewBehavior);
     }
 
-    private void setPreviewChecked(boolean checked) {
+    protected void setPreviewChecked(boolean checked) {
         if (mPreview != null) {
             mPreview.setChecked(checked);
             int resId = checked ? R.string.expand_attribution_panel
@@ -450,7 +376,7 @@
         }
     }
 
-    private void setPreviewBehavior(final View v) {
+    private void setPreviewBehavior(View v) {
         CheckBox checkbox = (CheckBox) v;
         BottomSheetBehavior<?> behavior = BottomSheetBehavior.from(mBottomSheet);
 
@@ -461,6 +387,46 @@
         }
     }
 
+    protected void setUpExploreIntent(@Nullable Runnable callback) {
+        Context context = getContext();
+        if (context == null) {
+            return;
+        }
+        String actionUrl = mWallpaper.getActionUrl(context);
+        if (actionUrl != null && !actionUrl.isEmpty()) {
+            Uri exploreUri = Uri.parse(mWallpaper.getActionUrl(context));
+            ExploreIntentChecker intentChecker =
+                    InjectorProvider.getInjector().getExploreIntentChecker(context);
+
+            intentChecker.fetchValidActionViewIntent(exploreUri, exploreIntent -> {
+                if (getActivity() == null) {
+                    return;
+                }
+
+                mExploreIntent = exploreIntent;
+                if (callback != null) {
+                    callback.run();
+                }
+            });
+        } else {
+            if (callback != null) {
+                callback.run();
+            }
+        }
+    }
+
+    /**
+     * Configure loading indicator with a MaterialProgressDrawable.
+     */
+    protected void setUpLoadingIndicator() {
+        mLoadingProgressBar.setProgressTintList(ColorStateList.valueOf(getAttrColor(
+                new ContextThemeWrapper(requireContext(), getDeviceDefaultTheme()),
+                android.R.attr.colorAccent)));
+        mLoadingProgressBar.show();
+    }
+
+    protected abstract boolean isLoaded();
+
     @Override
     public void onSet(int destination) {
         setCurrentWallpaper(destination);
@@ -483,10 +449,6 @@
     public void onDestroy() {
         super.onDestroy();
         mWallpaperSetter.cleanUp();
-        if (mProgressDrawable != null) {
-            mProgressDrawable.stop();
-        }
-        mFullResImageView.recycle();
     }
 
     @Override
@@ -497,42 +459,20 @@
         outState.putInt(KEY_BOTTOM_SHEET_STATE, bottomSheetBehavior.getState());
     }
 
-    private void onSetWallpaperClicked(View button) {
-        if (BuildCompat.isAtLeastN()) {
-            mWallpaperSetter.requestDestination(getContext(), getFragmentManager(), this,
-                    mWallpaper instanceof LiveWallpaperInfo);
-        } else {
-            setCurrentWallpaper(WallpaperPersister.DEST_HOME_SCREEN);
+    protected void onSetWallpaperClicked(View button) {
+        mWallpaperSetter.requestDestination(getContext(), getFragmentManager(), this,
+                mWallpaper instanceof LiveWallpaperInfo);
+    }
+
+    private void onExploreClicked(View button) {
+        if (getContext() == null) {
+            return;
         }
-    }
+        Context context = getContext();
+        mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context),
+                mWallpaper.getActionLabelRes(context));
 
-    /**
-     * Returns a zoom level that is similar to the actual zoom, but that is exactly 0.5 ** n for some
-     * integer n. This is useful for downsampling a bitmap--we want to see the bitmap at full detail,
-     * or downsampled to 1 in every 2 pixels, or 1 in 4, and so on, depending on the zoom.
-     */
-    private static float getDownsampleZoom(float actualZoom) {
-        if (actualZoom > 1) {
-            // Very zoomed in, but we can't sample more than 1 pixel per pixel.
-            return 1.0f;
-        }
-        float lower = 1.0f / roundUpToPower2((int) Math.ceil(1 / actualZoom));
-        float upper = lower * 2;
-        return nearestValue(actualZoom, lower, upper);
-    }
-
-    /**
-     * Returns the integer rounded up to the next power of 2.
-     */
-    private static int roundUpToPower2(int value) {
-        return 1 << (32 - Integer.numberOfLeadingZeros(value - 1));
-    }
-
-    /**
-     * Returns the closer of two values a and b to the given value.
-     */
-    private static float nearestValue(float value, float a, float b) {
-        return Math.abs(a - value) < Math.abs(b - value) ? a : b;
+        startActivity(mExploreIntent);
     }
 
     private void setUpBottomSheetListeners() {
@@ -554,6 +494,8 @@
                     case STATE_EXPANDED:
                         setPreviewChecked(false /* checked */);
                         break;
+                    default:
+                        Log.v(TAG, "Ignoring BottomSheet state: " + newState);
                 }
             }
 
@@ -565,264 +507,13 @@
                 } else {
                     alpha = 1f - slideOffset;
                 }
-                mAttributionTitle.setAlpha(alpha);
-                mAttributionSubtitle1.setAlpha(alpha);
-                mAttributionSubtitle2.setAlpha(alpha);
-                mAttributionExploreButton.setAlpha(alpha);
+                setBottomSheetContentAlpha(alpha);
             }
         });
     }
 
-    private boolean isWallpaperLoaded() {
-        return mFullResImageView.hasImage();
-    }
+    protected void setBottomSheetContentAlpha(float alpha) {
 
-    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));
-        }
-
-        if (mPreviewMode == MODE_CROP_AND_SET_WALLPAPER) {
-            mSetWallpaperButton.setVisibility(View.VISIBLE);
-            mSetWallpaperButton.setOnClickListener(this::onSetWallpaperClicked);
-        } else {
-            mSetWallpaperButton.setVisibility(View.GONE);
-        }
-
-        String actionUrl = mWallpaper.getActionUrl(context);
-
-        mAttributionExploreButton.setVisibility(View.GONE);
-        if (actionUrl != null && !actionUrl.isEmpty()) {
-            if (mExploreIntent != null) {
-                mAttributionExploreButton.setVisibility(View.VISIBLE);
-                mAttributionExploreButton.setText(context.getString(
-                        mWallpaper.getActionLabelRes(context)));
-
-                mAttributionExploreButton.setOnClickListener(view -> {
-                    mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context),
-                            mWallpaper.getActionLabelRes(context));
-
-                    startActivity(mExploreIntent);
-                });
-            }
-        }
-
-        if (mAttributionExploreButton.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 (bottomSheetBehavior.getState() == 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);
-        }
-
-        // Let the state change listener take care of animating a state change to the initial state
-        // if there's a state change.
-        bottomSheetBehavior.setState(mBottomSheetInitialState);
-    }
-
-    /**
-     * Initializes MosaicView by initializing tiling, setting a fallback page bitmap, and
-     * initializing a zoom-scroll observer and click listener.
-     */
-    private void initFullResView() {
-        mFullResImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
-
-        // Set a solid black "page bitmap" so MosaicView draws a black background while waiting
-        // for the image to load or a transparent one if a thumbnail already loaded.
-        Bitmap blackBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
-        int color = (mLowResImageView.getDrawable() == null) ? Color.BLACK : Color.TRANSPARENT;
-        blackBitmap.setPixel(0, 0, color);
-        mFullResImageView.setImage(ImageSource.bitmap(blackBitmap));
-
-        // Then set a fallback "page bitmap" to cover the whole MosaicView, which is an actual
-        // (lower res) version of the image to be displayed.
-        Point targetPageBitmapSize = new Point(mRawWallpaperSize);
-        mWallpaperAsset.decodeBitmap(targetPageBitmapSize.x, targetPageBitmapSize.y,
-                new BitmapReceiver() {
-                    @Override
-                    public void onBitmapDecoded(Bitmap pageBitmap) {
-                        // Check that the activity is still around since the decoding task started.
-                        if (getActivity() == null) {
-                            return;
-                        }
-
-                        // Some of these may be null depending on if the Fragment is paused, stopped,
-                        // or destroyed.
-                        if (mLoadingIndicator != null) {
-                            mLoadingIndicator.setVisibility(View.GONE);
-                        }
-                        // The page bitmap may be null if there was a decoding error, so show an
-                        // error dialog.
-                        if (pageBitmap == null) {
-                            showLoadWallpaperErrorDialog();
-                            return;
-                        }
-                        if (mFullResImageView != null) {
-                            // Set page bitmap.
-                            mFullResImageView.setImage(ImageSource.bitmap(pageBitmap));
-
-                            setDefaultWallpaperZoomAndScroll();
-                            crossFadeInMosaicView();
-                        }
-                        if (mProgressDrawable != null) {
-                            mProgressDrawable.stop();
-                        }
-                        getActivity().invalidateOptionsMenu();
-
-                        populateAttributionPane();
-                    }
-                });
-    }
-
-    /**
-     * Makes the MosaicView visible with an alpha fade-in animation while fading out the loading
-     * indicator.
-     */
-    private void crossFadeInMosaicView() {
-        long shortAnimationDuration = getResources().getInteger(
-                android.R.integer.config_shortAnimTime);
-
-        mFullResImageView.setAlpha(0f);
-        mFullResImageView.animate()
-                .alpha(1f)
-                .setDuration(shortAnimationDuration)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        // Clear the thumbnail bitmap reference to save memory since it's no longer
-                        // visible.
-                        if (mLowResImageView != null) {
-                            mLowResImageView.setImageBitmap(null);
-                        }
-                    }
-                });
-
-        mLoadingIndicator.animate()
-                .alpha(0f)
-                .setDuration(shortAnimationDuration)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        if (mLoadingIndicator != null) {
-                            mLoadingIndicator.setVisibility(View.GONE);
-                        }
-                    }
-                });
-    }
-
-    /**
-     * Sets the default wallpaper zoom and scroll position based on a "crop surface"
-     * (with extra width to account for parallax) superimposed on the screen. Shows as much of the
-     * wallpaper as possible on the crop surface and align screen to crop surface such that the
-     * default preview matches what would be seen by the user in the left-most home screen.
-     *
-     * <p>This method is called once in the Fragment lifecycle after the wallpaper asset has loaded
-     * and rendered to the layout.
-     */
-    private void setDefaultWallpaperZoomAndScroll() {
-        // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface.
-        float defaultWallpaperZoom =
-                WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mDefaultCropSurfaceSize);
-        float minWallpaperZoom =
-                WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mScreenSize);
-
-        Point screenToCropSurfacePosition = WallpaperCropUtils.calculateCenterPosition(
-                mDefaultCropSurfaceSize, mScreenSize, true /* alignStart */, isRtl());
-        Point zoomedWallpaperSize = new Point(
-                Math.round(mRawWallpaperSize.x * defaultWallpaperZoom),
-                Math.round(mRawWallpaperSize.y * defaultWallpaperZoom));
-        Point cropSurfaceToWallpaperPosition = WallpaperCropUtils.calculateCenterPosition(
-                zoomedWallpaperSize, mDefaultCropSurfaceSize, false /* alignStart */, isRtl());
-
-        // Set min wallpaper zoom and max zoom on MosaicView widget.
-        mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom));
-        mFullResImageView.setMinScale(minWallpaperZoom);
-
-        // Set center to composite positioning between scaled wallpaper and screen.
-        PointF centerPosition = new PointF(
-                mRawWallpaperSize.x / 2f,
-                mRawWallpaperSize.y / 2f);
-        centerPosition.offset( - (screenToCropSurfacePosition.x + cropSurfaceToWallpaperPosition.x),
-                - (screenToCropSurfacePosition.y + cropSurfaceToWallpaperPosition.y));
-
-        mFullResImageView.setScaleAndCenter(minWallpaperZoom, centerPosition);
-    }
-
-    protected Rect calculateCropRect() {
-        // Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom).
-        float wallpaperZoom = mFullResImageView.getScale();
-        int scaledWallpaperWidth = (int) (mRawWallpaperSize.x * wallpaperZoom);
-        int scaledWallpaperHeight = (int) (mRawWallpaperSize.y * wallpaperZoom);
-        Rect rect = new Rect();
-        mFullResImageView.visibleFileRect(rect);
-        int scrollX = (int) (rect.left * wallpaperZoom);
-        int scrollY = (int) (rect.top * wallpaperZoom);
-
-        rect.set(0, 0, scaledWallpaperWidth, scaledWallpaperHeight);
-        Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
-                getActivity().getWindowManager().getDefaultDisplay());
-        // Crop rect should start off as the visible screen and then include extra width and height
-        // if available within wallpaper at the current zoom.
-        Rect cropRect = new Rect(scrollX, scrollY, scrollX + screenSize.x, scrollY + screenSize.y);
-
-        Point defaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
-                getResources(), getActivity().getWindowManager().getDefaultDisplay());
-        int extraWidth = defaultCropSurfaceSize.x - screenSize.x;
-        int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - screenSize.y) / 2f);
-
-        // Try to increase size of screenRect to include extra width depending on the layout
-        // direction.
-        if (isRtl()) {
-            cropRect.left = Math.max(cropRect.left - extraWidth, rect.left);
-        } else {
-            cropRect.right = Math.min(cropRect.right + extraWidth, rect.right);
-        }
-
-        // Try to increase the size of the cropRect to to include extra height.
-        int availableExtraHeightTop = cropRect.top - Math.max(
-                rect.top,
-                cropRect.top - extraHeightTopAndBottom);
-        int availableExtraHeightBottom = Math.min(
-                rect.bottom,
-                cropRect.bottom + extraHeightTopAndBottom) - cropRect.bottom;
-
-        int availableExtraHeightTopAndBottom =
-                Math.min(availableExtraHeightTop, availableExtraHeightBottom);
-        cropRect.top -= availableExtraHeightTopAndBottom;
-        cropRect.bottom += availableExtraHeightTopAndBottom;
-
-        return cropRect;
     }
 
     /**
@@ -830,35 +521,22 @@
      *
      * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both.
      */
-    private void setCurrentWallpaper(@Destination final int destination) {
-        mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, mWallpaperAsset,
-                destination, mFullResImageView.getScale(), calculateCropRect(),
-                new SetWallpaperCallback() {
-                    @Override
-                    public void onSuccess() {
-                        finishActivityWithResultOk();
-                    }
+    protected abstract void setCurrentWallpaper(@Destination int destination);
 
-                    @Override
-                    public void onError(@Nullable Throwable throwable) {
-                        showSetWallpaperErrorDialog(destination);
-                    }
-                });
-    }
-
-    private void finishActivityWithResultOk() {
+    protected void finishActivityWithResultOk() {
+        Activity activity = requireActivity();
         try {
-            Toast.makeText(
-                    getActivity(), R.string.wallpaper_set_successfully_message, Toast.LENGTH_SHORT).show();
+            Toast.makeText(activity,
+                    R.string.wallpaper_set_successfully_message, Toast.LENGTH_SHORT).show();
         } catch (NotFoundException e) {
             Log.e(TAG, "Could not show toast " + e);
         }
-        getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
-        getActivity().setResult(Activity.RESULT_OK);
-        getActivity().finish();
+        activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
+        activity.setResult(Activity.RESULT_OK);
+        activity.finish();
     }
 
-    private void showSetWallpaperErrorDialog(@Destination int wallpaperDestination) {
+    protected void showSetWallpaperErrorDialog(@Destination int wallpaperDestination) {
         SetWallpaperErrorDialogFragment newFragment = SetWallpaperErrorDialogFragment.newInstance(
                 R.string.set_wallpaper_error_message, wallpaperDestination);
         newFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
@@ -866,9 +544,9 @@
         // Show 'set wallpaper' error dialog now if it's safe to commit fragment transactions,
         // otherwise stage it for later when the hosting activity is in a state to commit fragment
         // transactions.
-        BasePreviewActivity activity = (BasePreviewActivity) getActivity();
+        BasePreviewActivity activity = (BasePreviewActivity) requireActivity();
         if (activity.isSafeToCommitFragmentTransaction()) {
-            newFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
+            newFragment.show(requireFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
         } else {
             mStagedSetWallpaperErrorDialogFragment = newFragment;
         }
@@ -878,85 +556,117 @@
      * Shows 'load wallpaper' error dialog now or stage it to be shown when the hosting activity is
      * in a state that allows committing fragment transactions.
      */
-    private void showLoadWallpaperErrorDialog() {
+    protected void showLoadWallpaperErrorDialog() {
         LoadWallpaperErrorDialogFragment dialogFragment =
                 LoadWallpaperErrorDialogFragment.newInstance();
-        dialogFragment.setTargetFragment(PreviewFragment.this, UNUSED_REQUEST_CODE);
+        dialogFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
 
         // Show 'load wallpaper' error dialog now or stage it to be shown when the hosting
         // activity is in a state that allows committing fragment transactions.
         BasePreviewActivity activity = (BasePreviewActivity) getActivity();
         if (activity != null && activity.isSafeToCommitFragmentTransaction()) {
-            dialogFragment.show(PreviewFragment.this.getFragmentManager(),
-                    TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
+            dialogFragment.show(requireFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
         } else {
             mStagedLoadWallpaperErrorDialogFragment = dialogFragment;
         }
     }
 
-    @IntDef({
-            ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
-            ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
-            ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
-            ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE})
-    private @interface ActivityInfoScreenOrientation {
-    }
-
-    /**
-     * Gets the appropriate ActivityInfo orientation for the current configuration orientation to
-     * enable locking screen rotation at API levels lower than 18.
-     */
-    @ActivityInfoScreenOrientation
-    private int getCompatActivityInfoOrientation() {
-        int configOrientation = getResources().getConfiguration().orientation;
-        final Display display = getActivity().getWindowManager().getDefaultDisplay();
-        int naturalOrientation = Configuration.ORIENTATION_LANDSCAPE;
-        switch (display.getRotation()) {
-            case Surface.ROTATION_0:
-            case Surface.ROTATION_180:
-                // We are currently in the same basic orientation as the natural orientation.
-                naturalOrientation = configOrientation;
-                break;
-            case Surface.ROTATION_90:
-            case Surface.ROTATION_270:
-                // We are currently in the other basic orientation to the natural orientation.
-                naturalOrientation = (configOrientation == Configuration.ORIENTATION_LANDSCAPE)
-                        ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
-                break;
-            default:
-                // continue below
-        }
-
-        // Since the map starts at portrait, we need to offset if this device's natural orientation
-        // is landscape.
-        int indexOffset = 0;
-        if (naturalOrientation == Configuration.ORIENTATION_LANDSCAPE) {
-            indexOffset = 1;
-        }
-
-        switch ((display.getRotation() + indexOffset) % 4) {
-            case 0:
-                return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-            case 1:
-                return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-            case 2:
-                return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
-            case 3:
-                return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
-            default:
-                Log.e(TAG, "Display rotation did not correspond to a valid ActivityInfo orientation"
-                        +  "with display rotation: " + display.getRotation() + " and index offset: "
-                        + indexOffset + ".");
-                return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-        }
-    }
-
     /**
      * Returns whether layout direction is RTL (or false for LTR). Since native RTL layout support
      * was added in API 17, returns false for versions lower than 17.
      */
-    private boolean isRtl() {
+    protected boolean isRtl() {
         return getResources().getConfiguration().getLayoutDirection()
                     == View.LAYOUT_DIRECTION_RTL;
     }
-}
\ No newline at end of file
+
+    protected static class InfoPageController {
+
+        public static View createView(LayoutInflater inflater) {
+            return inflater.inflate(R.layout.preview_page_info, null /* root */);
+        }
+
+        private final int mPreviewMode;
+        private final View mInfoPage;
+        private final TextView mAttributionTitle;
+        private final TextView mAttributionSubtitle1;
+        private final TextView mAttributionSubtitle2;
+        private final Button mExploreButton;
+        private final Button mSetWallpaperButton;
+        private final View mSpacer;
+
+        public InfoPageController(View infoPage, int previewMode) {
+            mInfoPage = infoPage;
+            mPreviewMode = previewMode;
+
+            mAttributionTitle = mInfoPage.findViewById(R.id.preview_attribution_pane_title);
+            mAttributionSubtitle1 = mInfoPage.findViewById(R.id.preview_attribution_pane_subtitle1);
+            mAttributionSubtitle2 = mInfoPage.findViewById(R.id.preview_attribution_pane_subtitle2);
+            mSpacer = mInfoPage.findViewById(R.id.spacer);
+
+            mExploreButton = mInfoPage.findViewById(R.id.preview_attribution_pane_explore_button);
+            mSetWallpaperButton = mInfoPage.findViewById(
+                    R.id.preview_attribution_pane_set_wallpaper_button);
+        }
+
+        public void populate(List<String> attributions, boolean showMetadata,
+                OnClickListener setWallpaperOnClickListener,
+                CharSequence exploreButtonLabel,
+                @Nullable OnClickListener exploreOnClickListener) {
+            if (attributions.size() > 0 && attributions.get(0) != null) {
+                mAttributionTitle.setText(attributions.get(0));
+            }
+
+            if (showMetadata) {
+                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(setWallpaperOnClickListener);
+
+            setUpExploreButton(exploreButtonLabel, exploreOnClickListener);
+
+            if (mExploreButton.getVisibility() == View.VISIBLE
+                    && mSetWallpaperButton.getVisibility() == View.VISIBLE) {
+                mSpacer.setVisibility(View.VISIBLE);
+            } else {
+                mSpacer.setVisibility(View.GONE);
+            }
+        }
+
+        public void setContentAlpha(float alpha) {
+            mSetWallpaperButton.setAlpha(alpha);
+            mExploreButton.setAlpha(alpha);
+            mAttributionTitle.setAlpha(alpha);
+            mAttributionSubtitle1.setAlpha(alpha);
+            mAttributionSubtitle2.setAlpha(alpha);
+        }
+
+        private void setUpSetWallpaperButton(OnClickListener setWallpaperOnClickListener) {
+            if (mPreviewMode == MODE_VIEW_ONLY) {
+                mSetWallpaperButton.setVisibility(View.GONE);
+            } else {
+                mSetWallpaperButton.setVisibility(View.VISIBLE);
+                mSetWallpaperButton.setOnClickListener(setWallpaperOnClickListener);
+            }
+        }
+
+        private void setUpExploreButton(CharSequence label,
+                @Nullable OnClickListener exploreOnClickListener) {
+            mExploreButton.setVisibility(View.GONE);
+            if (exploreOnClickListener == null) {
+                return;
+            }
+            mExploreButton.setVisibility(View.VISIBLE);
+            mExploreButton.setText(label);
+
+            mExploreButton.setOnClickListener(exploreOnClickListener);
+        }
+    }
+}
diff --git a/src/com/android/wallpaper/picker/StartRotationDialogFragment.java b/src/com/android/wallpaper/picker/StartRotationDialogFragment.java
index d5fd017..f0e4dcd 100755
--- a/src/com/android/wallpaper/picker/StartRotationDialogFragment.java
+++ b/src/com/android/wallpaper/picker/StartRotationDialogFragment.java
@@ -29,8 +29,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-
-import androidx.appcompat.view.ContextThemeWrapper;
 import androidx.fragment.app.DialogFragment;
 
 import com.android.wallpaper.R;
@@ -42,29 +40,14 @@
  */
 public class StartRotationDialogFragment extends DialogFragment {
     private static final String KEY_IS_WIFI_ONLY_CHECKED = "key_is_wifi_only_checked";
-    private static final String KEY_IS_LIVE_WALLPAPER_PREVIEW_NEEDED = "key_is_live_wallpaper_needed";
     private static final boolean DEFAULT_IS_WIFI_ONLY = true;
 
     private boolean mIsWifiOnlyChecked;
-    private boolean mIsLiveWallpaperPreviewNeeded;
-
-    public static StartRotationDialogFragment newInstance(boolean isLiveWallpaperPreviewNeeded) {
-        StartRotationDialogFragment dialogFragment = new StartRotationDialogFragment();
-        Bundle args = new Bundle();
-        args.putBoolean(KEY_IS_LIVE_WALLPAPER_PREVIEW_NEEDED, isLiveWallpaperPreviewNeeded);
-        dialogFragment.setArguments(args);
-        return dialogFragment;
-    }
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        Bundle args = getArguments();
-        if (args != null) {
-            mIsLiveWallpaperPreviewNeeded = args.getBoolean(KEY_IS_LIVE_WALLPAPER_PREVIEW_NEEDED);
-        }
-
         if (savedInstanceState == null) {
             mIsWifiOnlyChecked = DEFAULT_IS_WIFI_ONLY;
         } else {
@@ -117,14 +100,10 @@
     }
 
     private int getBodyTextResourceId() {
-        return mIsLiveWallpaperPreviewNeeded
-                ? R.string.start_rotation_dialog_body_live_wallpaper_needed
-                : R.string.start_rotation_dialog_body;
+        return R.string.start_rotation_dialog_body;
     }
 
     private int getPositiveButtonTextResourceId() {
-        return mIsLiveWallpaperPreviewNeeded
-                ? R.string.start_rotation_dialog_continue
-                : android.R.string.ok;
+        return android.R.string.ok;
     }
 }
diff --git a/src/com/android/wallpaper/picker/TopLevelPickerActivity.java b/src/com/android/wallpaper/picker/TopLevelPickerActivity.java
index ac6a13b..87358ec 100755
--- a/src/com/android/wallpaper/picker/TopLevelPickerActivity.java
+++ b/src/com/android/wallpaper/picker/TopLevelPickerActivity.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.Drawable;
@@ -32,6 +33,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.WindowInsets;
 import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -47,6 +49,7 @@
 
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.compat.BuildCompat;
 import com.android.wallpaper.compat.ButtonDrawableSetterCompat;
 import com.android.wallpaper.config.Flags;
 import com.android.wallpaper.model.Category;
@@ -241,6 +244,29 @@
 
     private void initializeMobile(boolean shouldForceRefresh) {
         setContentView(R.layout.activity_single_fragment);
+        getWindow().getDecorView().setSystemUiVisibility(
+                getWindow().getDecorView().getSystemUiVisibility()
+                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+        findViewById(R.id.fragment_container)
+                .setOnApplyWindowInsetsListener((view, windowInsets) -> {
+            view.setPadding(view.getPaddingLeft(), windowInsets.getSystemWindowInsetTop(),
+                    view.getPaddingRight(), view.getBottom());
+            // Consume only the top inset (status bar), to let other content in the Activity consume
+            // the nav bar (ie, by using "fitSystemWindows")
+            if (BuildCompat.isAtLeastQ()) {
+                WindowInsets.Builder builder = new WindowInsets.Builder(windowInsets);
+                builder.setSystemWindowInsets(Insets.of(windowInsets.getSystemWindowInsetLeft(),
+                        0, windowInsets.getStableInsetRight(),
+                        windowInsets.getSystemWindowInsetBottom()));
+                return builder.build();
+            } else {
+                return windowInsets.replaceSystemWindowInsets(
+                        windowInsets.getSystemWindowInsetLeft(),
+                        0, windowInsets.getStableInsetRight(),
+                        windowInsets.getSystemWindowInsetBottom());
+            }
+        });
 
         // Set toolbar as the action bar.
         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java b/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java
index 1f5dc99..de5ba84 100755
--- a/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java
+++ b/src/com/android/wallpaper/picker/individual/IndividualPickerActivity.java
@@ -16,15 +16,10 @@
 package com.android.wallpaper.picker.individual;
 
 import android.app.Activity;
-import android.app.WallpaperManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Insets;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.MenuItem;
@@ -33,25 +28,23 @@
 import android.widget.Toast;
 
 import androidx.appcompat.widget.Toolbar;
-import androidx.core.content.ContextCompat;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
 
 import com.android.wallpaper.R;
 import com.android.wallpaper.compat.BuildCompat;
 import com.android.wallpaper.model.Category;
+import com.android.wallpaper.model.CategoryProvider;
+import com.android.wallpaper.model.CategoryReceiver;
 import com.android.wallpaper.model.InlinePreviewIntentFactory;
 import com.android.wallpaper.model.LiveWallpaperInfo;
 import com.android.wallpaper.model.PickerIntentFactory;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.module.Injector;
 import com.android.wallpaper.module.InjectorProvider;
-import com.android.wallpaper.module.LiveWallpaperStatusChecker;
-import com.android.wallpaper.module.NoBackupImageWallpaper;
 import com.android.wallpaper.module.WallpaperPersister;
 import com.android.wallpaper.picker.BaseActivity;
 import com.android.wallpaper.picker.PreviewActivity.PreviewActivityIntentFactory;
-import com.android.wallpaper.util.ActivityUtils;
 import com.android.wallpaper.util.DiskBasedLogger;
 
 /**
@@ -63,13 +56,11 @@
     private static final String EXTRA_CATEGORY_COLLECTION_ID =
             "com.android.wallpaper.category_collection_id";
     private static final int PREVIEW_WALLPAPER_REQUEST_CODE = 0;
-    private static final int NO_BACKUP_IMAGE_WALLPAPER_REQUEST_CODE = 1;
     private static final int PREVIEW_LIVEWALLPAPER_REQUEST_CODE = 2;
     private static final String KEY_CATEGORY_COLLECTION_ID = "key_category_collection_id";
 
     private InlinePreviewIntentFactory mPreviewIntentFactory;
     private WallpaperPersister mWallpaperPersister;
-    private LiveWallpaperStatusChecker mLiveWallpaperStatusChecker;
     private Category mCategory;
     private String mCategoryCollectionId;
 
@@ -85,7 +76,6 @@
         mPreviewIntentFactory = new PreviewActivityIntentFactory();
         Injector injector = InjectorProvider.getInjector();
         mWallpaperPersister = injector.getWallpaperPersister(this);
-        mLiveWallpaperStatusChecker = injector.getLiveWallpaperStatusChecker(this);
 
         FragmentManager fm = getSupportFragmentManager();
         Fragment fragment = fm.findFragmentById(R.id.fragment_container);
@@ -93,18 +83,29 @@
         mCategoryCollectionId = (savedInstanceState == null)
                 ? getIntent().getStringExtra(EXTRA_CATEGORY_COLLECTION_ID)
                 : savedInstanceState.getString(KEY_CATEGORY_COLLECTION_ID);
-        mCategory = injector.getCategoryProvider(this).getCategory(mCategoryCollectionId);
-        if (mCategory == null) {
-            DiskBasedLogger.e(TAG, "Failed to find the category: " + mCategoryCollectionId, this);
-            // We either were called with an invalid collection Id, or we're restarting with no
-            // saved state, or with a collection id that doesn't exist anymore.
-            // In those cases, we cannot continue, so let's just go back.
-            finish();
-            return;
-        }
+        CategoryProvider categoryProvider = injector.getCategoryProvider(this);
+        categoryProvider.fetchCategories(new CategoryReceiver() {
+            @Override
+            public void onCategoryReceived(Category category) {
+                // Do nothing.
+            }
 
-        setTitle(mCategory.getTitle());
-        getSupportActionBar().setTitle(mCategory.getTitle());
+            @Override
+            public void doneFetchingCategories() {
+                mCategory = categoryProvider.getCategory(mCategoryCollectionId);
+                if (mCategory == null) {
+                    DiskBasedLogger.e(TAG, "Failed to find the category: " + mCategoryCollectionId,
+                            IndividualPickerActivity.this);
+                    // We either were called with an invalid collection Id, or we're restarting with
+                    // no saved state, or with a collection id that doesn't exist anymore.
+                    // In those cases, we cannot continue, so let's just go back.
+                    finish();
+                    return;
+                }
+                onCategoryLoaded();
+            }
+        }, false);
+
         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
 
         toolbar.getNavigationIcon().setTint(getColor(R.color.toolbar_icon_color));
@@ -114,7 +115,8 @@
                 getWindow().getDecorView().getSystemUiVisibility()
                         | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                         | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-        getWindow().getDecorView().setOnApplyWindowInsetsListener((view, windowInsets) -> {
+        ((View) findViewById(R.id.fragment_container).getParent())
+                .setOnApplyWindowInsetsListener((view, windowInsets) -> {
             view.setPadding(view.getPaddingLeft(), windowInsets.getSystemWindowInsetTop(),
                     view.getPaddingRight(), view.getBottom());
             // Consume only the top inset (status bar), to let other content in the Activity consume
@@ -141,12 +143,17 @@
         }
     }
 
+    private void onCategoryLoaded() {
+        setTitle(mCategory.getTitle());
+        getSupportActionBar().setTitle(mCategory.getTitle());
+    }
+
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         int id = item.getItemId();
         if (id == android.R.id.home) {
-            // Handle Up as a Global back since the only entry point to IndividualPickerActivity is from
-            // TopLevelPickerActivity.
+            // Handle Up as a Global back since the only entry point to IndividualPickerActivity is
+            // from TopLevelPickerActivity.
             onBackPressed();
             return true;
         }
@@ -169,18 +176,6 @@
                     finishWithResultOk(shouldShowMessage);
                 }
                 break;
-
-            case NO_BACKUP_IMAGE_WALLPAPER_REQUEST_CODE:
-                // User clicked "Set wallpaper" in live wallpaper preview UI.
-                // NOTE: Don't check for the result code prior to KitKat MR2 because a bug on those versions
-                // caused the result code to be discarded from LivePicker so we can't rely on it.
-                if ((!BuildCompat.isAtLeastL() || resultCode == Activity.RESULT_OK)
-                        && mLiveWallpaperStatusChecker.isNoBackupImageWallpaperSet()
-                        && mCategory.getWallpaperRotationInitializer().startRotation(getApplicationContext())) {
-                    finishWithResultOk(true);
-                }
-                break;
-
             default:
                 Log.e(TAG, "Invalid request code: " + requestCode);
         }
@@ -196,18 +191,6 @@
                         : PREVIEW_WALLPAPER_REQUEST_CODE);
     }
 
-    /**
-     * Shows the system live wallpaper preview for the {@link NoBackupImageWallpaper} which is used to
-     * draw rotating wallpapers on pre-N Android builds.
-     */
-    public void showNoBackupImageWallpaperPreview() {
-        Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
-        ComponentName componentName = new ComponentName(this, NoBackupImageWallpaper.class);
-        intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, componentName);
-        ActivityUtils.startActivityForResultSafely(
-                this, intent, NO_BACKUP_IMAGE_WALLPAPER_REQUEST_CODE);
-    }
-
     private void finishWithResultOk(boolean shouldShowMessage) {
         if (shouldShowMessage) {
             try {
diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
index 654ec13..c0c4ce5 100755
--- a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
+++ b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
@@ -48,6 +48,9 @@
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
 import com.android.wallpaper.config.Flags;
+import com.android.wallpaper.model.Category;
+import com.android.wallpaper.model.CategoryProvider;
+import com.android.wallpaper.model.CategoryReceiver;
 import com.android.wallpaper.model.WallpaperCategory;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.model.WallpaperReceiver;
@@ -55,13 +58,11 @@
 import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
 import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
 import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
-import com.android.wallpaper.model.WallpaperRotationInitializer.RotationStateListener;
 import com.android.wallpaper.module.FormFactorChecker;
 import com.android.wallpaper.module.FormFactorChecker.FormFactor;
 import com.android.wallpaper.module.Injector;
 import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.module.PackageStatusNotifier;
-import com.android.wallpaper.module.RotatingWallpaperComponentChecker;
 import com.android.wallpaper.module.WallpaperChangedNotifier;
 import com.android.wallpaper.module.WallpaperPersister;
 import com.android.wallpaper.module.WallpaperPersister.Destination;
@@ -113,7 +114,6 @@
 
     WallpaperPreferences mWallpaperPreferences;
     WallpaperChangedNotifier mWallpaperChangedNotifier;
-    RotatingWallpaperComponentChecker mRotatingWallpaperComponentChecker;
     RecyclerView mImageGrid;
     IndividualAdapter mAdapter;
     WallpaperCategory mCategory;
@@ -247,8 +247,6 @@
         mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
         mWallpaperChangedNotifier.registerListener(mWallpaperChangedListener);
 
-        mRotatingWallpaperComponentChecker = injector.getRotatingWallpaperComponentChecker();
-
         mFormFactor = injector.getFormFactorChecker(appContext).getFormFactor();
 
         mPackageStatusNotifier = injector.getPackageStatusNotifier(appContext);
@@ -257,27 +255,38 @@
         mRandom = new Random();
         mHandler = new Handler();
 
-        String collectionId = getArguments().getString(ARG_CATEGORY_COLLECTION_ID);
-        mCategory = (WallpaperCategory) injector.getCategoryProvider(appContext).getCategory(
-                collectionId);
-        if (mCategory == null) {
-            DiskBasedLogger.e(TAG, "Failed to find the category.", appContext);
-
-            // The absence of this category in the CategoryProvider indicates a broken state, probably due
-            // to a relaunch into this activity/fragment following a crash immediately prior; see
-            // b//38030129. Hence, finish the activity and return.
-            getActivity().finish();
-            return;
-        }
-
-        mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
-
         // Clear Glide's cache if night-mode changed to ensure thumbnails are reloaded
         if (savedInstanceState != null && (savedInstanceState.getInt(KEY_NIGHT_MODE)
                 != (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK))) {
             Glide.get(getContext()).clearMemory();
         }
 
+        CategoryProvider categoryProvider = injector.getCategoryProvider(appContext);
+        categoryProvider.fetchCategories(new CategoryReceiver() {
+            @Override
+            public void onCategoryReceived(Category category) {
+                // Do nothing.
+            }
+
+            @Override
+            public void doneFetchingCategories() {
+                mCategory = (WallpaperCategory) categoryProvider.getCategory(
+                        getArguments().getString(ARG_CATEGORY_COLLECTION_ID));
+                if (mCategory == null) {
+                    DiskBasedLogger.e(TAG, "Failed to find the category.", getContext());
+
+                    // The absence of this category in the CategoryProvider indicates a broken
+                    // state, see b/38030129. Hence, finish the activity and return.
+                    getActivity().finish();
+                    return;
+                }
+                onCategoryLoaded();
+            }
+        }, false);
+    }
+
+    protected void onCategoryLoaded() {
+        mWallpaperRotationInitializer = mCategory.getWallpaperRotationInitializer();
         fetchWallpapers(false);
 
         if (mCategory.supportsThirdParty()) {
@@ -290,6 +299,8 @@
             mPackageStatusNotifier.addListener(mAppStatusListener,
                     WallpaperService.SERVICE_INTERFACE);
         }
+
+        maybeSetUpImageGrid();
     }
 
     void fetchWallpapers(boolean forceReload) {
@@ -346,7 +357,8 @@
         }
         GridMarginDecoration.applyTo(mImageGrid);
 
-        setUpImageGrid();
+        maybeSetUpImageGrid();
+
         setUpBottomSheet();
 
         return view;
@@ -369,6 +381,26 @@
                 gridPaddingPx, gridPaddingPx, 0, paddingBottomPx);
     }
 
+    private void maybeSetUpImageGrid() {
+        // Skip if mImageGrid been initialized yet
+        if (mImageGrid == null) {
+            return;
+        }
+        // Skip if category hasn't loaded yet
+        if (mCategory == null) {
+            return;
+        }
+        // Skip if the adapter was already created
+        if (mAdapter != null) {
+            return;
+        }
+        setUpImageGrid();
+    }
+
+    /**
+     * Create the adapter and assign it to mImageGrid.
+     * Both mImageGrid and mCategory are guaranteed to not be null when this method is called.
+     */
     void setUpImageGrid() {
         mAdapter = new IndividualAdapter(mWallpapers);
         mImageGrid.setAdapter(mAdapter);
@@ -408,7 +440,8 @@
     public void onResume() {
         super.onResume();
 
-        WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity());
+        WallpaperPreferences preferences = InjectorProvider.getInjector()
+                .getPreferences(getActivity());
         preferences.setLastAppActiveTimestamp(new Date().getTime());
 
         // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in
@@ -504,31 +537,23 @@
      * state of the user's device and binds the state of the current category's rotation to the "start
      * rotation" tile.
      */
-    private void refreshRotationHolder(final RotationHolder rotationHolder) {
+    private void refreshRotationHolder(RotationHolder rotationHolder) {
         mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
-                new RotationStateListener() {
-                    @Override
-                    public void onRotationStateReceived(
-                            @RotationInitializationState final int rotationInitializationState) {
+                rotationState -> {
+                    // Update the UI state of the "start rotation" tile displayed on screen.
+                    // Do this in a Handler so it is scheduled at the end of the message queue.
+                    // This is necessary to ensure we do not remove or add data from the adapter
+                    // while the layout is still being computed. RecyclerView documentation
+                    // therefore recommends performing such changes in a Handler.
+                    new Handler().post(() -> {
+                        // A config change may have destroyed the activity since the refresh
+                        // started, so check for that to avoid an NPE.
+                        if (getActivity() == null) {
+                            return;
+                        }
 
-                        // Update the UI state of the "start rotation" tile displayed on screen. Do this in a
-                        // Handler so it is scheduled at the end of the message queue. This is necessary to
-                        // ensure we do not remove or add data from the adapter while the layout is still being
-                        // computed. RecyclerView documentation therefore recommends performing such changes in
-                        // a Handler.
-                        new android.os.Handler().post(new Runnable() {
-                            @Override
-                            public void run() {
-                                // A config change may have destroyed the activity since the refresh started, so
-                                // check for that to avoid an NPE.
-                                if (getActivity() == null) {
-                                    return;
-                                }
-
-                                rotationHolder.bindRotationInitializationState(rotationInitializationState);
-                            }
-                        });
-                    }
+                        rotationHolder.bindRotationInitializationState(rotationState);
+                    });
                 });
     }
 
@@ -577,34 +602,33 @@
                         // app before the first wallpaper image in rotation finishes downloading.
                         Activity activity = getActivity();
 
-                        if (activity != null
-                                && mWallpaperRotationInitializer
-                                .isNoBackupImageWallpaperPreviewNeeded(appContext)) {
-                            ((IndividualPickerActivity) activity).showNoBackupImageWallpaperPreview();
-                        } else {
-                            if (mWallpaperRotationInitializer.startRotation(appContext)) {
-                                if (activity != null && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
-                                    try {
-                                        Toast.makeText(getActivity(), R.string.wallpaper_set_successfully_message,
-                                                Toast.LENGTH_SHORT).show();
-                                    } catch (NotFoundException e) {
-                                        Log.e(TAG, "Could not show toast " + e);
-                                    }
 
-                                    activity.setResult(Activity.RESULT_OK);
-                                    activity.finish();
-                                } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
-                                    mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+                        if (mWallpaperRotationInitializer.startRotation(appContext)) {
+                            if (activity != null
+                                    && mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
+                                try {
+                                    Toast.makeText(getActivity(),
+                                            R.string.wallpaper_set_successfully_message,
+                                            Toast.LENGTH_SHORT).show();
+                                } catch (NotFoundException e) {
+                                    Log.e(TAG, "Could not show toast " + e);
                                 }
-                            } else { // Failed to start rotation.
-                                showStartRotationErrorDialog(networkPreference);
 
-                                if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
-                                    DesktopRotationHolder rotationViewHolder =
-                                            (DesktopRotationHolder) mImageGrid.findViewHolderForAdapterPosition(
-                                                    SPECIAL_FIXED_TILE_ADAPTER_POSITION);
-                                    rotationViewHolder.setSelectionState(SelectableHolder.SELECTION_STATE_DESELECTED);
-                                }
+                                activity.setResult(Activity.RESULT_OK);
+                                activity.finish();
+                            } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+                                mAdapter.updateSelectedTile(SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+                            }
+                        } else { // Failed to start rotation.
+                            showStartRotationErrorDialog(networkPreference);
+
+                            if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
+                                DesktopRotationHolder rotationViewHolder =
+                                        (DesktopRotationHolder)
+                                                mImageGrid.findViewHolderForAdapterPosition(
+                                                SPECIAL_FIXED_TILE_ADAPTER_POSITION);
+                                rotationViewHolder.setSelectionState(
+                                        SelectableHolder.SELECTION_STATE_DESELECTED);
                             }
                         }
                     }
@@ -645,18 +669,15 @@
     }
 
     int getNumColumns() {
-        return TileSizeCalculator.getNumIndividualColumns(getActivity());
+        Activity activity = getActivity();
+        return activity == null ? 0 : TileSizeCalculator.getNumIndividualColumns(activity);
     }
 
     /**
      * Returns whether rotation is enabled for this category.
      */
     boolean isRotationEnabled() {
-        boolean isRotationSupported =
-                mRotatingWallpaperComponentChecker.getRotatingWallpaperSupport(getContext())
-                        == RotatingWallpaperComponentChecker.ROTATING_WALLPAPER_SUPPORT_SUPPORTED;
-
-        return isRotationSupported && mWallpaperRotationInitializer != null;
+        return mWallpaperRotationInitializer != null;
     }
 
     @Override
@@ -694,10 +715,10 @@
             super(itemView);
             itemView.setOnClickListener(this);
 
-            mTileLayout = (FrameLayout) itemView.findViewById(R.id.daily_refresh);
-            mRotationMessage = (TextView) itemView.findViewById(R.id.rotation_tile_message);
-            mRotationTitle = (TextView) itemView.findViewById(R.id.rotation_tile_title);
-            mRefreshIcon = (ImageView) itemView.findViewById(R.id.rotation_tile_refresh_icon);
+            mTileLayout = itemView.findViewById(R.id.daily_refresh);
+            mRotationMessage = itemView.findViewById(R.id.rotation_tile_message);
+            mRotationTitle = itemView.findViewById(R.id.rotation_tile_title);
+            mRefreshIcon = itemView.findViewById(R.id.rotation_tile_refresh_icon);
             mTileLayout.getLayoutParams().height = mTileSizePx.y;
 
             // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
@@ -711,7 +732,8 @@
                 mRotationMessage.setTextColor(
                         getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
                 mRefreshIcon.setColorFilter(
-                        getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color), Mode.SRC_IN);
+                        getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color),
+                        Mode.SRC_IN);
                 return;
             }
 
@@ -727,10 +749,7 @@
 
         @Override
         public void onClick(View v) {
-            boolean isLiveWallpaperNeeded = mWallpaperRotationInitializer
-                    .isNoBackupImageWallpaperPreviewNeeded(getActivity().getApplicationContext());
-            DialogFragment startRotationDialogFragment = StartRotationDialogFragment
-                    .newInstance(isLiveWallpaperNeeded);
+            DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
             startRotationDialogFragment.setTargetFragment(
                     IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
             startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
diff --git a/src/com/android/wallpaper/util/TileSizeCalculator.java b/src/com/android/wallpaper/util/TileSizeCalculator.java
index f1c76b2..84ecad7 100755
--- a/src/com/android/wallpaper/util/TileSizeCalculator.java
+++ b/src/com/android/wallpaper/util/TileSizeCalculator.java
@@ -28,6 +28,8 @@
 import com.android.wallpaper.module.FormFactorChecker.FormFactor;
 import com.android.wallpaper.module.InjectorProvider;
 
+import androidx.annotation.NonNull;
+
 /**
  * Simple utility class that calculates tile sizes relative to the size of the display.
  */
@@ -65,7 +67,7 @@
      * Returns the number of columns for a grid of category tiles. Selects from fewer and more columns
      * based on the width of the activity.
      */
-    public static int getNumCategoryColumns(Activity activity) {
+    public static int getNumCategoryColumns(@NonNull Activity activity) {
         int windowWidthPx = getActivityWindowWidthPx(activity);
         return getNumCategoryColumns(activity, windowWidthPx);
     }
@@ -74,7 +76,7 @@
      * Returns the number of columns for a grid of individual tiles. Selects from fewer and more
      * columns based on the width of the activity.
      */
-    public static int getNumIndividualColumns(Activity activity) {
+    public static int getNumIndividualColumns(@NonNull Activity activity) {
         int windowWidthPx = getActivityWindowWidthPx(activity);
         return getNumIndividualColumns(activity, windowWidthPx);
     }
@@ -109,7 +111,7 @@
     /**
      * Returns the size of a category grid tile in px.
      */
-    public static Point getCategoryTileSize(Activity activity) {
+    public static Point getCategoryTileSize(@NonNull Activity activity) {
         Context appContext = activity.getApplicationContext();
         int windowWidthPx = getActivityWindowWidthPx(activity);
 
@@ -120,7 +122,7 @@
     /**
      * Returns the size of an individual grid tile for the given activity in px.
      */
-    public static Point getIndividualTileSize(Activity activity) {
+    public static Point getIndividualTileSize(@NonNull Activity activity) {
         Context appContext = activity.getApplicationContext();
         int windowWidthPx = getActivityWindowWidthPx(activity);
 
@@ -133,7 +135,7 @@
      * category or individual tile on any-sized activity on the device. This size matches the
      * individual tile size when an activity takes up the entire screen's width.
      */
-    public static Point getSuggestedThumbnailSize(Context appContext) {
+    public static Point getSuggestedThumbnailSize(@NonNull Context appContext) {
         // Category tiles are larger than individual tiles, so get the number of columns for categories
         // and then calculate a tile size for when the app window takes up the entire display.
         int windowWidthPx = getDeviceDisplayWidthPx(appContext);
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/src_override/com/android/wallpaper/module/WallpapersInjector.java b/src_override/com/android/wallpaper/module/WallpapersInjector.java
index 8e3b3e9..5a39524 100755
--- a/src_override/com/android/wallpaper/module/WallpapersInjector.java
+++ b/src_override/com/android/wallpaper/module/WallpapersInjector.java
@@ -17,12 +17,12 @@
 
 import android.content.Context;
 
+import androidx.fragment.app.Fragment;
+
 import com.android.wallpaper.model.CategoryProvider;
 import com.android.wallpaper.model.WallpaperInfo;
 import com.android.wallpaper.monitor.PerformanceMonitor;
-import com.android.wallpaper.picker.PreviewFragment;
-
-import androidx.fragment.app.Fragment;
+import com.android.wallpaper.picker.ImagePreviewFragment;
 
 /**
  * A concrete, real implementation of the dependency provider.
@@ -69,7 +69,7 @@
         WallpaperInfo wallpaperInfo,
         int mode,
         boolean testingModeEnabled) {
-        return PreviewFragment.newInstance(wallpaperInfo, mode, testingModeEnabled);
+        return ImagePreviewFragment.newInstance(wallpaperInfo, mode, testingModeEnabled);
     }
 
     @Override
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..be224e3
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,59 @@
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+#
+# Build rule for WallpaperPicker2 tests
+#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    androidx.annotation_annotation \
+    androidx.test.core \
+    androidx.test.runner \
+    androidx.test.rules \
+    androidx.test.espresso.contrib \
+    androidx.test.espresso.intents \
+    mockito-target-minus-junit4 \
+    androidx.test.espresso.core \
+    hamcrest-library \
+    hamcrest
+
+ifneq (,$(wildcard frameworks/base))
+    LOCAL_PRIVATE_PLATFORM_APIS := true
+else
+    LOCAL_SDK_VERSION := 29
+    LOCAL_MIN_SDK_VERSION := 26
+endif
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_ANDROID_LIBRARIES := WallpaperPicker2CommonDepsLib
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_INSTRUMENTATION_FOR := WallpaperPicker2
+
+LOCAL_PACKAGE_NAME := WallpaperPicker2Tests
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1e35c92
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.wallpaper.tests">
+
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="29"/>
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation
+        android:functionalTest="false"
+        android:handleProfiling="false"
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.wallpaper">
+    </instrumentation>
+</manifest>
diff --git a/tests/src/com/android/wallpaper/picker/PreviewActivityTest.java b/tests/src/com/android/wallpaper/picker/PreviewActivityTest.java
new file mode 100644
index 0000000..e1feba5
--- /dev/null
+++ b/tests/src/com/android/wallpaper/picker/PreviewActivityTest.java
@@ -0,0 +1,510 @@
+/*
+ * 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 androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static junit.framework.TestCase.assertFalse;
+
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.widget.TextView;
+
+import androidx.test.espresso.intent.Intents;
+import androidx.test.filters.MediumTest;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.wallpaper.R;
+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.testing.TestAsset;
+import com.android.wallpaper.testing.TestExploreIntentChecker;
+import com.android.wallpaper.testing.TestInjector;
+import com.android.wallpaper.testing.TestUserEventLogger;
+import com.android.wallpaper.testing.TestWallpaperInfo;
+import com.android.wallpaper.testing.TestWallpaperPersister;
+import com.android.wallpaper.util.ScreenSizeCalculator;
+import com.android.wallpaper.util.WallpaperCropUtils;
+
+import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link PreviewActivity}.
+ */
+@RunWith(AndroidJUnit4ClassRunner.class)
+@MediumTest
+public class PreviewActivityTest {
+
+    private static final float FLOAT_ERROR_MARGIN = 0.001f;
+    private static final String ACTION_URL = "http://google.com";
+
+    private TestWallpaperInfo mMockWallpaper;
+    private Injector mInjector;
+    private TestWallpaperPersister mWallpaperPersister;
+    private TestUserEventLogger mEventLogger;
+    private TestExploreIntentChecker mExploreIntentChecker;
+
+    @Rule
+    public ActivityTestRule<PreviewActivity> mActivityRule =
+            new ActivityTestRule<>(PreviewActivity.class, false, false);
+
+    @Before
+    public void setUp() {
+
+        mInjector = new TestInjector();
+        InjectorProvider.setInjector(mInjector);
+
+        Intents.init();
+
+        mMockWallpaper = new TestWallpaperInfo(TestWallpaperInfo.COLOR_BLACK);
+        List<String> attributions = new ArrayList<>();
+        attributions.add("Title");
+        attributions.add("Subtitle 1");
+        attributions.add("Subtitle 2");
+        mMockWallpaper.setAttributions(attributions);
+        mMockWallpaper.setCollectionId("collection");
+        mMockWallpaper.setWallpaperId("wallpaper");
+        mMockWallpaper.setActionUrl(ACTION_URL);
+
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mWallpaperPersister = (TestWallpaperPersister) mInjector.getWallpaperPersister(context);
+        mEventLogger = (TestUserEventLogger) mInjector.getUserEventLogger(context);
+        mExploreIntentChecker = (TestExploreIntentChecker)
+                mInjector.getExploreIntentChecker(context);
+    }
+
+    @After
+    public void tearDown() {
+        Intents.release();
+        mActivityRule.finishActivity();
+    }
+
+    private void launchActivityIntentWithMockWallpaper() {
+        Intent intent = PreviewActivity.newIntent(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), mMockWallpaper);
+        intent.putExtra(BasePreviewActivity.EXTRA_TESTING_MODE_ENABLED, true);
+
+        mActivityRule.launchActivity(intent);
+    }
+
+    private void finishSettingWallpaper() throws Throwable {
+        mActivityRule.runOnUiThread(() -> mWallpaperPersister.finishSettingWallpaper());
+    }
+
+    @Test
+    public void testRendersWallpaperDrawableFromIntent() {
+        launchActivityIntentWithMockWallpaper();
+        PreviewActivity activity = mActivityRule.getActivity();
+        SubsamplingScaleImageView mosaicView = activity.findViewById(R.id.full_res_image);
+
+        assertTrue(mosaicView.hasImage());
+    }
+
+    @Test
+    public void testClickSetWallpaper_Success_HomeScreen() throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Home screen".
+        onView(withText(R.string.set_wallpaper_home_screen_destination)).perform(click());
+
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+
+        finishSettingWallpaper();
+
+        // Mock system wallpaper bitmap should be equal to the mock WallpaperInfo's bitmap.
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Bitmap srcBitmap = ((TestAsset) mMockWallpaper.getAsset(context)).getBitmap();
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentHomeWallpaper()));
+
+        // The wallpaper should have been set on the home screen.
+        assertEquals(WallpaperPersister.DEST_HOME_SCREEN, mWallpaperPersister.getLastDestination());
+        assertEquals(1, mEventLogger.getNumWallpaperSetEvents());
+
+        assertEquals(1, mEventLogger.getNumWallpaperSetResultEvents());
+        assertEquals(UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS,
+                mEventLogger.getLastWallpaperSetResult());
+    }
+
+    @Test
+    public void testClickSetWallpaper_Success_LockScreen() throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Lock screen."
+        onView(withText(R.string.set_wallpaper_lock_screen_destination)).perform(click());
+
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+
+        finishSettingWallpaper();
+
+        // Mock system wallpaper bitmap should be equal to the mock WallpaperInfo's bitmap.
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Bitmap srcBitmap = ((TestAsset) mMockWallpaper.getAsset(context)).getBitmap();
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentLockWallpaper()));
+
+        // The wallpaper should have been set on the lock screen.
+        assertEquals(WallpaperPersister.DEST_LOCK_SCREEN, mWallpaperPersister.getLastDestination());
+        assertEquals(1, mEventLogger.getNumWallpaperSetEvents());
+
+        assertEquals(1, mEventLogger.getNumWallpaperSetResultEvents());
+        assertEquals(UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS,
+                mEventLogger.getLastWallpaperSetResult());
+    }
+
+    @Test
+    public void testClickSetWallpaper_Success_BothHomeAndLockScreen() throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Both."
+        onView(withText(R.string.set_wallpaper_both_destination)).perform(click());
+
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+
+        finishSettingWallpaper();
+
+        // Mock system wallpaper bitmap should be equal to the mock WallpaperInfo's bitmap.
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Bitmap srcBitmap = ((TestAsset) mMockWallpaper.getAsset(context)).getBitmap();
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentHomeWallpaper()));
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentLockWallpaper()));
+
+        // The wallpaper should have been set on both the home and the lock screen.
+        assertEquals(WallpaperPersister.DEST_BOTH, mWallpaperPersister.getLastDestination());
+        assertEquals(1, mEventLogger.getNumWallpaperSetEvents());
+
+        assertEquals(1, mEventLogger.getNumWallpaperSetResultEvents());
+        assertEquals(UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS,
+                mEventLogger.getLastWallpaperSetResult());
+    }
+
+    @Test
+    public void testClickSetWallpaper_Fails_HomeScreen_ShowsErrorDialog()
+            throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+
+        mWallpaperPersister.setFailNextCall(true);
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Home screen."
+        onView(withText(R.string.set_wallpaper_home_screen_destination)).perform(click());
+
+        finishSettingWallpaper();
+
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+        onView(withText(R.string.set_wallpaper_error_message)).check(matches(isDisplayed()));
+
+        assertEquals(1, mEventLogger.getNumWallpaperSetResultEvents());
+        assertEquals(UserEventLogger.WALLPAPER_SET_RESULT_FAILURE,
+                mEventLogger.getLastWallpaperSetResult());
+
+        // Set next call to succeed and current wallpaper bitmap should not be null and equals to
+        // the
+        // mock wallpaper bitmap after clicking "try again".
+        mWallpaperPersister.setFailNextCall(false);
+
+        onView(withText(R.string.try_again)).perform(click());
+        finishSettingWallpaper();
+
+        assertNotNull(mWallpaperPersister.getCurrentHomeWallpaper());
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Bitmap srcBitmap = ((TestAsset) mMockWallpaper.getAsset(context)).getBitmap();
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentHomeWallpaper()));
+
+        // The wallpaper should have been set on the home screen.
+        assertEquals(WallpaperPersister.DEST_HOME_SCREEN, mWallpaperPersister.getLastDestination());
+    }
+
+    @Test
+    public void testClickSetWallpaper_Fails_LockScreen_ShowsErrorDialog()
+            throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+
+        mWallpaperPersister.setFailNextCall(true);
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Lock screen."
+        onView(withText(R.string.set_wallpaper_lock_screen_destination)).perform(click());
+
+        finishSettingWallpaper();
+
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+        onView(withText(R.string.set_wallpaper_error_message)).check(matches(isDisplayed()));
+
+        assertEquals(1, mEventLogger.getNumWallpaperSetResultEvents());
+        assertEquals(UserEventLogger.WALLPAPER_SET_RESULT_FAILURE,
+                mEventLogger.getLastWallpaperSetResult());
+
+        // Set next call to succeed and current wallpaper bitmap should not be null and equals to
+        // the
+        // mock wallpaper bitmap after clicking "try again".
+        mWallpaperPersister.setFailNextCall(false);
+
+        onView(withText(R.string.try_again)).perform(click());
+        finishSettingWallpaper();
+
+        assertNotNull(mWallpaperPersister.getCurrentLockWallpaper());
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Bitmap srcBitmap = ((TestAsset) mMockWallpaper.getAsset(context)).getBitmap();
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentLockWallpaper()));
+
+        // The wallpaper should have been set on the lock screen.
+        assertEquals(WallpaperPersister.DEST_LOCK_SCREEN, mWallpaperPersister.getLastDestination());
+    }
+
+    @Test
+    public void testClickSetWallpaper_Fails_BothHomeAndLock_ShowsErrorDialog()
+            throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+
+        mWallpaperPersister.setFailNextCall(true);
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Both."
+        onView(withText(R.string.set_wallpaper_both_destination)).perform(click());
+
+        finishSettingWallpaper();
+
+        assertNull(mWallpaperPersister.getCurrentHomeWallpaper());
+        assertNull(mWallpaperPersister.getCurrentLockWallpaper());
+        onView(withText(R.string.set_wallpaper_error_message)).check(matches(isDisplayed()));
+
+        assertEquals(1, mEventLogger.getNumWallpaperSetResultEvents());
+        assertEquals(UserEventLogger.WALLPAPER_SET_RESULT_FAILURE,
+                mEventLogger.getLastWallpaperSetResult());
+
+        // Set next call to succeed and current wallpaper bitmap should not be null and equals to
+        // the mock wallpaper bitmap after clicking "try again".
+        mWallpaperPersister.setFailNextCall(false);
+
+        onView(withText(R.string.try_again)).perform(click());
+        finishSettingWallpaper();
+
+        assertNotNull(mWallpaperPersister.getCurrentHomeWallpaper());
+        assertNotNull(mWallpaperPersister.getCurrentLockWallpaper());
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Bitmap srcBitmap = ((TestAsset) mMockWallpaper.getAsset(context)).getBitmap();
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentHomeWallpaper()));
+        assertTrue(srcBitmap.sameAs(mWallpaperPersister.getCurrentLockWallpaper()));
+
+        // The wallpaper should have been set on both the home screen and the lock screen.
+        assertEquals(WallpaperPersister.DEST_BOTH, mWallpaperPersister.getLastDestination());
+    }
+
+    @Test
+    public void testClickSetWallpaper_CropsAndScalesWallpaper() {
+        launchActivityIntentWithMockWallpaper();
+        // Scale should not have a meaningful value before clicking "set wallpaper".
+        assertTrue(mWallpaperPersister.getScale() < 0);
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Home screen".
+        onView(withText(R.string.set_wallpaper_home_screen_destination)).perform(click());
+
+        // WallpaperPersister's scale should match the ScaleImageView's scale.
+        float zoom = ((SubsamplingScaleImageView)
+                mActivityRule.getActivity().findViewById(R.id.full_res_image)).getScale();
+        assertEquals(mWallpaperPersister.getScale(), zoom, FLOAT_ERROR_MARGIN);
+
+        Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
+                mActivityRule.getActivity().getWindowManager().getDefaultDisplay());
+        int maxDim = Math.max(screenSize.x, screenSize.y);
+        Rect cropRect = mWallpaperPersister.getCropRect();
+
+        // Crop rect should be greater or equal than screen size in both directions.
+        assertTrue(cropRect.width() >= maxDim);
+        assertTrue(cropRect.height() >= maxDim);
+    }
+
+    @Test
+    public void testClickSetWallpaper_FailsCroppingAndScalingWallpaper_ShowsErrorDialog()
+            throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        mWallpaperPersister.setFailNextCall(true);
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+        // Destination dialog is shown; click "Home screen".
+        onView(withText(R.string.set_wallpaper_home_screen_destination)).perform(click());
+
+        finishSettingWallpaper();
+
+        onView(withText(R.string.set_wallpaper_error_message)).check(matches(isDisplayed()));
+    }
+
+    /**
+     * Tests that tapping Set Wallpaper shows the destination dialog (i.e., choosing
+     * between Home screen, Lock screen, or Both).
+     */
+    @Test
+    public void testClickSetWallpaper_ShowsDestinationDialog() {
+        launchActivityIntentWithMockWallpaper();
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+        onView(withText(R.string.set_wallpaper_dialog_message)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testSetsDefaultWallpaperZoomAndScroll() {
+        float expectedWallpaperZoom;
+        int expectedWallpaperScrollX;
+        int expectedWallpaperScrollY;
+
+        launchActivityIntentWithMockWallpaper();
+        PreviewActivity activity = mActivityRule.getActivity();
+        SubsamplingScaleImageView fullResImageView = activity.findViewById(R.id.full_res_image);
+
+        Point defaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
+                activity.getResources(), activity.getWindowManager().getDefaultDisplay());
+        Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
+                activity.getWindowManager().getDefaultDisplay());
+        TestAsset asset = (TestAsset) mMockWallpaper.getAsset(activity);
+        Point wallpaperSize = new Point(asset.getBitmap().getWidth(),
+                asset.getBitmap().getHeight());
+
+        expectedWallpaperZoom = WallpaperCropUtils.calculateMinZoom(
+                wallpaperSize, defaultCropSurfaceSize);
+
+        // Current zoom should match the minimum zoom required to fit wallpaper
+        // completely on the crop surface.
+        assertEquals(expectedWallpaperZoom, fullResImageView.getScale(), FLOAT_ERROR_MARGIN);
+
+        Point scaledWallpaperSize = new Point(
+                (int) (wallpaperSize.x * expectedWallpaperZoom),
+                (int) (wallpaperSize.y * expectedWallpaperZoom));
+        Point cropSurfaceToScreen = WallpaperCropUtils.calculateCenterPosition(
+                defaultCropSurfaceSize, screenSize, true /* alignStart */, false /* isRtl */);
+        Point wallpaperToCropSurface = WallpaperCropUtils.calculateCenterPosition(
+                scaledWallpaperSize, defaultCropSurfaceSize, false /* alignStart */,
+                false /* isRtl */);
+
+        expectedWallpaperScrollX = wallpaperToCropSurface.x + cropSurfaceToScreen.x;
+        expectedWallpaperScrollY = wallpaperToCropSurface.y + cropSurfaceToScreen.y;
+
+        // ZoomView should be scrolled in X and Y directions such that the crop surface is centered
+        // relative to the wallpaper and the screen is centered (and aligned left) relative to the
+        // crop surface.
+        assertEquals(expectedWallpaperScrollX, fullResImageView.getScrollX());
+        assertEquals(expectedWallpaperScrollY, fullResImageView.getScrollY());
+    }
+
+    @Test
+    public void testSetWallpaper_TemporarilyLocksScreenOrientation() throws Throwable {
+        launchActivityIntentWithMockWallpaper();
+        PreviewActivity activity = mActivityRule.getActivity();
+
+        assertFalse(activity.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+
+        onView(withId(R.id.preview_attribution_pane_set_wallpaper_button)).perform(click());
+
+        // Destination dialog is shown; click "Home screen".
+        onView(withText(R.string.set_wallpaper_home_screen_destination)).perform(click());
+
+        assertEquals(ActivityInfo.SCREEN_ORIENTATION_LOCKED, activity.getRequestedOrientation());
+
+        // Finish setting the wallpaper to check that the screen orientation is no longer locked.
+        finishSettingWallpaper();
+
+        assertNotEquals(activity.getRequestedOrientation(), ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+    }
+    @Test
+    public void testShowsWallpaperAttribution() {
+        launchActivityIntentWithMockWallpaper();
+        PreviewActivity activity = mActivityRule.getActivity();
+
+        TextView titleView = activity.findViewById(R.id.preview_attribution_pane_title);
+        assertEquals("Title", titleView.getText());
+
+        TextView subtitle1View = activity.findViewById(R.id.preview_attribution_pane_subtitle1);
+        assertEquals("Subtitle 1", subtitle1View.getText());
+
+        TextView subtitle2View = activity.findViewById(R.id.preview_attribution_pane_subtitle2);
+        assertEquals("Subtitle 2", subtitle2View.getText());
+    }
+
+    /**
+     * Tests that if there was a failure decoding the wallpaper bitmap, then the activity shows an
+     * informative error dialog with an "OK" button, when clicked finishes the activity.
+     */
+    @Test
+    public void testLoadWallpaper_Failed_ShowsErrorDialog() {
+        // Simulate a corrupted asset that fails to perform decoding operations.
+        mMockWallpaper.corruptAssets();
+        launchActivityIntentWithMockWallpaper();
+
+        onView(withText(R.string.load_wallpaper_error_message)).check(matches(isDisplayed()));
+
+        onView(withText(android.R.string.ok)).perform(click());
+
+        assertTrue(mActivityRule.getActivity().isFinishing());
+    }
+
+    /**
+     * Tests that the explore button is not visible, even if there is an action URL present, if
+     * there is no activity on the device which can handle such an explore action.
+     */
+    @Test
+    public void testNoActionViewHandler_ExploreButtonNotVisible() {
+        mExploreIntentChecker.setViewHandlerExists(false);
+
+        launchActivityIntentWithMockWallpaper();
+        onView(withId(R.id.preview_attribution_pane_explore_button)).check(
+                matches(not(isDisplayed())));
+    }
+}
diff --git a/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java b/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
new file mode 100644
index 0000000..d91c16a
--- /dev/null
+++ b/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
@@ -0,0 +1,496 @@
+/*
+ * 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.individual;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+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.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;
+import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static junit.framework.TestCase.assertFalse;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation.ActivityResult;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.widget.FrameLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.contrib.RecyclerViewActions;
+import androidx.test.espresso.intent.Intents;
+import androidx.test.filters.MediumTest;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.wallpaper.R;
+import com.android.wallpaper.config.Flags;
+import com.android.wallpaper.model.Category;
+import com.android.wallpaper.model.PickerIntentFactory;
+import com.android.wallpaper.model.WallpaperInfo;
+import com.android.wallpaper.model.WallpaperRotationInitializer;
+import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
+import com.android.wallpaper.module.FormFactorChecker;
+import com.android.wallpaper.module.Injector;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.testing.TestCategoryProvider;
+import com.android.wallpaper.testing.TestFormFactorChecker;
+import com.android.wallpaper.testing.TestInjector;
+import com.android.wallpaper.testing.TestUserEventLogger;
+import com.android.wallpaper.testing.TestWallpaperCategory;
+import com.android.wallpaper.testing.TestWallpaperInfo;
+import com.android.wallpaper.testing.TestWallpaperRotationInitializer;
+
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link IndividualPickerActivity}.
+ */
+@RunWith(AndroidJUnit4ClassRunner.class)
+@MediumTest
+public class IndividualPickerActivityTest {
+
+    private static final String EXTRA_WALLPAPER_INFO =
+            "com.android.wallpaper.picker.wallpaper_info";
+    private static final TestWallpaperInfo sWallpaperInfo1 = new TestWallpaperInfo(
+            TestWallpaperInfo.COLOR_BLACK, "test-wallpaper-1");
+    private static final TestWallpaperInfo sWallpaperInfo2 = new TestWallpaperInfo(
+            TestWallpaperInfo.COLOR_BLACK, "test-wallpaper-2");
+    private static final TestWallpaperInfo sWallpaperInfo3 = new TestWallpaperInfo(
+            TestWallpaperInfo.COLOR_BLACK, "test-wallpaper-3");
+
+    private TestCategoryProvider mTestCategoryProvider;
+
+    private TestFormFactorChecker mTestFormFactorChecker;
+    private Injector mInjector;
+
+    private TestWallpaperCategory mTestCategory;
+
+    @Rule
+    public ActivityTestRule<IndividualPickerActivity> mActivityRule =
+            new ActivityTestRule<>(IndividualPickerActivity.class, false, false);
+
+    @Before
+    public void setUp() {
+        Intents.init();
+
+        mInjector = new TestInjector();
+        InjectorProvider.setInjector(mInjector);
+
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mTestFormFactorChecker = (TestFormFactorChecker) mInjector.getFormFactorChecker(context);
+        mTestCategoryProvider = (TestCategoryProvider) mInjector.getCategoryProvider(context);
+
+        sWallpaperInfo1.setAttributions(Arrays.asList(
+                "Attribution 0", "Attribution 1", "Attribution 2"));
+    }
+
+    @After
+    public void tearDown() {
+        Intents.release();
+        mActivityRule.finishActivity();
+    }
+
+    private IndividualPickerActivity getActivity() {
+        return mActivityRule.getActivity();
+    }
+
+    private void setUpFragmentForTesting() {
+        IndividualPickerFragment fragment = (IndividualPickerFragment)
+                getActivity().getSupportFragmentManager().findFragmentById(R.id.fragment_container);
+        fragment.setTestingMode(true);
+    }
+
+    private void setActivityWithMockWallpapers(boolean isRotationEnabled,
+            @RotationInitializationState int rotationState) {
+        sWallpaperInfo1.setCollectionId("collection");
+
+        ArrayList<WallpaperInfo> wallpapers = new ArrayList<>();
+        wallpapers.add(sWallpaperInfo1);
+        wallpapers.add(sWallpaperInfo2);
+        wallpapers.add(sWallpaperInfo3);
+
+        mTestCategory = new TestWallpaperCategory(
+                "Test category", "collection", wallpapers, 0 /* priority */);
+        mTestCategory.setIsRotationEnabled(isRotationEnabled);
+        mTestCategory.setRotationInitializationState(rotationState);
+
+        List<Category> testCategories = mTestCategoryProvider.getTestCategories();
+        testCategories.set(0, mTestCategory);
+
+        PickerIntentFactory intentFactory =
+                new IndividualPickerActivity.IndividualPickerActivityIntentFactory();
+        Intent intent = intentFactory.newIntent(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                mTestCategory.getCollectionId());
+        mActivityRule.launchActivity(intent);
+    }
+
+    @Test
+    public void testDrawsTilesForProvidedWallpapers() {
+        setActivityWithMockWallpapers(false /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        IndividualPickerActivity activity = getActivity();
+
+        RecyclerView recyclerView = activity.findViewById(R.id.wallpaper_grid);
+
+        // There are only three wallpapers in the category, so the grid should only have three
+        // items.
+        assertNotNull(recyclerView.findViewHolderForAdapterPosition(0));
+        assertNotNull(recyclerView.findViewHolderForAdapterPosition(1));
+        assertNotNull(recyclerView.findViewHolderForAdapterPosition(2));
+        assertNull(recyclerView.findViewHolderForAdapterPosition(3));
+    }
+
+    @Test
+    public void testClickTile_Mobile_LaunchesPreviewActivityWithWallpaper() {
+        mTestFormFactorChecker.setFormFactor(FormFactorChecker.FORM_FACTOR_MOBILE);
+
+        setActivityWithMockWallpapers(false /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        onView(withId(R.id.wallpaper_grid)).perform(
+                RecyclerViewActions.actionOnItemAtPosition(0, click()));
+        intended(allOf(
+                hasComponent(hasClassName("com.android.wallpaper.picker.PreviewActivity")),
+                hasExtra(equalTo(EXTRA_WALLPAPER_INFO), equalTo(sWallpaperInfo1))));
+
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        TestUserEventLogger eventLogger = (TestUserEventLogger) mInjector.getUserEventLogger(
+                context);
+        assertEquals(1, eventLogger.getNumIndividualWallpaperSelectedEvents());
+        assertEquals(sWallpaperInfo1.getCollectionId(context), eventLogger.getLastCollectionId());
+    }
+
+    /**
+     * Tests that the static daily rotation tile (with flag dynamicStartRotationTileEnabled=false)
+     * has a background of the light blue accent color and says "Tap to turn on".
+     */
+    @Test
+    public void testRotationEnabled_StaticDailyRotationTile() {
+        Flags.dynamicStartRotationTileEnabled = false;
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        onView(withText(R.string.daily_refresh_tile_title)).check(matches(isDisplayed()));
+        onView(withText(R.string.daily_refresh_tile_subtitle)).check(matches(isDisplayed()));
+
+        // Check that the background color of the "daily refresh" tile is the blue accent color.
+        FrameLayout rotationTile = getActivity().findViewById(R.id.daily_refresh);
+        int backgroundColor = ((ColorDrawable) rotationTile.getBackground()).getColor();
+        assertEquals(getActivity().getResources().getColor(R.color.accent_color),
+                backgroundColor);
+    }
+
+    /**
+     * Tests that when rotation is enabled and the rotation for this category is already in effect
+     * on both home & lock screens, then the "start rotation" tile reads "Home & Lock".
+     */
+    @Test
+    public void testRotationEnabled_RotationInitializedHomeAndLock() {
+        Flags.dynamicStartRotationTileEnabled = true;
+
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK);
+        getActivity();
+
+        onView(withText(R.string.daily_refresh_tile_title)).check(matches(isDisplayed()));
+        onView(withText(R.string.home_and_lock_short_label)).check(matches(isDisplayed()));
+    }
+
+    /**
+     * Tests that when rotation is enabled and the rotation is aleady for this category is already
+     * in
+     * effect on the home-screen only, then the "start rotation" tile reads "Home screen".
+     */
+    @Test
+    public void testRotationEnabled_RotationInitializedHomeScreenOnly() {
+        Flags.dynamicStartRotationTileEnabled = true;
+
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_HOME_ONLY);
+        getActivity();
+
+        onView(withText(R.string.daily_refresh_tile_title)).check(matches(isDisplayed()));
+        onView(withText(R.string.home_screen_message)).check(matches(isDisplayed()));
+    }
+
+    /**
+     * Tests that after the IndividualPickerActivity loads, if the state of the current category's
+     * rotation changes while the activity is restarted, then the UI for the "start rotation" tile
+     * changes to reflect that new state.
+     */
+    @Test
+    public void testActivityRestarted_RotationStateChanges_StartRotationTileUpdates()
+            throws Throwable {
+        Flags.dynamicStartRotationTileEnabled = true;
+
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_HOME_ONLY);
+
+        onView(withText(R.string.daily_refresh_tile_title)).check(matches(isDisplayed()));
+        onView(withText(R.string.home_screen_message)).check(matches(isDisplayed()));
+
+        // Now change the rotation initialization state such that the tile should say
+        // "Tap to turn on" after the activity resumes.
+        setUpFragmentForTesting();
+        TestWallpaperRotationInitializer
+                testWPRotationInitializer = (TestWallpaperRotationInitializer)
+                mTestCategory.getWallpaperRotationInitializer();
+        testWPRotationInitializer.setRotationInitializationState(
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+
+        // Restart the activity.
+        IndividualPickerActivity activity = getActivity();
+        mActivityRule.runOnUiThread(activity::recreate);
+
+        onView(withText(R.string.daily_refresh_tile_title)).check(matches(isDisplayed()));
+        onView(withText(R.string.daily_refresh_tile_subtitle)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testRotationDisabled_DoesNotRenderDailyRefreshTile() {
+        setActivityWithMockWallpapers(false /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        onView(withText(R.string.daily_refresh_tile_title)).check(doesNotExist());
+        onView(withText(R.string.daily_refresh_tile_subtitle)).check(doesNotExist());
+    }
+
+    @Test
+    public void testClickDailyRefreshTile_ShowsStartRotationDialog() {
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        onView(withId(R.id.daily_refresh)).perform(click());
+
+        onView(withId(R.id.start_rotation_wifi_only_checkbox))
+                .check(matches(isDisplayed()));
+        // WiFi-only option should be checked by default.
+        onView(withId(R.id.start_rotation_wifi_only_checkbox))
+                .check(matches(isChecked()));
+    }
+
+    @Test
+    public void testShowStartRotationDialog_WifiOnly_ClickOK_StartsRotation() throws Throwable {
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        setUpFragmentForTesting();
+        TestWallpaperRotationInitializer
+                testWPRotationInitializer = (TestWallpaperRotationInitializer)
+                mTestCategory.getWallpaperRotationInitializer();
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        // Mock out the intent and response for the live wallpaper preview.
+        Matcher<Intent> expectedIntent = hasAction(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
+        intending(expectedIntent).respondWith(new ActivityResult(Activity.RESULT_OK, null));
+
+        onView(withId(R.id.daily_refresh)).perform(click());
+
+        onView(withText(android.R.string.ok)).perform(click());
+        mActivityRule.runOnUiThread(() -> {
+            testWPRotationInitializer.finishDownloadingFirstWallpaper(true /* isSuccessful */);
+            assertTrue(testWPRotationInitializer.isRotationInitialized());
+            assertTrue(testWPRotationInitializer.isWifiOnly());
+
+            // The activity should finish if starting a rotation was successful.
+            assertTrue(getActivity().isFinishing());
+        });
+    }
+
+    @Test
+    public void testShowStartRotationDialog_WifiOnly_ClickOK_Fails_ShowsErrorDialog() {
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        setUpFragmentForTesting();
+        TestWallpaperRotationInitializer
+                testWPRotationInitializer = (TestWallpaperRotationInitializer)
+                mTestCategory.getWallpaperRotationInitializer();
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        onView(withId(R.id.daily_refresh)).perform(click());
+        onView(withText(android.R.string.ok)).perform(click());
+
+        testWPRotationInitializer.finishDownloadingFirstWallpaper(false /* isSuccessful */);
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        // Error dialog should be shown with retry option.
+        onView(withText(R.string.start_rotation_error_message)).check(matches(isDisplayed()));
+        onView(withText(R.string.try_again)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testStartRotation_WifiOnly_FailOnce_Retry() throws Throwable {
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        setUpFragmentForTesting();
+        TestWallpaperRotationInitializer
+                testWPRotationInitializer = (TestWallpaperRotationInitializer)
+                mTestCategory.getWallpaperRotationInitializer();
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        onView(withId(R.id.daily_refresh)).perform(click());
+        onView(withText(android.R.string.ok)).perform(click());
+
+        testWPRotationInitializer.finishDownloadingFirstWallpaper(false /* isSuccessful */);
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        // Click try again to retry.
+        onView(withText(R.string.try_again)).perform(click());
+
+        mActivityRule.runOnUiThread(() -> {
+            testWPRotationInitializer.finishDownloadingFirstWallpaper(true /* isSuccessful */);
+            assertTrue(testWPRotationInitializer.isRotationInitialized());
+            assertTrue(testWPRotationInitializer.isWifiOnly());
+        });
+    }
+
+    @Test
+    public void testShowStartRotationDialog_TurnOffWifiOnly_ClickOK_StartsRotation()
+            throws Throwable {
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        setUpFragmentForTesting();
+        TestWallpaperRotationInitializer
+                testWPRotationInitializer = (TestWallpaperRotationInitializer)
+                mTestCategory.getWallpaperRotationInitializer();
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        onView(withId(R.id.daily_refresh)).perform(click());
+        // Click on WiFi-only option to toggle it off.
+        onView(withId(R.id.start_rotation_wifi_only_checkbox)).perform(click());
+        onView(withText(android.R.string.ok)).perform(click());
+
+        mActivityRule.runOnUiThread(() -> {
+            testWPRotationInitializer.finishDownloadingFirstWallpaper(true /* isSuccessful */);
+            assertTrue(testWPRotationInitializer.isRotationInitialized());
+            assertFalse(testWPRotationInitializer.isWifiOnly());
+        });
+    }
+
+    @Test
+    public void testStartRotation_WifiOnly_FailOnce_Retry_ShouldStillHaveWifiTurnedOff()
+            throws Throwable {
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        setUpFragmentForTesting();
+        TestWallpaperRotationInitializer
+                testWPRotationInitializer = (TestWallpaperRotationInitializer)
+                mTestCategory.getWallpaperRotationInitializer();
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        onView(withId(R.id.daily_refresh)).perform(click());
+        // Click on WiFi-only option to toggle it off.
+        onView(withId(R.id.start_rotation_wifi_only_checkbox)).perform(click());
+        onView(withText(android.R.string.ok)).perform(click());
+
+        testWPRotationInitializer.finishDownloadingFirstWallpaper(false /* isSuccessful */);
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        // Click try again to retry.
+        onView(withText(R.string.try_again)).perform(click());
+
+        mActivityRule.runOnUiThread(() -> {
+            testWPRotationInitializer.finishDownloadingFirstWallpaper(true /* isSuccessful */);
+            assertTrue(testWPRotationInitializer.isRotationInitialized());
+            assertFalse(testWPRotationInitializer.isWifiOnly());
+        });
+    }
+
+    @Test
+    public void testShowStartRotationDialog_ClickCancel_DismissesDialog() {
+        setActivityWithMockWallpapers(true /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        getActivity();
+
+        setUpFragmentForTesting();
+        TestWallpaperRotationInitializer
+                testWPRotationInitializer = (TestWallpaperRotationInitializer)
+                mTestCategory.getWallpaperRotationInitializer();
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+
+        onView(withId(R.id.daily_refresh)).perform(click());
+        onView(withId(R.id.start_rotation_wifi_only_checkbox)).check(matches(isDisplayed()));
+        // WiFi-only option should be checked by default.
+        onView(withId(R.id.start_rotation_wifi_only_checkbox)).check(matches(isChecked()));
+
+        // Click "Cancel" to dismiss the dialog.
+        onView(withText(android.R.string.cancel)).perform(click());
+
+        // Rotation was not initialized and dialog is no longer visible.
+        assertFalse(testWPRotationInitializer.isRotationInitialized());
+        onView(withId(R.id.start_rotation_wifi_only_checkbox)).check(doesNotExist());
+    }
+
+    /**
+     * Tests that when no title is present, a wallpaper tile's content description attribute is
+     * set to the first attribution.
+     */
+    @Test
+    public void testWallpaperHasContentDescriptionFromAttribution() {
+        setActivityWithMockWallpapers(false /* isRotationEnabled */,
+                WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+        IndividualPickerActivity activity = getActivity();
+
+        RecyclerView recyclerView = activity.findViewById(R.id.wallpaper_grid);
+
+        RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(0);
+        assertEquals("Attribution 0", holder.itemView.findViewById(R.id.tile)
+                .getContentDescription());
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestAlarmManagerWrapper.java b/tests/src/com/android/wallpaper/testing/TestAlarmManagerWrapper.java
new file mode 100644
index 0000000..c9bbdd2
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestAlarmManagerWrapper.java
@@ -0,0 +1,71 @@
+/*
+ * 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.testing;
+
+import android.app.PendingIntent;
+
+import com.android.wallpaper.module.AlarmManagerWrapper;
+
+/**
+ * Mock of {@link AlarmManagerWrapper}.
+ */
+public class TestAlarmManagerWrapper implements AlarmManagerWrapper {
+
+    private int mExactAlarmSetCount;
+    private int mInexactAlarmSetCount;
+    private int mAlarmCanceledCount;
+
+    private long mLastInexactTriggerAtMillis;
+
+    @Override
+    public void set(int type, long triggerAtMillis, PendingIntent operation) {
+        mExactAlarmSetCount++;
+    }
+
+    @Override
+    public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
+            PendingIntent operation) {
+        mExactAlarmSetCount++;
+    }
+
+    @Override
+    public void setInexactRepeating(int type, long triggerAtMillis, long intervalMillis,
+            PendingIntent operation) {
+        mInexactAlarmSetCount++;
+        mLastInexactTriggerAtMillis = triggerAtMillis;
+    }
+
+    @Override
+    public void cancel(PendingIntent operation) {
+        mAlarmCanceledCount++;
+    }
+
+    public int getExactAlarmSetCount() {
+        return mExactAlarmSetCount;
+    }
+
+    public int getInexactAlarmSetCount() {
+        return mInexactAlarmSetCount;
+    }
+
+    public int getAlarmCanceledCount() {
+        return mAlarmCanceledCount;
+    }
+
+    public long getLastInexactTriggerAtMillis() {
+        return mLastInexactTriggerAtMillis;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestAsset.java b/tests/src/com/android/wallpaper/testing/TestAsset.java
new file mode 100644
index 0000000..368919a
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestAsset.java
@@ -0,0 +1,99 @@
+/*
+ * 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.testing;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+
+import com.android.wallpaper.asset.Asset;
+
+
+/**
+ * Test implementation of Asset which blocks on Bitmap decoding operations.
+ */
+public final class TestAsset extends Asset {
+
+    private Bitmap mBitmap;
+    private boolean mIsCorrupt;
+
+    /**
+     * Constructs an asset underpinned by a 1x1 bitmap uniquely identifiable by the given pixel
+     * color.
+     *
+     * @param pixelColor Color of the asset's single pixel.
+     * @param isCorrupt  Whether or not the asset is corrupt and fails to validly decode bitmaps and
+     *                   dimensions.
+     */
+    public TestAsset(int pixelColor, boolean isCorrupt) {
+        mIsCorrupt = isCorrupt;
+
+        if (!mIsCorrupt) {
+            mBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+            mBitmap.setPixel(0, 0, pixelColor);
+        } else {
+            mBitmap = null;
+        }
+    }
+
+    @Override
+    public void decodeBitmap(int targetWidth, int targetHeight, BitmapReceiver receiver) {
+        receiver.onBitmapDecoded(mBitmap);
+    }
+
+    @Override
+    public void decodeBitmapRegion(Rect unused, int targetWidth, int targetHeight,
+            BitmapReceiver receiver) {
+        receiver.onBitmapDecoded(mBitmap);
+    }
+
+    @Override
+    public void decodeRawDimensions(Activity unused, DimensionsReceiver receiver) {
+        receiver.onDimensionsDecoded(mIsCorrupt ? null : new Point(1, 1));
+    }
+
+    @Override
+    public boolean supportsTiling() {
+        return false;
+    }
+
+    @Override
+    public void loadDrawableWithTransition(
+            Context context,
+            ImageView imageView,
+            int transitionDurationMillis,
+            @Nullable DrawableLoadedListener drawableLoadedListener,
+            int placeholderColor) {
+        if (drawableLoadedListener != null) {
+            drawableLoadedListener.onDrawableLoaded();
+        }
+    }
+
+    /** Returns the bitmap synchronously. Convenience method for tests. */
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        mBitmap = bitmap;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestBitmapCropper.java b/tests/src/com/android/wallpaper/testing/TestBitmapCropper.java
new file mode 100644
index 0000000..7daeeab
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestBitmapCropper.java
@@ -0,0 +1,58 @@
+/*
+ * 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.testing;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.asset.Asset.BitmapReceiver;
+import com.android.wallpaper.module.BitmapCropper;
+
+/**
+ * Test double for BitmapCropper.
+ */
+public class TestBitmapCropper implements BitmapCropper {
+
+    private boolean mFailNextCall;
+
+    public TestBitmapCropper() {
+        mFailNextCall = false;
+    }
+
+    @Override
+    public void cropAndScaleBitmap(Asset asset, float scale, Rect cropRect,
+            Callback callback) {
+        if (mFailNextCall) {
+            callback.onError(null /* throwable */);
+            return;
+        }
+        // Crop rect in pixels of source image.
+        Rect scaledCropRect = new Rect(
+                Math.round((float) cropRect.left / scale),
+                Math.round((float) cropRect.top / scale),
+                Math.round((float) cropRect.right / scale),
+                Math.round((float) cropRect.bottom / scale));
+
+        asset.decodeBitmapRegion(scaledCropRect, cropRect.width(), cropRect.height(),
+                new BitmapReceiver() {
+                    @Override
+                    public void onBitmapDecoded(Bitmap bitmap) {
+                        callback.onBitmapCropped(bitmap);
+                    }
+                });
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestCategoryProvider.java b/tests/src/com/android/wallpaper/testing/TestCategoryProvider.java
new file mode 100644
index 0000000..310840c
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestCategoryProvider.java
@@ -0,0 +1,94 @@
+/*
+ * 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.testing;
+
+import android.os.Handler;
+
+import com.android.wallpaper.model.Category;
+import com.android.wallpaper.model.CategoryProvider;
+import com.android.wallpaper.model.CategoryReceiver;
+import com.android.wallpaper.model.ImageCategory;
+import com.android.wallpaper.model.WallpaperInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test implementation of {@link CategoryProvider}.
+ */
+public class TestCategoryProvider implements CategoryProvider {
+    private final List<Category> mCategories;
+
+    public TestCategoryProvider() {
+        Category category1 = new ImageCategory(
+                "My photos" /* title */,
+                "image_wallpapers" /* collection */,
+                0 /* priority */);
+
+        ArrayList<WallpaperInfo> wallpapers = new ArrayList<>();
+        WallpaperInfo wallpaperInfo = new com.android.wallpaper.testing.TestWallpaperInfo(0);
+        wallpapers.add(wallpaperInfo);
+        Category category2 = new com.android.wallpaper.testing.TestWallpaperCategory(
+                "Test category", "init_collection", wallpapers,
+                1 /* priority */);
+
+        mCategories = new ArrayList<>();
+        mCategories.add(category1);
+        mCategories.add(category2);
+    }
+
+    @Override
+    public void fetchCategories(CategoryReceiver receiver, boolean forceRefresh) {
+        // Mimic real behavior by fetching asynchronously.
+        new Handler().post(new Runnable() {
+            @Override
+            public void run() {
+                List<Category> categories = getTestCategories();
+                for (Category category : categories) {
+                    receiver.onCategoryReceived(category);
+                }
+                receiver.doneFetchingCategories();
+            }
+        });
+    }
+
+    @Override
+    public int getSize() {
+        return mCategories == null ? 0 : mCategories.size();
+    }
+
+    @Override
+    public Category getCategory(int index) {
+        return mCategories == null ? null : mCategories.get(index);
+    }
+
+    @Override
+    public Category getCategory(String collectionId) {
+        Category category;
+        for (int i = 0; i < mCategories.size(); i++) {
+            category = mCategories.get(i);
+            if (category.getCollectionId().equals(collectionId)) {
+                return category;
+            }
+        }
+        return null;
+    }
+
+    /** Returns a list of test Category objects used by this TestCategoryProvider. */
+    public List<Category> getTestCategories() {
+        return mCategories;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestCurrentWallpaperInfoFactory.java b/tests/src/com/android/wallpaper/testing/TestCurrentWallpaperInfoFactory.java
new file mode 100644
index 0000000..3e8c665
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestCurrentWallpaperInfoFactory.java
@@ -0,0 +1,70 @@
+/*
+ * 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.testing;
+
+import android.content.Context;
+
+import com.android.wallpaper.compat.BuildCompat;
+import com.android.wallpaper.model.WallpaperInfo;
+import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.WallpaperRefresher;
+
+import java.util.List;
+
+/**
+ * Test double of {@link CurrentWallpaperInfoFactory}.
+ */
+public class TestCurrentWallpaperInfoFactory implements CurrentWallpaperInfoFactory {
+
+    private WallpaperRefresher mRefresher;
+
+    public TestCurrentWallpaperInfoFactory(Context context) {
+        mRefresher = InjectorProvider.getInjector().getWallpaperRefresher(
+                context.getApplicationContext());
+    }
+
+    @Override
+    public void createCurrentWallpaperInfos(final WallpaperInfoCallback callback,
+            boolean forceRefresh) {
+        mRefresher.refresh((homeWallpaperMetadata, lockWallpaperMetadata, presentationMode) -> {
+
+            WallpaperInfo homeWallpaper = createTestWallpaperInfo(
+                    homeWallpaperMetadata.getAttributions(),
+                    homeWallpaperMetadata.getActionUrl(),
+                    homeWallpaperMetadata.getCollectionId());
+
+            WallpaperInfo lockWallpaper = null;
+            if (lockWallpaperMetadata != null && BuildCompat.isAtLeastN()) {
+                lockWallpaper = createTestWallpaperInfo(
+                        lockWallpaperMetadata.getAttributions(),
+                        lockWallpaperMetadata.getActionUrl(),
+                        lockWallpaperMetadata.getCollectionId());
+            }
+
+            callback.onWallpaperInfoCreated(homeWallpaper, lockWallpaper, presentationMode);
+        });
+    }
+
+    private static WallpaperInfo createTestWallpaperInfo(List<String> attributions,
+            String actionUrl, String collectionId) {
+        TestWallpaperInfo wallpaper = new TestWallpaperInfo(TestWallpaperInfo.COLOR_BLACK);
+        wallpaper.setAttributions(attributions);
+        wallpaper.setActionUrl(actionUrl);
+        wallpaper.setCollectionId(collectionId);
+        return wallpaper;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestExploreIntentChecker.java b/tests/src/com/android/wallpaper/testing/TestExploreIntentChecker.java
new file mode 100644
index 0000000..dc1f1a8
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestExploreIntentChecker.java
@@ -0,0 +1,44 @@
+/*
+ * 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.testing;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import com.android.wallpaper.module.ExploreIntentChecker;
+
+/**
+ * Test implementation of ExploreIntentChecker.
+ */
+public class TestExploreIntentChecker implements ExploreIntentChecker {
+
+    private boolean mViewHandlerExists;
+
+    public TestExploreIntentChecker() {
+        // True by default.
+        mViewHandlerExists = true;
+    }
+
+    @Override
+    public void fetchValidActionViewIntent(Uri uri, IntentReceiver receiver) {
+        Intent intent = mViewHandlerExists ? new Intent(Intent.ACTION_VIEW, uri) : null;
+        receiver.onIntentReceived(intent);
+    }
+
+    public void setViewHandlerExists(boolean exists) {
+        mViewHandlerExists = exists;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestFormFactorChecker.java b/tests/src/com/android/wallpaper/testing/TestFormFactorChecker.java
new file mode 100644
index 0000000..ba77172
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestFormFactorChecker.java
@@ -0,0 +1,41 @@
+/*
+ * 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.testing;
+
+import com.android.wallpaper.module.FormFactorChecker;
+
+/**
+ * Test implementation of {@code FormFactorChecker}.
+ */
+public class TestFormFactorChecker implements FormFactorChecker {
+
+    @FormFactor
+    private int mFormFactor;
+
+    public TestFormFactorChecker() {
+        mFormFactor = FORM_FACTOR_MOBILE;
+    }
+
+    @FormFactor
+    @Override
+    public int getFormFactor() {
+        return mFormFactor;
+    }
+
+    public void setFormFactor(@FormFactor int formFactor) {
+        mFormFactor = formFactor;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestInjector.java b/tests/src/com/android/wallpaper/testing/TestInjector.java
new file mode 100644
index 0000000..0da755e
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestInjector.java
@@ -0,0 +1,244 @@
+/*
+ * 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.testing;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.wallpaper.compat.WallpaperManagerCompat;
+import com.android.wallpaper.model.CategoryProvider;
+import com.android.wallpaper.model.WallpaperInfo;
+import com.android.wallpaper.module.AlarmManagerWrapper;
+import com.android.wallpaper.module.BitmapCropper;
+import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
+import com.android.wallpaper.module.DefaultLiveWallpaperInfoFactory;
+import com.android.wallpaper.module.DrawableLayerResolver;
+import com.android.wallpaper.module.ExploreIntentChecker;
+import com.android.wallpaper.module.FormFactorChecker;
+import com.android.wallpaper.module.Injector;
+import com.android.wallpaper.module.LiveWallpaperInfoFactory;
+import com.android.wallpaper.module.LoggingOptInStatusProvider;
+import com.android.wallpaper.module.NetworkStatusNotifier;
+import com.android.wallpaper.module.PackageStatusNotifier;
+import com.android.wallpaper.module.PartnerProvider;
+import com.android.wallpaper.module.SystemFeatureChecker;
+import com.android.wallpaper.module.UserEventLogger;
+import com.android.wallpaper.module.WallpaperPersister;
+import com.android.wallpaper.module.WallpaperPreferences;
+import com.android.wallpaper.module.WallpaperRefresher;
+import com.android.wallpaper.module.WallpaperRotationRefresher;
+import com.android.wallpaper.monitor.PerformanceMonitor;
+import com.android.wallpaper.network.Requester;
+import com.android.wallpaper.picker.ImagePreviewFragment;
+import com.android.wallpaper.picker.individual.IndividualPickerFragment;
+
+/**
+ * Test implementation of the dependency injector.
+ */
+public class TestInjector implements Injector {
+
+    private BitmapCropper mBitmapCropper;
+    private CategoryProvider mCategoryProvider;
+    private PartnerProvider mPartnerProvider;
+    private WallpaperPreferences mPrefs;
+    private WallpaperPersister mWallpaperPersister;
+    private WallpaperRefresher mWallpaperRefresher;
+    private Requester mRequester;
+    private WallpaperManagerCompat mWallpaperManagerCompat;
+    private CurrentWallpaperInfoFactory mCurrentWallpaperInfoFactory;
+    private NetworkStatusNotifier mNetworkStatusNotifier;
+    private AlarmManagerWrapper mAlarmManagerWrapper;
+    private UserEventLogger mUserEventLogger;
+    private ExploreIntentChecker mExploreIntentChecker;
+    private SystemFeatureChecker mSystemFeatureChecker;
+    private FormFactorChecker mFormFactorChecker;
+    private WallpaperRotationRefresher mWallpaperRotationRefresher;
+    private PerformanceMonitor mPerformanceMonitor;
+    private LoggingOptInStatusProvider mLoggingOptInStatusProvider;
+
+    @Override
+    public BitmapCropper getBitmapCropper() {
+        if (mBitmapCropper == null) {
+            mBitmapCropper = new com.android.wallpaper.testing.TestBitmapCropper();
+        }
+        return mBitmapCropper;
+    }
+
+    @Override
+    public CategoryProvider getCategoryProvider(Context context) {
+        if (mCategoryProvider == null) {
+            mCategoryProvider = new TestCategoryProvider();
+        }
+        return mCategoryProvider;
+    }
+
+    @Override
+    public PartnerProvider getPartnerProvider(Context context) {
+        if (mPartnerProvider == null) {
+            mPartnerProvider = new TestPartnerProvider();
+        }
+        return mPartnerProvider;
+    }
+
+    @Override
+    public WallpaperPreferences getPreferences(Context context) {
+        if (mPrefs == null) {
+            mPrefs = new TestWallpaperPreferences();
+        }
+        return mPrefs;
+    }
+
+    @Override
+    public WallpaperPersister getWallpaperPersister(Context context) {
+        if (mWallpaperPersister == null) {
+            mWallpaperPersister = new TestWallpaperPersister(context.getApplicationContext());
+        }
+        return mWallpaperPersister;
+    }
+
+    @Override
+    public WallpaperRefresher getWallpaperRefresher(Context context) {
+        if (mWallpaperRefresher == null) {
+            mWallpaperRefresher = new TestWallpaperRefresher(context.getApplicationContext());
+        }
+        return mWallpaperRefresher;
+    }
+
+    @Override
+    public Requester getRequester(Context unused) {
+        return null;
+    }
+
+    @Override
+    public WallpaperManagerCompat getWallpaperManagerCompat(Context context) {
+        if (mWallpaperManagerCompat == null) {
+            mWallpaperManagerCompat = new com.android.wallpaper.testing.TestWallpaperManagerCompat(
+                    context.getApplicationContext());
+        }
+        return mWallpaperManagerCompat;
+    }
+
+    @Override
+    public CurrentWallpaperInfoFactory getCurrentWallpaperFactory(Context context) {
+        if (mCurrentWallpaperInfoFactory == null) {
+            mCurrentWallpaperInfoFactory =
+                    new TestCurrentWallpaperInfoFactory(context.getApplicationContext());
+        }
+        return mCurrentWallpaperInfoFactory;
+    }
+
+    @Override
+    public LoggingOptInStatusProvider getLoggingOptInStatusProvider(Context context) {
+        if (mLoggingOptInStatusProvider == null) {
+            mLoggingOptInStatusProvider = new TestLoggingOptInStatusProvider();
+        }
+        return mLoggingOptInStatusProvider;
+    }
+
+    @Override
+    public NetworkStatusNotifier getNetworkStatusNotifier(Context context) {
+        if (mNetworkStatusNotifier == null) {
+            mNetworkStatusNotifier = new TestNetworkStatusNotifier();
+        }
+        return mNetworkStatusNotifier;
+    }
+
+    @Override
+    public AlarmManagerWrapper getAlarmManagerWrapper(Context unused) {
+        if (mAlarmManagerWrapper == null) {
+            mAlarmManagerWrapper = new TestAlarmManagerWrapper();
+        }
+        return mAlarmManagerWrapper;
+    }
+
+    @Override
+    public UserEventLogger getUserEventLogger(Context unused) {
+        if (mUserEventLogger == null) {
+            mUserEventLogger = new com.android.wallpaper.testing.TestUserEventLogger();
+        }
+        return mUserEventLogger;
+    }
+
+    @Override
+    public ExploreIntentChecker getExploreIntentChecker(Context unused) {
+        if (mExploreIntentChecker == null) {
+            mExploreIntentChecker = new TestExploreIntentChecker();
+        }
+        return mExploreIntentChecker;
+    }
+
+    @Override
+    public SystemFeatureChecker getSystemFeatureChecker() {
+        if (mSystemFeatureChecker == null) {
+            mSystemFeatureChecker = new com.android.wallpaper.testing.TestSystemFeatureChecker();
+        }
+        return mSystemFeatureChecker;
+    }
+
+    @Override
+    public FormFactorChecker getFormFactorChecker(Context unused) {
+        if (mFormFactorChecker == null) {
+            mFormFactorChecker = new TestFormFactorChecker();
+        }
+        return mFormFactorChecker;
+    }
+
+    @Override
+    public WallpaperRotationRefresher getWallpaperRotationRefresher() {
+        if (mWallpaperRotationRefresher == null) {
+            mWallpaperRotationRefresher = (context, listener) -> {
+                // Not implemented
+                listener.onError();
+            };
+        }
+        return mWallpaperRotationRefresher;
+    }
+
+    @Override
+    public Fragment getPreviewFragment(Context context, WallpaperInfo wallpaperInfo, int mode,
+            boolean testingModeEnabled) {
+        return ImagePreviewFragment.newInstance(wallpaperInfo, mode, testingModeEnabled);
+    }
+
+    @Override
+    public PackageStatusNotifier getPackageStatusNotifier(Context context) {
+        return null;
+    }
+
+    @Override
+    public IndividualPickerFragment getIndividualPickerFragment(String collectionId) {
+        return IndividualPickerFragment.newInstance(collectionId);
+    }
+
+    @Override
+    public LiveWallpaperInfoFactory getLiveWallpaperInfoFactory(Context context) {
+        return new DefaultLiveWallpaperInfoFactory();
+    }
+
+    @Override
+    public DrawableLayerResolver getDrawableLayerResolver() {
+        return null;
+    }
+
+    @Override
+    public PerformanceMonitor getPerformanceMonitor() {
+        if (mPerformanceMonitor == null) {
+            mPerformanceMonitor = new TestPerformanceMonitor();
+        }
+        return mPerformanceMonitor;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestLoggingOptInStatusProvider.java b/tests/src/com/android/wallpaper/testing/TestLoggingOptInStatusProvider.java
new file mode 100644
index 0000000..f4434b1
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestLoggingOptInStatusProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.testing;
+
+import com.android.wallpaper.module.LoggingOptInStatusProvider;
+
+/** Test implementation of {@link LoggingOptInStatusProvider}. */
+public class TestLoggingOptInStatusProvider implements LoggingOptInStatusProvider {
+
+    @Override
+    public void fetchOptInValue(OptInValueReceiver receiver) {
+        receiver.onOptInValueReady(true /* optedIn */);
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestNetworkStatusNotifier.java b/tests/src/com/android/wallpaper/testing/TestNetworkStatusNotifier.java
new file mode 100644
index 0000000..b09ee43
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestNetworkStatusNotifier.java
@@ -0,0 +1,61 @@
+/*
+ * 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.testing;
+
+
+import com.android.wallpaper.module.NetworkStatusNotifier;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test implementation of {@link NetworkStatusNotifier} which enables clients to manually notify
+ * listeners of a network status change.
+ */
+public class TestNetworkStatusNotifier implements NetworkStatusNotifier {
+
+    private List<Listener> mListeners;
+    @NetworkStatus
+    private int mNetworkStatus;
+
+    public TestNetworkStatusNotifier() {
+        mListeners = new ArrayList<>();
+        mNetworkStatus = NETWORK_CONNECTED;
+    }
+
+    @Override
+    public int getNetworkStatus() {
+        return mNetworkStatus;
+    }
+
+    @Override
+    public void registerListener(Listener listener) {
+        mListeners.add(listener);
+        listener.onNetworkChanged(mNetworkStatus);
+    }
+
+    @Override
+    public void unregisterListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    public void setAndNotifyNetworkStatus(@NetworkStatus int networkStatus) {
+        mNetworkStatus = networkStatus;
+        for (Listener listener : mListeners) {
+            listener.onNetworkChanged(mNetworkStatus);
+        }
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestPartnerProvider.java b/tests/src/com/android/wallpaper/testing/TestPartnerProvider.java
new file mode 100644
index 0000000..fbdd0af
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestPartnerProvider.java
@@ -0,0 +1,58 @@
+/*
+ * 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.testing;
+
+import android.content.res.Resources;
+
+import com.android.wallpaper.module.PartnerProvider;
+
+import java.io.File;
+
+/**
+ * Test implementation for PartnerProvider.
+ */
+public class TestPartnerProvider implements PartnerProvider {
+    private File mLegacyWallpaperDirectory;
+
+    @Override
+    public Resources getResources() {
+        return null;
+    }
+
+    @Override
+    public File getLegacyWallpaperDirectory() {
+        return mLegacyWallpaperDirectory;
+    }
+
+    /**
+     * Sets the File to be returned by subsequent calls to getLegacyWallpaperDirectory().
+     *
+     * @param dir The legacy wallpaper directory.
+     */
+    public void setLegacyWallpaperDirectory(File dir) {
+        mLegacyWallpaperDirectory = dir;
+    }
+
+    @Override
+    public String getPackageName() {
+        return null;
+    }
+
+    @Override
+    public boolean shouldHideDefaultWallpaper() {
+        return false;
+    }
+}
diff --git a/src/com/android/wallpaper/module/LiveWallpaperStatusChecker.java b/tests/src/com/android/wallpaper/testing/TestPerformanceMonitor.java
old mode 100755
new mode 100644
similarity index 60%
copy from src/com/android/wallpaper/module/LiveWallpaperStatusChecker.java
copy to tests/src/com/android/wallpaper/testing/TestPerformanceMonitor.java
index 67d87f8..7fe3ade
--- a/src/com/android/wallpaper/module/LiveWallpaperStatusChecker.java
+++ b/tests/src/com/android/wallpaper/testing/TestPerformanceMonitor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -13,15 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wallpaper.module;
+package com.android.wallpaper.testing;
+
+import com.android.wallpaper.monitor.PerformanceMonitor;
 
 /**
- * Reads whether the application's live wallpaper service is set to the device.
+ * No-op performance monitor for test.
  */
-public interface LiveWallpaperStatusChecker {
+public class TestPerformanceMonitor implements PerformanceMonitor {
 
-    /**
-     * Returns whether the live wallpaper for daily wallpapers is set to the device.
-     */
-    boolean isNoBackupImageWallpaperSet();
+    @Override
+    public void recordFullResPreviewLoadedMemorySnapshot() {
+    }
 }
diff --git a/tests/src/com/android/wallpaper/testing/TestSystemFeatureChecker.java b/tests/src/com/android/wallpaper/testing/TestSystemFeatureChecker.java
new file mode 100644
index 0000000..775e693
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestSystemFeatureChecker.java
@@ -0,0 +1,41 @@
+/*
+ * 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.testing;
+
+import android.content.Context;
+
+import com.android.wallpaper.module.SystemFeatureChecker;
+
+/**
+ * Test implementation of {@link SystemFeatureChecker}.
+ */
+public class TestSystemFeatureChecker implements SystemFeatureChecker {
+
+    private boolean mHasTelephony;
+
+    public TestSystemFeatureChecker() {
+        mHasTelephony = true;
+    }
+
+    @Override
+    public boolean hasTelephony(Context context) {
+        return mHasTelephony;
+    }
+
+    public void setHasTelephony(boolean hasTelephony) {
+        mHasTelephony = hasTelephony;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestUserEventLogger.java b/tests/src/com/android/wallpaper/testing/TestUserEventLogger.java
new file mode 100644
index 0000000..9f0489e
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestUserEventLogger.java
@@ -0,0 +1,289 @@
+/*
+ * 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.testing;
+
+import com.android.wallpaper.module.UserEventLogger;
+import com.android.wallpaper.module.WallpaperPersister.WallpaperPosition;
+
+/**
+ * Test implementation of {@link UserEventLogger}.
+ */
+public class TestUserEventLogger implements UserEventLogger {
+
+    private int mNumDailyRefreshTurnedOnEvents;
+    private int mNumCurrentWallpaperPreviewedEvents;
+    private int mNumActionClickedEvents;
+    private int mNumIndividualWallpaperSelectedEvents;
+    private int mNumCategorySelectedEvents;
+    private int mNumWallpaperSetEvents;
+    private int mNumWallpaperSetResultEvents;
+    private String mLastCollectionId;
+    private String mLastWallpaperId;
+    @WallpaperSetResult
+    private int mLastWallpaperSetResult;
+    private int mLastDailyRotationHour;
+    private int mNum1DayActiveLogs;
+    private int mNum7DayActiveLogs;
+    private int mNum14DayActiveLogs;
+    private int mNum28DayActiveLogs;
+    private int mLastDailyWallpaperRotationStatus;
+    private int mNumDaysDailyRotationFailed;
+    private int mNumDaysDailyRotationNotAttempted;
+    private int mLastDailyWallpaperUpdateResult;
+    private int mStandalonePreviewLaunches;
+    private int mNumRestores;
+    @WallpaperPosition
+    private int mWallpaperPosition;
+
+    public TestUserEventLogger() {
+        mLastDailyRotationHour = -1;
+        mLastDailyWallpaperRotationStatus = -1;
+        mNumDaysDailyRotationFailed = -1;
+        mNumDaysDailyRotationNotAttempted = -1;
+    }
+
+    @Override
+    public void logResumed(boolean provisioned, boolean wallpaper) {
+
+    }
+
+    @Override
+    public void logStopped() {
+
+    }
+
+    @Override
+    public void logAppLaunched() {
+        // Do nothing.
+    }
+
+    @Override
+    public void logDailyRefreshTurnedOn() {
+        mNumDailyRefreshTurnedOnEvents++;
+    }
+
+    public int getNumDailyRefreshTurnedOnEvents() {
+        return mNumDailyRefreshTurnedOnEvents;
+    }
+
+    @Override
+    public void logCurrentWallpaperPreviewed() {
+        mNumCurrentWallpaperPreviewedEvents++;
+    }
+
+    @Override
+    public void logActionClicked(String collectionId, int actionLabelResId) {
+        mNumActionClickedEvents++;
+        mLastCollectionId = collectionId;
+    }
+
+    public int getNumCurrentWallpaperPreviewedEvents() {
+        return mNumCurrentWallpaperPreviewedEvents;
+    }
+
+    public int getNumActionClickedEvents() {
+        return mNumActionClickedEvents;
+    }
+
+    @Override
+    public void logIndividualWallpaperSelected(String collectionId) {
+        mNumIndividualWallpaperSelectedEvents++;
+        mLastCollectionId = collectionId;
+    }
+
+    public int getNumIndividualWallpaperSelectedEvents() {
+        return mNumIndividualWallpaperSelectedEvents;
+    }
+
+    @Override
+    public void logCategorySelected(String collectionId) {
+        mNumCategorySelectedEvents++;
+        mLastCollectionId = collectionId;
+    }
+
+    public int getNumCategorySelectedEvents() {
+        return mNumCategorySelectedEvents;
+    }
+
+    @Override
+    public void logWallpaperSet(String collectionId, String wallpaperId) {
+        mNumWallpaperSetEvents++;
+        mLastCollectionId = collectionId;
+        mLastWallpaperId = wallpaperId;
+    }
+
+    @Override
+    public void logWallpaperSetResult(@WallpaperSetResult int result) {
+        mNumWallpaperSetResultEvents++;
+        mLastWallpaperSetResult = result;
+    }
+
+    @Override
+    public void logWallpaperSetFailureReason(@WallpaperSetFailureReason int reason) {
+        // No-op
+    }
+
+
+    @Override
+    public void logNumDailyWallpaperRotationsInLastWeek() {
+        // No-op
+    }
+
+    @Override
+    public void logNumDailyWallpaperRotationsPreviousDay() {
+        // No-op
+    }
+
+    @Override
+    public void logDailyWallpaperRotationHour(int hour) {
+        mLastDailyRotationHour = hour;
+    }
+
+    @Override
+    public void logDailyWallpaperDecodes(boolean decodes) {
+        // No-op
+    }
+
+    @Override
+    public void logRefreshDailyWallpaperButtonClicked() {
+        // No-op
+    }
+
+    @Override
+    public void logDailyWallpaperRotationStatus(int status) {
+        mLastDailyWallpaperRotationStatus = status;
+    }
+
+    @Override
+    public void logDailyWallpaperSetNextWallpaperResult(@DailyWallpaperUpdateResult int result) {
+        mLastDailyWallpaperUpdateResult = result;
+    }
+
+    @Override
+    public void logDailyWallpaperSetNextWallpaperCrash(@DailyWallpaperUpdateCrash int crash) {
+        // No-op
+    }
+
+    @Override
+    public void logNumDaysDailyRotationFailed(int days) {
+        mNumDaysDailyRotationFailed = days;
+    }
+
+    @Override
+    public void logDailyWallpaperMetadataRequestFailure(
+            @DailyWallpaperMetadataFailureReason int reason) {
+        // No-op
+    }
+
+    @Override
+    public void logNumDaysDailyRotationNotAttempted(int days) {
+        mNumDaysDailyRotationNotAttempted = days;
+    }
+
+    @Override
+    public void logStandalonePreviewLaunched() {
+        mStandalonePreviewLaunches++;
+    }
+
+    @Override
+    public void logStandalonePreviewImageUriHasReadPermission(boolean isReadPermissionGranted) {
+        // No-op
+    }
+
+    @Override
+    public void logStandalonePreviewStorageDialogApproved(boolean isApproved) {
+        // No-op
+    }
+
+    @Override
+    public void logWallpaperPresentationMode() {
+        // No-op
+    }
+
+    @Override
+    public void logRestored() {
+        mNumRestores++;
+    }
+
+    public int getNumWallpaperSetEvents() {
+        return mNumWallpaperSetEvents;
+    }
+
+    public String getLastCollectionId() {
+        return mLastCollectionId;
+    }
+
+    public String getLastWallpaperId() {
+        return mLastWallpaperId;
+    }
+
+    public int getNumWallpaperSetResultEvents() {
+        return mNumWallpaperSetResultEvents;
+    }
+
+    @WallpaperSetResult
+    public int getLastWallpaperSetResult() {
+        return mLastWallpaperSetResult;
+    }
+
+    public int getLastDailyRotationHour() {
+        return mLastDailyRotationHour;
+    }
+
+    public int getNum1DayActiveLogs() {
+        return mNum1DayActiveLogs;
+    }
+
+    public int getNum7DayActiveLogs() {
+        return mNum7DayActiveLogs;
+    }
+
+    public int getNum14DayActiveLogs() {
+        return mNum14DayActiveLogs;
+    }
+
+    public int getNum28DayActiveLogs() {
+        return mNum28DayActiveLogs;
+    }
+
+    public int getLastDailyWallpaperRotationStatus() {
+        return mLastDailyWallpaperRotationStatus;
+    }
+
+    public int getNumDaysDailyRotationFailed() {
+        return mNumDaysDailyRotationFailed;
+    }
+
+    public int getNumDaysDailyRotationNotAttempted() {
+        return mNumDaysDailyRotationNotAttempted;
+    }
+
+    public int getLastDailyWallpaperUpdateResult() {
+        return mLastDailyWallpaperUpdateResult;
+    }
+
+    public int getStandalonePreviewLaunches() {
+        return mStandalonePreviewLaunches;
+    }
+
+    public int getNumRestores() {
+        return mNumRestores;
+    }
+
+    public int getWallpaperPosition() {
+        return mWallpaperPosition;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestWallpaperCategory.java b/tests/src/com/android/wallpaper/testing/TestWallpaperCategory.java
new file mode 100644
index 0000000..c69ecf8
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestWallpaperCategory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.testing;
+
+
+import com.android.wallpaper.model.WallpaperCategory;
+import com.android.wallpaper.model.WallpaperInfo;
+import com.android.wallpaper.model.WallpaperRotationInitializer;
+import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
+
+import java.util.List;
+
+/**
+ * Test-only subclass of {@link WallpaperCategory} which can be configured to provide a test double
+ * {@link WallpaperRotationInitializer}.
+ */
+public class TestWallpaperCategory extends WallpaperCategory {
+    private boolean mIsRotationEnabled;
+    private TestWallpaperRotationInitializer mWallpaperRotationInitializer;
+
+    public TestWallpaperCategory(String title, String collectionId, List<WallpaperInfo> wallpapers,
+            int priority) {
+        super(title, collectionId, wallpapers, priority);
+        mIsRotationEnabled = false;
+        mWallpaperRotationInitializer = new TestWallpaperRotationInitializer();
+    }
+
+    @Override
+    public WallpaperRotationInitializer getWallpaperRotationInitializer() {
+        return (mIsRotationEnabled) ? mWallpaperRotationInitializer : null;
+    }
+
+    @Override
+    public List<WallpaperInfo> getMutableWallpapers() {
+        return super.getMutableWallpapers();
+    }
+
+    /** Sets whether rotation is enabled on this category. */
+    public void setIsRotationEnabled(boolean isRotationEnabled) {
+        mIsRotationEnabled = isRotationEnabled;
+    }
+
+    public void setRotationInitializationState(@RotationInitializationState int rotationState) {
+        mWallpaperRotationInitializer.setRotationInitializationState(rotationState);
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestWallpaperInfo.java b/tests/src/com/android/wallpaper/testing/TestWallpaperInfo.java
new file mode 100644
index 0000000..c0ccee6
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestWallpaperInfo.java
@@ -0,0 +1,228 @@
+/*
+ * 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.testing;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.model.InlinePreviewIntentFactory;
+import com.android.wallpaper.model.WallpaperInfo;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test model object for a wallpaper coming from local drawable resources.
+ */
+public class TestWallpaperInfo extends WallpaperInfo {
+    public static final int COLOR_BLACK = 0;
+    public static final Parcelable.Creator<TestWallpaperInfo> CREATOR =
+            new Parcelable.Creator<TestWallpaperInfo>() {
+                @Override
+                public TestWallpaperInfo createFromParcel(Parcel in) {
+                    return new TestWallpaperInfo(in);
+                }
+
+                @Override
+                public TestWallpaperInfo[] newArray(int size) {
+                    return new TestWallpaperInfo[size];
+                }
+            };
+    private int mPixelColor;
+    private TestAsset mAsset;
+    private TestAsset mThumbAsset;
+    private List<String> mAttributions;
+    private android.app.WallpaperInfo mWallpaperComponent;
+    private String mActionUrl;
+    private String mBaseImageUrl;
+    private String mCollectionId;
+    private String mWallpaperId;
+    private boolean mIsAssetCorrupt;
+    private int mBackupPermission;
+
+    /** Constructs a test WallpaperInfo object representing a 1x1 wallpaper of the given color. */
+    public TestWallpaperInfo(int pixelColor) {
+        this(pixelColor, "test-wallpaper");
+    }
+
+    /** Constructs a test WallpaperInfo object representing a 1x1 wallpaper of the given color. */
+    public TestWallpaperInfo(int pixelColor, String id) {
+        mPixelColor = pixelColor;
+        mAttributions = Arrays.asList("Test wallpaper");
+        mWallpaperComponent = null;
+        mIsAssetCorrupt = false;
+        mBackupPermission = BACKUP_ALLOWED;
+        mWallpaperId = id;
+    }
+
+    private TestWallpaperInfo(Parcel in) {
+        mPixelColor = in.readInt();
+        mAttributions = in.createStringArrayList();
+        mActionUrl = in.readString();
+        mBaseImageUrl = in.readString();
+        mCollectionId = in.readString();
+        mWallpaperId = in.readString();
+        mIsAssetCorrupt = in.readInt() == 1;
+        mBackupPermission = in.readInt();
+    }
+
+    @Override
+    public Drawable getOverlayIcon(Context context) {
+        return null;
+    }
+
+    @Override
+    public List<String> getAttributions(Context context) {
+        return mAttributions;
+    }
+
+    /**
+     * Override default "Test wallpaper" attributions for testing.
+     */
+    public void setAttributions(List<String> attributions) {
+        mAttributions = attributions;
+    }
+
+    @Override
+    public String getActionUrl(Context unused) {
+        return mActionUrl;
+    }
+
+    /** Sets the action URL for this wallpaper. */
+    public void setActionUrl(String actionUrl) {
+        mActionUrl = actionUrl;
+    }
+
+    @Override
+    public String getBaseImageUrl() {
+        return mBaseImageUrl;
+    }
+
+    /** Sets the base image URL for this wallpaper. */
+    public void setBaseImageUrl(String baseImageUrl) {
+        mBaseImageUrl = baseImageUrl;
+    }
+
+    @Override
+    public String getCollectionId(Context unused) {
+        return mCollectionId;
+    }
+
+    /** Sets the collection ID for this wallpaper. */
+    public void setCollectionId(String collectionId) {
+        mCollectionId = collectionId;
+    }
+
+    @Override
+    public String getWallpaperId() {
+        return mWallpaperId;
+    }
+
+    /** Sets the ID for this wallpaper. */
+    public void setWallpaperId(String wallpaperId) {
+        mWallpaperId = wallpaperId;
+    }
+
+    @Override
+    public Asset getAsset(Context context) {
+        if (mAsset == null) {
+            mAsset = new TestAsset(mPixelColor, mIsAssetCorrupt);
+        }
+        return mAsset;
+    }
+
+    @Override
+    public Asset getThumbAsset(Context context) {
+        if (mThumbAsset == null) {
+            mThumbAsset = new TestAsset(mPixelColor, mIsAssetCorrupt);
+        }
+        return mThumbAsset;
+    }
+
+    @Override
+    public void showPreview(Activity srcActivity,
+            InlinePreviewIntentFactory inlinePreviewIntentFactory, int requestCode) {
+        srcActivity.startActivityForResult(
+                inlinePreviewIntentFactory.newIntent(srcActivity, this), requestCode);
+    }
+
+    @Override
+    @BackupPermission
+    public int getBackupPermission() {
+        return mBackupPermission;
+    }
+
+    public void setBackupPermission(@BackupPermission int backupPermission) {
+        mBackupPermission = backupPermission;
+    }
+
+    @Override
+    public android.app.WallpaperInfo getWallpaperComponent() {
+        return mWallpaperComponent;
+    }
+
+    public void setWallpaperComponent(android.app.WallpaperInfo wallpaperComponent) {
+        mWallpaperComponent = wallpaperComponent;
+    }
+
+    /**
+     * Simulates that the {@link Asset} instances returned by calls to #getAsset and #getThumbAsset
+     * on
+     * this object are "corrupt" and will fail to perform decode operations such as #decodeBitmap,
+     * #decodeBitmapRegion, #decodeRawDimensions, etc (these methods will call their callbacks with
+     * null instead of meaningful objects).
+     */
+    public void corruptAssets() {
+        mIsAssetCorrupt = true;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        parcel.writeInt(mPixelColor);
+        parcel.writeStringList(mAttributions);
+        parcel.writeString(mActionUrl);
+        parcel.writeString(mBaseImageUrl);
+        parcel.writeString(mCollectionId);
+        parcel.writeString(mWallpaperId);
+        parcel.writeInt(mIsAssetCorrupt ? 1 : 0);
+        parcel.writeInt(mBackupPermission);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof TestWallpaperInfo) {
+            return mPixelColor == ((TestWallpaperInfo) object).mPixelColor;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mPixelColor;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestWallpaperManagerCompat.java b/tests/src/com/android/wallpaper/testing/TestWallpaperManagerCompat.java
new file mode 100644
index 0000000..69f6839
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestWallpaperManagerCompat.java
@@ -0,0 +1,135 @@
+/*
+ * 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.testing;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.wallpaper.compat.WallpaperManagerCompat;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Test double for {@link WallpaperManagerCompat}. */
+public class TestWallpaperManagerCompat extends WallpaperManagerCompat {
+    private static final String TAG = "TestWPManagerCompat";
+
+    ParcelFileDescriptor mSystemParcelFd;
+    ParcelFileDescriptor mLockParcelFd;
+    int mHomeWallpaperId = 0;
+    int mLockWallpaperId = 0;
+
+    private boolean mAllowBackup;
+    private Context mAppContext;
+    private Drawable mTestDrawable;
+
+    public TestWallpaperManagerCompat(Context appContext) {
+        mAppContext = appContext;
+        mAllowBackup = true;
+    }
+
+    @Override
+    public int setStream(InputStream stream, Rect visibleCropHint, boolean allowBackup,
+            int whichWallpaper) throws IOException {
+        mAllowBackup = allowBackup;
+        return ++mHomeWallpaperId;
+    }
+
+    @Override
+    public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup,
+            int whichWallpaper) throws IOException {
+        mAllowBackup = allowBackup;
+        return ++mLockWallpaperId;
+    }
+
+    @Override
+    public ParcelFileDescriptor getWallpaperFile(int whichWallpaper) {
+        if (whichWallpaper == WallpaperManagerCompat.FLAG_SYSTEM) {
+            return mSystemParcelFd;
+        } else if (whichWallpaper == WallpaperManagerCompat.FLAG_LOCK) {
+            return mLockParcelFd;
+        } else {
+            // :(
+            return null;
+        }
+    }
+
+    @Override
+    public Drawable getDrawable() {
+        if (mTestDrawable != null) {
+            return mTestDrawable;
+        }
+
+        // Retrieve WallpaperManager using Context#getSystemService instead of
+        // WallpaperManager#getInstance so it can be mocked out in test.
+        WallpaperManager wallpaperManager =
+                (WallpaperManager) mAppContext.getSystemService(Context.WALLPAPER_SERVICE);
+        return wallpaperManager.getDrawable();
+    }
+
+    @Override
+    public int getWallpaperId(@WallpaperLocation int whichWallpaper) {
+        switch (whichWallpaper) {
+            case WallpaperManagerCompat.FLAG_SYSTEM:
+                return mHomeWallpaperId;
+            case WallpaperManagerCompat.FLAG_LOCK:
+                return mLockWallpaperId;
+            default:
+                throw new IllegalArgumentException(
+                        "Wallpaper location must be one of FLAG_SYSTEM or "
+                                + "FLAG_LOCK but the value " + whichWallpaper + " was provided.");
+        }
+    }
+
+    public void setWallpaperFile(int whichWallpaper, ParcelFileDescriptor file) {
+        if (whichWallpaper == WallpaperManagerCompat.FLAG_SYSTEM) {
+            mSystemParcelFd = file;
+        } else if (whichWallpaper == WallpaperManagerCompat.FLAG_LOCK) {
+            mLockParcelFd = file;
+        } else {
+            Log.e(TAG, "Called setWallpaperFile without a valid distinct 'which' argument.");
+        }
+    }
+
+    public void setWallpaperId(@WallpaperLocation int whichWallpaper, int wallpaperId) {
+        switch (whichWallpaper) {
+            case WallpaperManagerCompat.FLAG_SYSTEM:
+                mHomeWallpaperId = wallpaperId;
+                break;
+            case WallpaperManagerCompat.FLAG_LOCK:
+                mLockWallpaperId = wallpaperId;
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Wallpaper location must be one of FLAG_SYSTEM or "
+                                + "FLAG_LOCK but the value " + whichWallpaper + " was provided.");
+        }
+    }
+
+    public void setDrawable(Drawable drawable) {
+        mTestDrawable = drawable;
+    }
+
+    /** Returns whether backup is allowed for the last set wallpaper. */
+    public boolean isBackupAllowed() {
+        return mAllowBackup;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestWallpaperPersister.java b/tests/src/com/android/wallpaper/testing/TestWallpaperPersister.java
new file mode 100644
index 0000000..5fc9421
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestWallpaperPersister.java
@@ -0,0 +1,223 @@
+/*
+ * 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.testing;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+import androidx.annotation.Nullable;
+
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.asset.Asset.BitmapReceiver;
+import com.android.wallpaper.model.WallpaperInfo;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.WallpaperChangedNotifier;
+import com.android.wallpaper.module.WallpaperPersister;
+import com.android.wallpaper.module.WallpaperPreferences;
+
+import java.util.List;
+
+/**
+ * Test double for {@link WallpaperPersister}.
+ */
+public class TestWallpaperPersister implements WallpaperPersister {
+
+    private Context mAppContext;
+    private WallpaperPreferences mPrefs;
+    private WallpaperChangedNotifier mWallpaperChangedNotifier;
+    private Bitmap mCurrentHomeWallpaper;
+    private Bitmap mCurrentLockWallpaper;
+    private Bitmap mPendingHomeWallpaper;
+    private Bitmap mPendingLockWallpaper;
+    private List<String> mHomeAttributions;
+    private String mHomeActionUrl;
+    @Destination
+    private int mDestination;
+    private WallpaperPersister.SetWallpaperCallback mCallback;
+    private boolean mFailNextCall;
+    private Rect mCropRect;
+    private float mScale;
+    @WallpaperPosition
+    private int mWallpaperPosition;
+
+    public TestWallpaperPersister(Context appContext) {
+        mAppContext = appContext;
+        mPrefs = InjectorProvider.getInjector().getPreferences(appContext);
+        mWallpaperChangedNotifier = WallpaperChangedNotifier.getInstance();
+
+        mCurrentHomeWallpaper = null;
+        mCurrentLockWallpaper = null;
+        mPendingHomeWallpaper = null;
+        mPendingLockWallpaper = null;
+        mFailNextCall = false;
+        mScale = -1.0f;
+    }
+
+    @Override
+    public void setIndividualWallpaper(final WallpaperInfo wallpaperInfo, Asset asset,
+            @Nullable final Rect cropRect, final float scale, final @Destination int destination,
+            final WallpaperPersister.SetWallpaperCallback callback) {
+        asset.decodeBitmap(50, 50, bitmap -> {
+            if (destination == DEST_HOME_SCREEN || destination == DEST_BOTH) {
+                mPendingHomeWallpaper = bitmap;
+                mPrefs.setHomeWallpaperAttributions(wallpaperInfo.getAttributions(mAppContext));
+                mPrefs.setWallpaperPresentationMode(
+                        WallpaperPreferences.PRESENTATION_MODE_STATIC);
+                mPrefs.setHomeWallpaperRemoteId(wallpaperInfo.getWallpaperId());
+            }
+            if (destination == DEST_LOCK_SCREEN || destination == DEST_BOTH) {
+                mPendingLockWallpaper = bitmap;
+                mPrefs.setLockWallpaperAttributions(wallpaperInfo.getAttributions(mAppContext));
+            }
+            mDestination = destination;
+            mCallback = callback;
+            mCropRect = cropRect;
+            mScale = scale;
+        });
+    }
+
+    @Override
+    public void setIndividualWallpaperWithPosition(Activity activity, WallpaperInfo wallpaper,
+            @WallpaperPosition int wallpaperPosition, SetWallpaperCallback callback) {
+        wallpaper.getAsset(activity).decodeBitmap(50, 50, new BitmapReceiver() {
+            @Override
+            public void onBitmapDecoded(@Nullable Bitmap bitmap) {
+                mPendingHomeWallpaper = bitmap;
+                mPrefs.setHomeWallpaperAttributions(wallpaper.getAttributions(mAppContext));
+                mPrefs.setHomeWallpaperBaseImageUrl(wallpaper.getBaseImageUrl());
+                mPrefs.setHomeWallpaperActionUrl(wallpaper.getActionUrl(mAppContext));
+                mPrefs.setHomeWallpaperCollectionId(wallpaper.getCollectionId(mAppContext));
+                mPrefs.setHomeWallpaperRemoteId(wallpaper.getWallpaperId());
+                mPrefs.setWallpaperPresentationMode(WallpaperPreferences.PRESENTATION_MODE_STATIC);
+                mPendingLockWallpaper = bitmap;
+                mPrefs.setLockWallpaperAttributions(wallpaper.getAttributions(mAppContext));
+
+                mDestination = WallpaperPersister.DEST_BOTH;
+                mCallback = callback;
+                mWallpaperPosition = wallpaperPosition;
+            }
+        });
+    }
+
+    @Override
+    public boolean setWallpaperInRotation(Bitmap wallpaperBitmap, List<String> attributions,
+            int actionLabelRes, int actionIconRes, String actionUrl, String collectionId) {
+        if (mFailNextCall) {
+            return false;
+        }
+
+        mCurrentHomeWallpaper = wallpaperBitmap;
+        mCurrentLockWallpaper = wallpaperBitmap;
+        mHomeAttributions = attributions;
+        mHomeActionUrl = actionUrl;
+        return true;
+    }
+
+    @Override
+    public int setWallpaperBitmapInNextRotation(Bitmap wallpaperBitmap) {
+        mCurrentHomeWallpaper = wallpaperBitmap;
+        mCurrentLockWallpaper = wallpaperBitmap;
+        return 1;
+    }
+
+    @Override
+    public boolean finalizeWallpaperForNextRotation(List<String> attributions, String actionUrl,
+            int actionLabelRes, int actionIconRes, String collectionId, int wallpaperId) {
+        mHomeAttributions = attributions;
+        mHomeActionUrl = actionUrl;
+        return true;
+    }
+
+    /** Returns mock system wallpaper bitmap. */
+    public Bitmap getCurrentHomeWallpaper() {
+        return mCurrentHomeWallpaper;
+    }
+
+    /** Returns mock lock screen wallpaper bitmap. */
+    public Bitmap getCurrentLockWallpaper() {
+        return mCurrentLockWallpaper;
+    }
+
+    /** Returns mock home attributions. */
+    public List<String> getHomeAttributions() {
+        return mHomeAttributions;
+    }
+
+    /** Returns the home wallpaper action URL. */
+    public String getHomeActionUrl() {
+        return mHomeActionUrl;
+    }
+
+    /** Returns the Destination a wallpaper was most recently set on. */
+    @Destination
+    public int getLastDestination() {
+        return mDestination;
+    }
+
+    /**
+     * Sets whether the next "set wallpaper" operation should fail or succeed.
+     */
+    public void setFailNextCall(Boolean failNextCall) {
+        mFailNextCall = failNextCall;
+    }
+
+    /**
+     * Implemented so synchronous test methods can control the completion of what would otherwise be
+     * an asynchronous operation.
+     */
+    public void finishSettingWallpaper() {
+        if (mFailNextCall) {
+            mCallback.onError(null /* throwable */);
+        } else {
+            if (mDestination == DEST_HOME_SCREEN || mDestination == DEST_BOTH) {
+                mCurrentHomeWallpaper = mPendingHomeWallpaper;
+                mPendingHomeWallpaper = null;
+            }
+            if (mDestination == DEST_LOCK_SCREEN || mDestination == DEST_BOTH) {
+                mCurrentLockWallpaper = mPendingLockWallpaper;
+                mPendingLockWallpaper = null;
+            }
+            mCallback.onSuccess();
+            mWallpaperChangedNotifier.notifyWallpaperChanged();
+        }
+    }
+
+    @Override
+    public void setWallpaperInfoInPreview(WallpaperInfo wallpaperInfo) {
+    }
+
+    @Override
+    public void onLiveWallpaperSet() {
+    }
+
+    /** Returns the last requested wallpaper bitmap scale. */
+    public float getScale() {
+        return mScale;
+    }
+
+    /** Returns the last requested wallpaper crop. */
+    public Rect getCropRect() {
+        return mCropRect;
+    }
+
+    /** Returns the last selected wallpaper position option. */
+    @WallpaperPosition
+    public int getWallpaperPosition() {
+        return mWallpaperPosition;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestWallpaperPreferences.java b/tests/src/com/android/wallpaper/testing/TestWallpaperPreferences.java
new file mode 100644
index 0000000..715e81b
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestWallpaperPreferences.java
@@ -0,0 +1,457 @@
+/*
+ * 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.testing;
+
+import androidx.annotation.Nullable;
+
+import com.android.wallpaper.module.WallpaperPreferences;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test implementation of the WallpaperPreferences interface. Just keeps prefs in memory.
+ */
+public class TestWallpaperPreferences implements WallpaperPreferences {
+
+    @PresentationMode
+    private int mWallpaperPresentationMode;
+
+    private List<String> mHomeScreenAttributions;
+    private long mHomeScreenBitmapHashCode;
+    private int mHomeWallpaperManagerId;
+    private String mHomeScreenPackageName;
+    private String mHomeActionUrl;
+    private String mHomeBaseImageUrl;
+    private String mHomeCollectionId;
+    private String mHomeWallpaperRemoteId;
+
+    private List<String> mLockScreenAttributions;
+    private long mLockScreenBitmapHashCode;
+    private int mLockWallpaperManagerId;
+    private String mLockActionUrl;
+    private String mLockCollectionId;
+
+    private List<Long> mDailyRotations;
+    private long mDailyWallpaperEnabledTimestamp;
+    private long mLastDailyLogTimestamp;
+    private long mLastAppActiveTimestamp;
+    private int mLastDailyWallpaperRotationStatus;
+    private long mLastDailyWallpaperRotationStatusTimestamp;
+    private long mLastSyncTimestamp;
+    @PendingWallpaperSetStatus
+    private int mPendingWallpaperSetStatus;
+    @PendingDailyWallpaperUpdateStatus
+    private int mPendingDailyWallpaperUpdateStatus;
+    private int mNumDaysDailyRotationFailed;
+    private int mNumDaysDailyRotationNotAttempted;
+    private int mHomeWallpaperActionLabelRes;
+    private int mHomeWallpaperActionIconRes;
+    private int mLockWallpaperActionLabelRes;
+    private int mLockWallpaperActionIconRes;
+
+    public TestWallpaperPreferences() {
+        mWallpaperPresentationMode = WallpaperPreferences.PRESENTATION_MODE_STATIC;
+        mHomeScreenAttributions = Arrays.asList("Android wallpaper");
+        mHomeScreenBitmapHashCode = 0;
+        mHomeWallpaperManagerId = 0;
+
+        mLockScreenAttributions = Arrays.asList("Android wallpaper");
+        mLockScreenBitmapHashCode = 0;
+        mLockWallpaperManagerId = 0;
+
+        mDailyRotations = new ArrayList<>();
+        mDailyWallpaperEnabledTimestamp = -1;
+        mLastDailyLogTimestamp = -1;
+        mLastDailyWallpaperRotationStatus = -1;
+        mLastDailyWallpaperRotationStatusTimestamp = 0;
+        mLastSyncTimestamp = 0;
+        mPendingWallpaperSetStatus = WALLPAPER_SET_NOT_PENDING;
+    }
+
+    @Override
+    public int getWallpaperPresentationMode() {
+        return mWallpaperPresentationMode;
+    }
+
+    @Override
+    public void setWallpaperPresentationMode(@PresentationMode int presentationMode) {
+        mWallpaperPresentationMode = presentationMode;
+    }
+
+    @Override
+    public List<String> getHomeWallpaperAttributions() {
+        return mHomeScreenAttributions;
+    }
+
+    @Override
+    public void setHomeWallpaperAttributions(List<String> attributions) {
+        mHomeScreenAttributions = attributions;
+    }
+
+    @Override
+    public String getHomeWallpaperActionUrl() {
+        return mHomeActionUrl;
+    }
+
+    @Override
+    public void setHomeWallpaperActionUrl(String actionUrl) {
+        mHomeActionUrl = actionUrl;
+    }
+
+    @Override
+    public int getHomeWallpaperActionLabelRes() {
+        return mHomeWallpaperActionLabelRes;
+    }
+
+    @Override
+    public void setHomeWallpaperActionLabelRes(int resId) {
+        mHomeWallpaperActionLabelRes = resId;
+    }
+
+    @Override
+    public int getHomeWallpaperActionIconRes() {
+        return mHomeWallpaperActionIconRes;
+    }
+
+    @Override
+    public void setHomeWallpaperActionIconRes(int resId) {
+        mHomeWallpaperActionIconRes = resId;
+    }
+
+    @Override
+    public String getHomeWallpaperBaseImageUrl() {
+        return mHomeBaseImageUrl;
+    }
+
+    @Override
+    public void setHomeWallpaperBaseImageUrl(String baseImageUrl) {
+        mHomeBaseImageUrl = baseImageUrl;
+    }
+
+    @Override
+    public String getHomeWallpaperCollectionId() {
+        return mHomeCollectionId;
+    }
+
+    @Override
+    public void setHomeWallpaperCollectionId(String collectionId) {
+        mHomeCollectionId = collectionId;
+    }
+
+    @Override
+    public String getHomeWallpaperBackingFileName() {
+        return null;
+    }
+
+    @Override
+    public void setHomeWallpaperBackingFileName(String fileName) {
+
+    }
+
+    @Override
+    public void clearHomeWallpaperMetadata() {
+        mHomeScreenAttributions = null;
+        mWallpaperPresentationMode = WallpaperPreferences.PRESENTATION_MODE_STATIC;
+        mHomeScreenBitmapHashCode = 0;
+        mHomeScreenPackageName = null;
+        mHomeWallpaperManagerId = 0;
+    }
+
+    @Override
+    public long getHomeWallpaperHashCode() {
+        return mHomeScreenBitmapHashCode;
+    }
+
+    @Override
+    public void setHomeWallpaperHashCode(long hashCode) {
+        mHomeScreenBitmapHashCode = hashCode;
+    }
+
+    @Override
+    public String getHomeWallpaperPackageName() {
+        return mHomeScreenPackageName;
+    }
+
+    @Override
+    public void setHomeWallpaperPackageName(String packageName) {
+        mHomeScreenPackageName = packageName;
+    }
+
+    @Override
+    public int getHomeWallpaperManagerId() {
+        return mHomeWallpaperManagerId;
+    }
+
+    @Override
+    public void setHomeWallpaperManagerId(int homeWallpaperId) {
+        mHomeWallpaperManagerId = homeWallpaperId;
+    }
+
+    @Override
+    public String getHomeWallpaperRemoteId() {
+        return mHomeWallpaperRemoteId;
+    }
+
+    @Override
+    public void setHomeWallpaperRemoteId(String wallpaperRemoteId) {
+        mHomeWallpaperRemoteId = wallpaperRemoteId;
+    }
+
+    @Override
+    public List<String> getLockWallpaperAttributions() {
+        return mLockScreenAttributions;
+    }
+
+    @Override
+    public void setLockWallpaperAttributions(List<String> attributions) {
+        mLockScreenAttributions = attributions;
+    }
+
+    @Override
+    public String getLockWallpaperActionUrl() {
+        return mLockActionUrl;
+    }
+
+    @Override
+    public void setLockWallpaperActionUrl(String actionUrl) {
+        mLockActionUrl = actionUrl;
+    }
+
+    @Override
+    public int getLockWallpaperActionLabelRes() {
+        return mLockWallpaperActionLabelRes;
+    }
+
+    @Override
+    public void setLockWallpaperActionLabelRes(int resId) {
+        mLockWallpaperActionLabelRes = resId;
+    }
+
+    @Override
+    public int getLockWallpaperActionIconRes() {
+        return mLockWallpaperActionIconRes;
+    }
+
+    @Override
+    public void setLockWallpaperActionIconRes(int resId) {
+        mLockWallpaperActionIconRes = resId;
+    }
+
+    @Override
+    public String getLockWallpaperCollectionId() {
+        return mLockCollectionId;
+    }
+
+    @Override
+    public void setLockWallpaperCollectionId(String collectionId) {
+        mLockCollectionId = collectionId;
+    }
+
+    @Override
+    public String getLockWallpaperBackingFileName() {
+        return null;
+    }
+
+    @Override
+    public void setLockWallpaperBackingFileName(String fileName) {
+
+    }
+
+    @Override
+    public void clearLockWallpaperMetadata() {
+        mLockScreenAttributions = null;
+        mLockScreenBitmapHashCode = 0;
+        mLockWallpaperManagerId = 0;
+    }
+
+    @Override
+    public long getLockWallpaperHashCode() {
+        return mLockScreenBitmapHashCode;
+    }
+
+    @Override
+    public void setLockWallpaperHashCode(long hashCode) {
+        mLockScreenBitmapHashCode = hashCode;
+    }
+
+    @Override
+    public int getLockWallpaperId() {
+        return mLockWallpaperManagerId;
+    }
+
+    @Override
+    public void setLockWallpaperId(int lockWallpaperId) {
+        mLockWallpaperManagerId = lockWallpaperId;
+    }
+
+    @Override
+    public void addDailyRotation(long timestamp) {
+        mDailyRotations.add(timestamp);
+    }
+
+    @Override
+    public long getLastDailyRotationTimestamp() {
+        if (mDailyRotations.size() == 0) {
+            return -1;
+        }
+
+        return mDailyRotations.get(mDailyRotations.size() - 1);
+    }
+
+    @Override
+    public List<Long> getDailyRotationsInLastWeek() {
+        return mDailyRotations;
+    }
+
+    @Nullable
+    @Override
+    public List<Long> getDailyRotationsPreviousDay() {
+        return null;
+    }
+
+    @Override
+    public long getDailyWallpaperEnabledTimestamp() {
+        return mDailyWallpaperEnabledTimestamp;
+    }
+
+    @Override
+    public void setDailyWallpaperEnabledTimestamp(long timestamp) {
+        mDailyWallpaperEnabledTimestamp = timestamp;
+    }
+
+    @Override
+    public void clearDailyRotations() {
+        mDailyRotations.clear();
+    }
+
+    @Override
+    public long getLastDailyLogTimestamp() {
+        return mLastDailyLogTimestamp;
+    }
+
+    @Override
+    public void setLastDailyLogTimestamp(long timestamp) {
+        mLastDailyLogTimestamp = timestamp;
+    }
+
+    @Override
+    public long getLastAppActiveTimestamp() {
+        return mLastAppActiveTimestamp;
+    }
+
+    @Override
+    public void setLastAppActiveTimestamp(long timestamp) {
+        mLastAppActiveTimestamp = timestamp;
+    }
+
+    @Override
+    public void setDailyWallpaperRotationStatus(int status, long timestamp) {
+        mLastDailyWallpaperRotationStatus = status;
+        mLastDailyWallpaperRotationStatusTimestamp = timestamp;
+    }
+
+    @Override
+    public int getDailyWallpaperLastRotationStatus() {
+        return mLastDailyWallpaperRotationStatus;
+    }
+
+    @Override
+    public long getDailyWallpaperLastRotationStatusTimestamp() {
+        return mLastDailyWallpaperRotationStatusTimestamp;
+    }
+
+    @Override
+    public long getLastSyncTimestamp() {
+        return mLastSyncTimestamp;
+    }
+
+    @Override
+    public void setLastSyncTimestamp(long timestamp) {
+        mLastSyncTimestamp = timestamp;
+    }
+
+    @Override
+    public void setPendingWallpaperSetStatusSync(@PendingWallpaperSetStatus int setStatus) {
+        mPendingWallpaperSetStatus = setStatus;
+    }
+
+    @Override
+    public int getPendingWallpaperSetStatus() {
+        return mPendingWallpaperSetStatus;
+    }
+
+    @Override
+    public void setPendingWallpaperSetStatus(@PendingWallpaperSetStatus int setStatus) {
+        mPendingWallpaperSetStatus = setStatus;
+    }
+
+    @Override
+    public void setPendingDailyWallpaperUpdateStatusSync(
+            @PendingDailyWallpaperUpdateStatus int updateStatus) {
+        mPendingDailyWallpaperUpdateStatus = updateStatus;
+    }
+
+    @Override
+    public int getPendingDailyWallpaperUpdateStatus() {
+        return mPendingDailyWallpaperUpdateStatus;
+    }
+
+    @Override
+    public void setPendingDailyWallpaperUpdateStatus(
+            @PendingDailyWallpaperUpdateStatus int updateStatus) {
+        mPendingDailyWallpaperUpdateStatus = updateStatus;
+    }
+
+    @Override
+    public void incrementNumDaysDailyRotationFailed() {
+        mNumDaysDailyRotationFailed++;
+    }
+
+    @Override
+    public int getNumDaysDailyRotationFailed() {
+        return mNumDaysDailyRotationFailed;
+    }
+
+    public void setNumDaysDailyRotationFailed(int days) {
+        mNumDaysDailyRotationFailed = days;
+    }
+
+    @Override
+    public void resetNumDaysDailyRotationFailed() {
+        mNumDaysDailyRotationFailed = 0;
+    }
+
+    @Override
+    public void incrementNumDaysDailyRotationNotAttempted() {
+        mNumDaysDailyRotationNotAttempted++;
+    }
+
+    @Override
+    public int getNumDaysDailyRotationNotAttempted() {
+        return mNumDaysDailyRotationNotAttempted;
+    }
+
+    public void setNumDaysDailyRotationNotAttempted(int days) {
+        mNumDaysDailyRotationNotAttempted = days;
+    }
+
+    @Override
+    public void resetNumDaysDailyRotationNotAttempted() {
+        mNumDaysDailyRotationNotAttempted = 0;
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestWallpaperRefresher.java b/tests/src/com/android/wallpaper/testing/TestWallpaperRefresher.java
new file mode 100644
index 0000000..97961fe
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestWallpaperRefresher.java
@@ -0,0 +1,81 @@
+/*
+ * 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.testing;
+
+
+import android.app.WallpaperManager;
+import android.content.Context;
+
+import com.android.wallpaper.compat.BuildCompat;
+import com.android.wallpaper.model.WallpaperMetadata;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.WallpaperPreferences;
+import com.android.wallpaper.module.WallpaperRefresher;
+
+/**
+ * Test implementation of {@link WallpaperRefresher} which simply provides whatever metadata is
+ * saved in WallpaperPreferences and the image wallpaper set to {@link WallpaperManager}.
+ */
+public class TestWallpaperRefresher implements WallpaperRefresher {
+
+    private final Context mAppContext;
+
+    /**
+     * @param context The application's context.
+     */
+    public TestWallpaperRefresher(Context context) {
+        mAppContext = context.getApplicationContext();
+    }
+
+    @Override
+    public void refresh(RefreshListener listener) {
+
+        WallpaperPreferences prefs = InjectorProvider.getInjector().getPreferences(mAppContext);
+
+        if (BuildCompat.isAtLeastN() && prefs.getLockWallpaperId() > 0) {
+            listener.onRefreshed(
+                    new WallpaperMetadata(
+                            prefs.getHomeWallpaperAttributions(),
+                            prefs.getHomeWallpaperActionUrl(),
+                            prefs.getHomeWallpaperActionLabelRes(),
+                            prefs.getHomeWallpaperActionIconRes(),
+                            prefs.getHomeWallpaperCollectionId(),
+                            prefs.getHomeWallpaperBackingFileName(),
+                            null /* wallpaperComponent */),
+                    new WallpaperMetadata(
+                            prefs.getLockWallpaperAttributions(),
+                            prefs.getLockWallpaperActionUrl(),
+                            prefs.getLockWallpaperActionLabelRes(),
+                            prefs.getLockWallpaperActionIconRes(),
+                            prefs.getLockWallpaperCollectionId(),
+                            prefs.getLockWallpaperBackingFileName(),
+                            null /* wallpaperComponent */),
+                    prefs.getWallpaperPresentationMode());
+        } else {
+            listener.onRefreshed(
+                    new WallpaperMetadata(
+                            prefs.getHomeWallpaperAttributions(),
+                            prefs.getHomeWallpaperActionUrl(),
+                            prefs.getHomeWallpaperActionLabelRes(),
+                            prefs.getHomeWallpaperActionIconRes(),
+                            prefs.getHomeWallpaperCollectionId(),
+                            prefs.getHomeWallpaperBackingFileName(),
+                            null /* wallpaperComponent */),
+                    null,
+                    prefs.getWallpaperPresentationMode());
+        }
+    }
+}
diff --git a/tests/src/com/android/wallpaper/testing/TestWallpaperRotationInitializer.java b/tests/src/com/android/wallpaper/testing/TestWallpaperRotationInitializer.java
new file mode 100644
index 0000000..9494d19
--- /dev/null
+++ b/tests/src/com/android/wallpaper/testing/TestWallpaperRotationInitializer.java
@@ -0,0 +1,133 @@
+/*
+ * 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.testing;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.wallpaper.model.WallpaperRotationInitializer;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.WallpaperPreferences;
+
+/**
+ * Test implementation of {@link WallpaperRotationInitializer}.
+ */
+public class TestWallpaperRotationInitializer implements WallpaperRotationInitializer {
+
+    private boolean mIsRotationInitialized;
+    @NetworkPreference
+    private int mNetworkPreference;
+    private Listener mListener;
+    @RotationInitializationState
+    private int mRotationInitializationState;
+
+    TestWallpaperRotationInitializer() {
+        mIsRotationInitialized = false;
+        mNetworkPreference = NETWORK_PREFERENCE_WIFI_ONLY;
+        mRotationInitializationState = WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED;
+    }
+
+    TestWallpaperRotationInitializer(@RotationInitializationState int rotationState) {
+        mIsRotationInitialized = false;
+        mNetworkPreference = NETWORK_PREFERENCE_WIFI_ONLY;
+        mRotationInitializationState = rotationState;
+    }
+
+    private TestWallpaperRotationInitializer(Parcel unused) {
+        mIsRotationInitialized = false;
+        mNetworkPreference = NETWORK_PREFERENCE_WIFI_ONLY;
+    }
+
+    @Override
+    public void setFirstWallpaperInRotation(Context context,
+            @NetworkPreference int networkPreference,
+            Listener listener) {
+        mListener = listener;
+        mNetworkPreference = networkPreference;
+    }
+
+    @Override
+    public boolean startRotation(Context appContext) {
+        WallpaperPreferences wallpaperPreferences =
+                InjectorProvider.getInjector().getPreferences(appContext);
+        wallpaperPreferences.setWallpaperPresentationMode(
+                WallpaperPreferences.PRESENTATION_MODE_ROTATING);
+        return true;
+    }
+
+    @Override
+    public void fetchRotationInitializationState(Context context, RotationStateListener listener) {
+        listener.onRotationStateReceived(mRotationInitializationState);
+    }
+
+    @Override
+    public int getRotationInitializationStateDirty(Context context) {
+        return mRotationInitializationState;
+    }
+
+    /** Sets the mocked rotation initialization state. */
+    public void setRotationInitializationState(@RotationInitializationState int rotationState) {
+        mRotationInitializationState = rotationState;
+    }
+
+    /**
+     * Simulates completion of the asynchronous task to download the first wallpaper in a rotation..
+     *
+     * @param isSuccessful Whether the first wallpaper downloaded successfully.
+     */
+    public void finishDownloadingFirstWallpaper(boolean isSuccessful) {
+        if (isSuccessful) {
+            mIsRotationInitialized = true;
+            mListener.onFirstWallpaperInRotationSet();
+        } else {
+            mIsRotationInitialized = false;
+            mListener.onError();
+        }
+    }
+
+    /** Returns whether a wallpaper rotation is initialized. */
+    public boolean isRotationInitialized() {
+        return mIsRotationInitialized;
+    }
+
+    /** Returns whether a wallpaper rotation is enabled for WiFi-only. */
+    public boolean isWifiOnly() {
+        return mNetworkPreference == NETWORK_PREFERENCE_WIFI_ONLY;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<TestWallpaperRotationInitializer> CREATOR =
+            new Parcelable.Creator<TestWallpaperRotationInitializer>() {
+                @Override
+                public TestWallpaperRotationInitializer createFromParcel(Parcel in) {
+                    return new TestWallpaperRotationInitializer(in);
+                }
+
+                @Override
+                public TestWallpaperRotationInitializer[] newArray(int size) {
+                    return new TestWallpaperRotationInitializer[size];
+                }
+            };
+}