Merge remote-tracking branch 'goog/stage-aosp-master' into HEAD
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..28a7699
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+db_files
+*.iml
+.project
+.classpath
+.settings
+project.properties
+gen/
+bin/
+.idea/
+.gradle/
+local.properties
+gradle/
+build/
+gradlew*
\ No newline at end of file
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..cc97679
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2016 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)
+
+# Standalone Wallpaper picker app
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SDK_VERSION := current
+LOCAL_PACKAGE_NAME := WallpaperPicker
+include $(BUILD_PACKAGE)
+
+
+# Static library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libWallpaperPicker
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_JAVA_LIBRARIES := android-support-v4
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..8ef44c7
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.wallpaperpicker"
+        android:versionCode="1"
+        android:versionName="1.0"
+        >
+
+    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23" />
+
+    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+
+    <application>
+
+        <activity
+            android:name="com.android.wallpaperpicker.WallpaperPickerActivity"
+            android:theme="@style/WallpaperTheme.Picker"
+            android:label="@string/pick_wallpaper"
+            android:icon="@mipmap/ic_launcher_wallpaper"
+            android:finishOnCloseSystemDialogs="true">
+            <intent-filter>
+                <action android:name="android.intent.action.SET_WALLPAPER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="com.android.wallpaperpicker.WallpaperCropActivity"
+            android:theme="@style/WallpaperTheme"
+            android:label="@string/crop_wallpaper"
+            android:icon="@mipmap/ic_launcher_wallpaper"
+            android:finishOnCloseSystemDialogs="true">
+            <intent-filter>
+                <action android:name="android.service.wallpaper.CROP_AND_SET_WALLPAPER" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/LibraryManifest.xml b/LibraryManifest.xml
new file mode 100644
index 0000000..ebf1191
--- /dev/null
+++ b/LibraryManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wallpaperpicker"
+    android:versionCode="1"
+    android:versionName="1.0"
+    />
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..ee7426b
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,42 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.5.0'
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "22.0.1"
+
+    defaultConfig {
+        minSdkVersion 16
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        debug {
+            minifyEnabled false
+        }
+    }
+    sourceSets {
+        main {
+            res.srcDirs = ['res']
+            java.srcDirs = ['src']
+            manifest.srcFile 'LibraryManifest.xml'
+        }
+    }
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile 'com.android.support:support-v4:23.1.1'
+}
diff --git a/res/anim/fade_out.xml b/res/anim/fade_out.xml
new file mode 100644
index 0000000..9ca7407
--- /dev/null
+++ b/res/anim/fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<!-- startOffset is the same as the duration of the wallpaper_enter animation. We have this delay so
+    that we don't see the wallpaper changing before fading back to the home screen. -->
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:startOffset="@android:integer/config_longAnimTime"
+    android:duration="@android:integer/config_mediumAnimTime"
+    android:fromAlpha="1"
+    android:toAlpha="0"/>
\ No newline at end of file
diff --git a/res/drawable-hdpi/ic_actionbar_accept.png b/res/drawable-hdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000..53cf687
--- /dev/null
+++ b/res/drawable-hdpi/ic_actionbar_accept.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_images.png b/res/drawable-hdpi/ic_images.png
new file mode 100644
index 0000000..15e511c
--- /dev/null
+++ b/res/drawable-hdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_shadow_bottom.9.png b/res/drawable-hdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..e80558b
--- /dev/null
+++ b/res/drawable-hdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_shadow_top.9.png b/res/drawable-hdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..7e93865
--- /dev/null
+++ b/res/drawable-hdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_actionbar_accept.png b/res/drawable-mdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000..35cda8e
--- /dev/null
+++ b/res/drawable-mdpi/ic_actionbar_accept.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_images.png b/res/drawable-mdpi/ic_images.png
new file mode 100644
index 0000000..c4a2229
--- /dev/null
+++ b/res/drawable-mdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_shadow_bottom.9.png b/res/drawable-mdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..d95787b
--- /dev/null
+++ b/res/drawable-mdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_shadow_top.9.png b/res/drawable-mdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..8da913c
--- /dev/null
+++ b/res/drawable-mdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-v21/ic_tick.xml b/res/drawable-v21/ic_tick.xml
new file mode 100644
index 0000000..5b27027
--- /dev/null
+++ b/res/drawable-v21/ic_tick.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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="48dp"
+    android:viewportHeight="48"
+    android:viewportWidth="48"
+    android:width="48dp" >
+
+    <group>
+        <path
+            android:name="tick"
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M18 32.34l-8.34-8.34-2.83 2.83 11.17 11.17 24-24-2.83-2.83z" />
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/res/drawable-v21/wallpaper_tile_fg.xml b/res/drawable-v21/wallpaper_tile_fg.xml
new file mode 100644
index 0000000..97cdcd6
--- /dev/null
+++ b/res/drawable-v21/wallpaper_tile_fg.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#66FFFFFF" >
+
+    <item
+        android:id="@android:id/mask"
+        android:drawable="@android:color/white"/>
+    <item
+        android:bottom="23.25dp"
+        android:left="29.25dp"
+        android:right="29.25dp"
+        android:top="23.25dp">
+        <selector>
+            <item
+                android:drawable="@drawable/ic_tick"
+                android:state_selected="true"/>
+            <item
+                android:drawable="@drawable/ic_tick"
+                android:state_checked="true"/>
+        </selector>
+    </item>
+
+</ripple>
\ No newline at end of file
diff --git a/res/drawable-xhdpi/ic_actionbar_accept.png b/res/drawable-xhdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000..b52dc37
--- /dev/null
+++ b/res/drawable-xhdpi/ic_actionbar_accept.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_images.png b/res/drawable-xhdpi/ic_images.png
new file mode 100644
index 0000000..4974792
--- /dev/null
+++ b/res/drawable-xhdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_shadow_bottom.9.png b/res/drawable-xhdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..81571f3
--- /dev/null
+++ b/res/drawable-xhdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_shadow_top.9.png b/res/drawable-xhdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..8503a59
--- /dev/null
+++ b/res/drawable-xhdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_actionbar_accept.png b/res/drawable-xxhdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000..d9ad51c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_actionbar_accept.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_images.png b/res/drawable-xxhdpi/ic_images.png
new file mode 100644
index 0000000..c8b9f75
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_shadow_bottom.9.png b/res/drawable-xxhdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..55250f0
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_shadow_top.9.png b/res/drawable-xxhdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..3f22633
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_images.png b/res/drawable-xxxhdpi/ic_images.png
new file mode 100644
index 0000000..a19002e
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_images.png
Binary files differ
diff --git a/res/drawable/wallpaper_tile_fg.xml b/res/drawable/wallpaper_tile_fg.xml
new file mode 100644
index 0000000..c66fa50
--- /dev/null
+++ b/res/drawable/wallpaper_tile_fg.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" >
+        <shape>
+            <stroke
+                android:width="2dp"
+                android:color="#FFFFFFFF" />
+            <solid android:color="#33FFFFFF"/>
+        </shape>
+    </item>
+    <item android:state_focused="true" >
+        <shape>
+            <stroke
+                android:width="2dp"
+                android:color="#FFFFFFFF" />
+        </shape>
+    </item>
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="#33FFFFFF"/>
+        </shape>
+    </item>
+    <item android:state_selected="true" >
+        <shape>
+            <stroke
+                android:width="2dp"
+                android:color="#FFFFFFFF" />
+            <solid android:color="#33FFFFFF"/>
+        </shape>
+    </item>
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/layout/actionbar_set_wallpaper.xml b/res/layout/actionbar_set_wallpaper.xml
new file mode 100644
index 0000000..459f221
--- /dev/null
+++ b/res/layout/actionbar_set_wallpaper.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.wallpaperpicker.AlphaDisableableButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/ActionBarSetWallpaperStyle"
+    android:id="@+id/set_wallpaper_button"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingRight="20dp"
+    android:drawableLeft="@drawable/ic_actionbar_accept"
+    android:drawablePadding="8dp"
+    android:gravity="start|center_vertical"
+    android:text="@string/wallpaper_instructions"
+    android:enabled="false" />
diff --git a/res/layout/wallpaper_cropper.xml b/res/layout/wallpaper_cropper.xml
new file mode 100644
index 0000000..ebc617c
--- /dev/null
+++ b/res/layout/wallpaper_cropper.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, 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.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.wallpaperpicker.CropView
+        android:id="@+id/cropView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <ProgressBar
+        android:id="@+id/loading"
+        style="?android:attr/progressBarStyleLarge"
+        android:visibility="invisible"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:indeterminate="true"
+        android:indeterminateOnly="true"
+        android:background="@android:color/transparent" />
+</RelativeLayout>
diff --git a/res/layout/wallpaper_picker.xml b/res/layout/wallpaper_picker.xml
new file mode 100644
index 0000000..2ec737d
--- /dev/null
+++ b/res/layout/wallpaper_picker.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, 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"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <com.android.wallpaperpicker.CropView
+        android:id="@+id/cropView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <ProgressBar
+        android:id="@+id/loading"
+        style="?android:attr/progressBarStyleLarge"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:indeterminate="true"
+        android:indeterminateOnly="true"
+        android:visibility="invisible" />
+
+    <LinearLayout
+        android:id="@+id/wallpaper_strip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+        android:fitsSystemWindows="true"
+        android:orientation="vertical" >
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="2dp"
+            android:background="@drawable/tile_shadow_top" />
+
+        <HorizontalScrollView
+            android:id="@+id/wallpaper_scroll_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" >
+
+            <LinearLayout
+                android:id="@+id/master_wallpaper_list"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal" >
+
+                <LinearLayout
+                    android:id="@+id/wallpaper_list"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal" />
+
+                <LinearLayout
+                    android:id="@+id/live_wallpaper_list"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal" />
+
+                <LinearLayout
+                    android:id="@+id/third_party_wallpaper_list"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal" />
+            </LinearLayout>
+        </HorizontalScrollView>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="2dp"
+            android:background="@drawable/tile_shadow_bottom" />
+    </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/wallpaper_picker_image_picker_item.xml b/res/layout/wallpaper_picker_image_picker_item.xml
new file mode 100644
index 0000000..50bcea8
--- /dev/null
+++ b/res/layout/wallpaper_picker_image_picker_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.wallpaperpicker.CheckableFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/wallpaperThumbnailWidth"
+    android:layout_height="@dimen/wallpaperThumbnailHeight"
+    android:focusable="true"
+    android:clickable="true"
+    android:foreground="@drawable/wallpaper_tile_fg">
+    <ImageView
+        android:id="@+id/wallpaper_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/wallpaper_picker_translucent_gray"
+        android:scaleType="centerCrop" />
+    <TextView
+        android:id="@+id/wallpaper_item_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:gravity="center"
+        android:layout_gravity="center"
+        android:text="@string/pick_image"
+        android:drawableTop="@drawable/ic_images"
+        android:drawablePadding="4dp"
+        android:textColor="@android:color/white"/>
+</com.android.wallpaperpicker.CheckableFrameLayout>
diff --git a/res/layout/wallpaper_picker_item.xml b/res/layout/wallpaper_picker_item.xml
new file mode 100644
index 0000000..676e6bf
--- /dev/null
+++ b/res/layout/wallpaper_picker_item.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.wallpaperpicker.CheckableFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/wallpaperThumbnailWidth"
+    android:layout_height="@dimen/wallpaperThumbnailHeight"
+    android:focusable="true"
+    android:clickable="true"
+    android:foreground="@drawable/wallpaper_tile_fg">
+    <ImageView
+        android:id="@+id/wallpaper_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerCrop" />
+</com.android.wallpaperpicker.CheckableFrameLayout>
diff --git a/res/layout/wallpaper_picker_live_wallpaper_item.xml b/res/layout/wallpaper_picker_live_wallpaper_item.xml
new file mode 100644
index 0000000..694f02a
--- /dev/null
+++ b/res/layout/wallpaper_picker_live_wallpaper_item.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.wallpaperpicker.CheckableFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/wallpaperThumbnailWidth"
+    android:layout_height="@dimen/wallpaperThumbnailHeight"
+    android:focusable="true"
+    android:clickable="true"
+    android:foreground="@drawable/wallpaper_tile_fg">
+    <ImageView
+        android:id="@+id/wallpaper_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:background="@android:color/black"
+        android:scaleType="centerCrop" />
+    <ImageView
+        android:id="@+id/wallpaper_icon"
+        android:layout_width="@dimen/wallpaperItemIconSize"
+        android:layout_height="@dimen/wallpaperItemIconSize"
+        android:layout_gravity="center"
+        android:visibility="gone" />
+    <TextView
+        android:id="@+id/wallpaper_item_label"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:gravity="center"
+        android:padding="4dp"
+        android:layout_gravity="bottom"
+        android:background="@color/wallpaper_picker_translucent_gray"
+        android:textColor="@android:color/white"/>
+</com.android.wallpaperpicker.CheckableFrameLayout>
diff --git a/res/layout/wallpaper_picker_third_party_item.xml b/res/layout/wallpaper_picker_third_party_item.xml
new file mode 100644
index 0000000..806db36
--- /dev/null
+++ b/res/layout/wallpaper_picker_third_party_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.wallpaperpicker.CheckableFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/wallpaperThumbnailWidth"
+    android:layout_height="@dimen/wallpaperThumbnailHeight"
+    android:focusable="true"
+    android:clickable="true"
+    android:foreground="@drawable/wallpaper_tile_fg">
+    <ImageView
+        android:id="@+id/wallpaper_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/wallpaper_picker_translucent_gray"
+        android:scaleType="centerCrop" />
+    <TextView
+        android:id="@+id/wallpaper_item_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:gravity="center"
+        android:layout_gravity="center"
+        android:drawablePadding="4dp"
+        android:textColor="@android:color/white"/>
+</com.android.wallpaperpicker.CheckableFrameLayout>
diff --git a/res/menu/cab_delete_wallpapers.xml b/res/menu/cab_delete_wallpapers.xml
new file mode 100644
index 0000000..38ac5c4
--- /dev/null
+++ b/res/menu/cab_delete_wallpapers.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, 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.
+*/
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+  <item
+     android:id="@+id/menu_delete"
+     android:title="@string/wallpaper_delete"
+     android:showAsAction="always"
+     android:icon="@android:drawable/ic_menu_delete" />
+</menu>
diff --git a/res/mipmap-hdpi/ic_launcher_wallpaper.png b/res/mipmap-hdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..affee85
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_wallpaper.png b/res/mipmap-mdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..cb4443b
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_wallpaper.png b/res/mipmap-xhdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..60f8dce
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_wallpaper.png b/res/mipmap-xxhdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..023fb58
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000..bc87fe1
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Stel muurpapier"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Kon nie prent laai nie"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Kon nie prent as muurpapier laai nie"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Kon nie prent as muurpapier stel nie"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d gekies"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d gekies"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d gekies"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Muurpapier %1$d van %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Het <xliff:g id="LABEL">%1$s</xliff:g> gekies"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Vee uit"</string>
+    <string name="pick_image" msgid="3189640419551368385">"My foto\'s"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Muurpapiere"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Snoei muurpapier"</string>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000..23bf538
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"ልጣፍ  አዘጋጅ"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"ምስሉን መጫን አልተቻለም"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"ምስሉን እንደ ግድግዳ ወረቀት መጫን አልተቻለም"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"ምስሉን እንደ ግድግዳ ወረቀት ማዘጋጀት አልተቻለም"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ተመርጧል"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ተመርጧል"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ተመርጧል"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"ልጣፍ  %1$d የ%2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> ተመርጧል"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"ሰርዝ"</string>
+    <string name="pick_image" msgid="3189640419551368385">"የእኔ ፎቶዎች"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"የግድግዳ ወረቀቶች"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ልጣፍ  ይከርክሙ"</string>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000..db6834c
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"تعيين الخلفية"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"تعذر تحميل الصورة"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"تعذر تحميل الصورة كخلفية"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"تعذر تعيين الصورة كخلفية"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"‏تم تحديد %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"‏تم تحديد %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"‏تم تحديد %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"‏الخلفية %1$d من %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"تم تحديد <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"حذف"</string>
+    <string name="pick_image" msgid="3189640419551368385">"صوري"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"الخلفيات"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"اقتصاص الخلفية"</string>
+</resources>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
new file mode 100644
index 0000000..c42d711
--- /dev/null
+++ b/res/values-az-rAZ/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Divar kağı seçin"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Şəkli yükləmək alınmadı"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Şəkli divar kağızı olaraq yükləmək alınmadı"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Şəkli divar kağızı olaraq quraşdırmaq alınmadı"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d seçilib"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d seçilib"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d seçilib"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Divar kağızı %1$d of %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> seçilib"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Sil"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Fotolarım"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Divar kağızları"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Divar kağızını kəsin"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000..bf2a83b
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Задаване на тапета"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Изображението не можа да бъде заредено"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Изображението не можа да бъде заредено като тапет"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Изображението не можа да бъде зададено като тапет"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Избрахте %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Избрахте %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Избрахте %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Тапет %1$d от %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Избрахте <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Изтриване"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Моите снимки"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Тапети"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Подрязване на тапета"</string>
+</resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
new file mode 100644
index 0000000..1c4d3d4
--- /dev/null
+++ b/res/values-bn-rBD/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"ওয়ালপেপার সেট করুন"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"চিত্র লোড করা যায়নি"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"ওয়ালপেপার হিসাবে চিত্র লোড করা যায়নি"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"ওয়ালপেপার হিসাবে চিত্র সেট করা যায়নি"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$dটি নির্বাচন করা হয়েছে"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$dটি নির্বাচন করা হয়েছে"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$dটি নির্বাচন করা হয়েছে"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$dটির মধ্যে %1$dটি ওয়ালপেপার"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> নির্বাচন করা হয়েছে"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"মুছুন"</string>
+    <string name="pick_image" msgid="3189640419551368385">"আমার ফটো"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"ওয়ালপেপারগুলি"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ওয়ালপেপার কাটছাঁট করুন"</string>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000..9533ae9
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Estableix el fons de pantalla"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"No s\'ha pogut carregar la imatge."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"No s\'ha pogut carregar la imatge com a fons de pantalla."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"No s\'ha pogut definir la imatge com a fons de pantalla"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Seleccionats: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Seleccionats: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Seleccionats: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Fons de pantalla %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"S\'ha seleccionat <xliff:g id="LABEL">%1$s</xliff:g>."</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Suprimeix"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Les meves fotos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fons de pantalla"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Retallar fons de pantalla"</string>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..aab8cc8
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Nastavit jako tapetu"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Obrázek nelze načíst."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Obrázek nelze načíst jako tapetu."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Obrázek nelze nastavit jako tapetu"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Vybráno: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Vybráno: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Vybráno: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Tapeta %1$d z %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Vybrána položka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Smazat"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Moje fotografie"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Tapety"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Oříznutí tapety"</string>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000..10ef5b3
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Angiv baggrund"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Billedet kunne ikke indlæses"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Billedet kunne ikke indlæses som baggrund"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Billedet kunne ikke indlæses som baggrund"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d er valgt"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d er valgt"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d er valgt"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Baggrund %1$d af %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> blev valgt"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Slet"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mine billeder"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Baggrunde"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Beskær baggrunden"</string>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..be35b73
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Hintergrund auswählen"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Bild konnte nicht geladen werden."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Bild konnte nicht als Hintergrund geladen werden."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Bild konnte nicht als Hintergrund festgelegt werden."</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ausgewählt"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ausgewählt"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ausgewählt"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Hintergrund %1$d von %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> ausgewählt"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Löschen"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Meine Fotos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Hintergründe"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Hintergrund zuschneiden"</string>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000..1d799a8
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Ορισμός ταπετσαρίας"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Δεν ήταν δυνατή η φόρτωση της εικόνας"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Δεν ήταν δυνατή η φόρτωση της εικόνας ως ταπετσαρία"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Δεν ήταν δυνατός ο ορισμός της εικόνας ως ταπετσαρία"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d επιλεγμένα"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d επιλεγμένα"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d επιλεγμένα"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Ταπετσαρία %1$d από %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Επιλέχθηκε το <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Διαγραφή"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Οι φωτογραφίες μου"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Ταπετσαρίες"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Περικοπή ταπετσαρίας"</string>
+</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..a384ff6
--- /dev/null
+++ b/res/values-en-rAU/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Set wallpaper"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Couldn\'t load image"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Couldn\'t load image as wallpaper"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Couldn\'t set image as wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d selected"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d selected"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d selected"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Wallpaper %1$d of %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Selected <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Delete"</string>
+    <string name="pick_image" msgid="3189640419551368385">"My photos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Wallpapers"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Crop wallpaper"</string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..a384ff6
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Set wallpaper"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Couldn\'t load image"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Couldn\'t load image as wallpaper"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Couldn\'t set image as wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d selected"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d selected"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d selected"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Wallpaper %1$d of %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Selected <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Delete"</string>
+    <string name="pick_image" msgid="3189640419551368385">"My photos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Wallpapers"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Crop wallpaper"</string>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..a384ff6
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Set wallpaper"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Couldn\'t load image"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Couldn\'t load image as wallpaper"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Couldn\'t set image as wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d selected"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d selected"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d selected"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Wallpaper %1$d of %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Selected <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Delete"</string>
+    <string name="pick_image" msgid="3189640419551368385">"My photos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Wallpapers"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Crop wallpaper"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..924ea56
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Establecer como fondo de pantalla"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"No se pudo cargar la imagen."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"No se pudo cargar la imagen como fondo de pantalla."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"No se pudo establecer la imagen como fondo de pantalla"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d seleccionado"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d seleccionado"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d seleccionados"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Fondo de pantalla %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> seleccionado"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Eliminar"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mis fotos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fondos de pantalla"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Recortar fondo de pantalla"</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..8ecd3f4
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Establecer fondo"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"No se ha podido cargar la imagen"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"No se ha podido cargar la imagen como fondo de pantalla"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"No se ha podido establecer la imagen como fondo de pantalla"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Seleccionados: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Seleccionados: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Seleccionados: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Fondo de pantalla %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> seleccionado"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Eliminar"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mis fotos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fondos de pantalla"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Recortar fondo de pantalla"</string>
+</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..59ca770
--- /dev/null
+++ b/res/values-et-rEE/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Määra taustapilt"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Kujutist ei õnnestunud laadida"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Kujutist ei õnnestunud taustapildina laadida"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Kujutist ei õnnestunud taustapildiks määrata"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Valitud on %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Valitud on %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Valitud on %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d/%2$d taustapildist"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Valitud on <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Kustuta"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Minu fotod"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Taustapildid"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Taustapildi kärpimine"</string>
+</resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
new file mode 100644
index 0000000..fb77b1a
--- /dev/null
+++ b/res/values-eu-rES/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Ezarri horma-papera"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Ezin izan da irudia kargatu"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Ezin izan da irudia horma-paper gisa kargatu"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Ezin izan da ezarri irudia horma-paper gisa"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d hautatuta"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d hautatuta"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d hautatuta"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d/%2$d horma-papera"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> hautatu da"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Ezabatu"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Nire argazkiak"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Horma-paperak"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Ebaki horma-papera"</string>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000..da4b7a1
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"تنظیم کاغذدیواری"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"تصویر بارگیری نشد"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"تصویر به‌عنوان کاغذدیواری بارگیری نشد"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"تصویر به‌عنوان کاغذدیواری تنظیم نشد"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"‏%1$d انتخاب شد"</item>
+    <item quantity="one" msgid="8409622005831789373">"‏%1$d انتخاب شد"</item>
+    <item quantity="other" msgid="479468347731745357">"‏%1$d انتخاب شد"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"‏کاغذدیواری %1$d از %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> انتخاب شد"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"حذف"</string>
+    <string name="pick_image" msgid="3189640419551368385">"عکس‌های من"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"کاغذدیواری‌ها"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"برش کاغذدیواری"</string>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000..3c8f1f5
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Aseta taustakuva"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Kuvan lataus epäonnistui"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Kuvaa ei voitu ladata taustakuvaksi"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Kuvan asettaminen taustakuvaksi epäonnistui."</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d valittu"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d valittu"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d valittu"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Taustakuva %1$d/%2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Valittu: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Poista"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Omat valokuvat"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Taustakuvat"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Rajaa taustakuva"</string>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..ba1d430
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Définir le fond d\'écran"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Impossible de charger l\'image"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Impossible de charger l\'image comme fond d\'écran"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Impossible d\'utiliser l\'image comme fond d\'écran"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d sélectionné"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d sélectionné"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d sélectionné(s)"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Fond d\'écran %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Sélection : <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Supprimer"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mes photos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fonds d\'écran"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Rogner le fond d\'écran"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..9f3b525
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Définir comme fond d\'écran"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Impossible de charger l\'image."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Impossible de charger l\'image comme fond d\'écran."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Impossible de définir l\'image comme fond d\'écran."</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d élément sélectionné"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d élément sélectionné"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d éléments sélectionnés"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Fond d\'écran %1$d sur %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> sélectionné"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Supprimer"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mes photos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fonds d\'écran"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Recadrer le fond d\'écran"</string>
+</resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
new file mode 100644
index 0000000..5805489
--- /dev/null
+++ b/res/values-gl-rES/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Establecer fondo de pantalla"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Non se puido cargar a imaxe"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Non se puido cargar a imaxe como fondo de pantalla"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Non se puido definir a imaxe como fondo de pantalla"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Seleccionaches %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Seleccionaches %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Seleccionaches %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Fondo de pantalla %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Seleccionaches <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Eliminar"</string>
+    <string name="pick_image" msgid="3189640419551368385">"As miñas fotos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fondos de pantalla"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Recortar fondo de pantalla"</string>
+</resources>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
new file mode 100644
index 0000000..f293287
--- /dev/null
+++ b/res/values-gu-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"વૉલપેપર સેટ કરો"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"છબી લોડ કરી શકાઈ નથી"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"વૉલપેપર તરીકે છબી લોડ કરી શકાઈ નથી"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"વૉલપેપર તરીકે છબી સેટ કરી શક્યાં નથી"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d પસંદ કર્યો"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d પસંદ કર્યો"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d પસંદ કર્યો"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$d માંથી %1$d વૉલપેપર"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> પસંદ કર્યો"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"કાઢી નાખો"</string>
+    <string name="pick_image" msgid="3189640419551368385">"મારા ફોટા"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"વૉલપેપર્સ"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"વૉલપેપર કાપો"</string>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000..6f610b5
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"वॉलपेपर सेट करें"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"चित्र लोड नहीं किया जा सका"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"चित्र को वॉलपेपर के रूप में लोड नहीं किया जा सका"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"चित्र को वॉलपेपर के रूप में सेट नहीं किया जा सका"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d चयनित"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d चयनित"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d चयनित"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"वॉलपेपर %2$d में से %1$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"चयनित <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"हटाएं"</string>
+    <string name="pick_image" msgid="3189640419551368385">"मेरी फ़ोटो"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"वॉलपेपर"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"वॉलपेपर काटें"</string>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000..aaf5e1b
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Postavi pozadinu"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Nije moguće učitati sliku"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Nije moguće učitati sliku kao pozadinu"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Postavljanje slike kao pozadine nije uspjelo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Odabrano je %1$d stavki"</item>
+    <item quantity="one" msgid="8409622005831789373">"Odabrana je %1$d stavka"</item>
+    <item quantity="other" msgid="479468347731745357">"Odabrano stavki: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d. pozadinska slika od %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Odabrana je <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Izbriši"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Moje fotografije"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Pozadine"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Obrezivanje pozadinske slike"</string>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000..06c0952
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Háttérkép beállítása"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"A kép betöltése nem sikerült"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"A kép betöltése háttérképként nem sikerült"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"A kép beállítása háttérképként nem sikerült"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d kiválasztva"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d kiválasztva"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d kiválasztva"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d/%2$d. háttérkép"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> kiválasztva"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Törlés"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Saját fotók"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Háttérképek"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Háttérkép körbevágása"</string>
+</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..f68d49f
--- /dev/null
+++ b/res/values-hy-rAM/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Սահմանել պաստառը"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Չհաջողվեց բեռնել նկարը"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Չհաջողվեց նկարը սահմանել որպես պաստառ"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Չհաջողվեց նկարը դնել որպես պաստառ"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ընտրված"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ընտրված"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ընտրված"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d պաստառ՝ %2$d-ից"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Ընտրված է <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Ջնջել"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Իմ լուսանկարները"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Պաստառներ"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Եզրատել պաստառը"</string>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000..634eb1f
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Setel wallpaper"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Tidak dapat memuat gambar"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Tidak dapat memuat gambar sebagai wallpaper"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Tidak dapat menyetel gambar sebagai wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d dipilih"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d dipilih"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d dipilih"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Wallpaper %1$d dari %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> terpilih"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Hapus"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Foto saya"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Wallpaper"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Pangkas wallpaper"</string>
+</resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
new file mode 100644
index 0000000..eac44ec
--- /dev/null
+++ b/res/values-is-rIS/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Velja veggfóður"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Ekki var hægt að hlaða mynd"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Ekki var hægt að hlaða mynd sem veggfóður"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Ekki var hægt að nota mynd sem veggfóður"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d valin"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d valið"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d valin"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Veggfóður %1$d af %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> valið"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Eyða"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Myndirnar mínar"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Veggfóður"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Skera veggfóður"</string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..fdb0ce8
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Imposta sfondo"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Impossibile caricare l\'immagine"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Impossibile caricare l\'immagine come sfondo"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Impossibile impostare l\'immagine come sfondo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d selezionati"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d selezionato"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d selezionati"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Sfondo %1$d di %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Elemento selezionato: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Elimina"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Le mie foto"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Sfondi"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Ritaglia sfondo"</string>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000..c6a583d
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"הגדר טפט"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"לא ניתן היה לטעון את התמונה"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"לא ניתן היה לטעון את התמונה כטפט"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"לא ניתן היה להגדיר את התמונה כטפט"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"‏%1$d נבחרו"</item>
+    <item quantity="one" msgid="8409622005831789373">"‏%1$d נבחרו"</item>
+    <item quantity="other" msgid="479468347731745357">"‏%1$d נבחרו"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"‏טפט %1$d מתוך %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"בחרת <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"מחק"</string>
+    <string name="pick_image" msgid="3189640419551368385">"התמונות שלי"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"טפטים"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"חתוך את הטפט"</string>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..f18da5d
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"壁紙を設定"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"画像を読み込めませんでした"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"画像を壁紙として読み込めませんでした"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"画像を壁紙として設定できませんでした"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d個選択済み"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d個選択済み"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d個選択済み"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"壁紙: %1$d/%2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"選択: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"削除"</string>
+    <string name="pick_image" msgid="3189640419551368385">"マイフォト"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"壁紙"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"壁紙のトリミング"</string>
+</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..1f65282
--- /dev/null
+++ b/res/values-ka-rGE/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"ფონის დაყენება"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"სურათი ვერ ჩაიტვირთა."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"სურათი ფონად ვერ ჩაიტვირთა."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"სურათი ფონად ვერ დაყენდა"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"არჩეულია %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"არჩეულია %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"არჩეულია %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"ფონი %1$d %2$d-დან"</string>
+    <string name="announce_selection" msgid="123723511662250539">"არჩეული <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"წაშლა"</string>
+    <string name="pick_image" msgid="3189640419551368385">"ჩემი ფოტოები"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"ფონები"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ფონის ჩამოჭრა"</string>
+</resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
new file mode 100644
index 0000000..6f4ee7c
--- /dev/null
+++ b/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Тұсқағаз орнату"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Суретті жүктей алмады"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Суретті артқы фон ретінде жүктей алмады"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Кескінді тұсқағаз ретінде орнату мүмкін болмады"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d таңдалған"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d таңдалған"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d таңдалған"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d артқы фон, барлығы %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> таңдалған"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Жою"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Менің фотосуреттерім"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Артқы фондар"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Артқы фонды кесу"</string>
+</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..801260d
--- /dev/null
+++ b/res/values-km-rKH/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"កំណត់​ផ្ទាំង​រូបភាព"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"មិន​អាច​ផ្ទុក​រូបភាព"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"មិន​អាច​ផ្ទុក​រូបភាព​ជា​ផ្ទាំង​រូបភាព"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"មិនអាចកំណត់រូបភាពជាផ្ទាំងរូបភាពទេ"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"បាន​ជ្រើស %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"បាន​ជ្រើស %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"បាន​ជ្រើស %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"ផ្ទាំង​រូបភាព %1$d នៃ %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"បាន​ជ្រើស <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"លុប"</string>
+    <string name="pick_image" msgid="3189640419551368385">"រូបថតរបស់ខ្ញុំ"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"ផ្ទាំង​រូបភាព"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ច្រឹប​ផ្ទាំង​រូបភាព"</string>
+</resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
new file mode 100644
index 0000000..7d4d7e7
--- /dev/null
+++ b/res/values-kn-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"ವಾಲ್‌ಪೇಪರ್ ಹೊಂದಿಸಿ"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"ಚಿತ್ರವನ್ನು ಲೋಡ್‌ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"ಚಿತ್ರವನ್ನು ವಾಲ್‌ಪೇಪರ್‌ ರೂಪದಲ್ಲಿ ಲೋಡ್‌ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"ಚಿತ್ರವನ್ನು ವಾಲ್‌ಪೇಪರ್‌ ರೂಪದಲ್ಲಿ ಹೊಂದಿಸಲಾಗಲಿಲ್ಲ"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$d ರಲ್ಲಿ %1$d ವಾಲ್‌ಪೇಪರ್‌"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"ಅಳಿಸು"</string>
+    <string name="pick_image" msgid="3189640419551368385">"ನನ್ನ ಫೋಟೋಗಳು"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"ವಾಲ್‌ಪೇಪರ್‌ಗಳು"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ವಾಲ್‌ಪೇಪರ್‌ ಕತ್ತರಿಸಿ"</string>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..f407294
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"배경화면 설정"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"이미지를 로드할 수 없습니다."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"이미지를 배경화면으로 로드할 수 없습니다."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"이미지를 배경화면으로 설정할 수 없습니다."</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d개 선택됨"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d개 선택됨"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d개 선택됨"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"배경화면 %1$d/%2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> 선택함"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"삭제"</string>
+    <string name="pick_image" msgid="3189640419551368385">"내 사진"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"배경화면"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"배경화면 잘라내기"</string>
+</resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
new file mode 100644
index 0000000..f53f52b
--- /dev/null
+++ b/res/values-ky-rKG/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Тушкагаз орнотуу"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Сүрөт жүктөө мүмкүн болбоду"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Сүрөттү тушкагаз катары жүктөө кыйрады"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Сүрөт тушкагаз катары коюлбай койду"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d тандалды"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d тандалды"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d тандалды"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$d ичинен %1$d тушкагаз"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> тандалды"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Жок кылуу"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Менин сүрөттөрүм"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Тушкагаздар"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Тушкагазды тегиздөө"</string>
+</resources>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..1da6d29
--- /dev/null
+++ b/res/values-lo-rLA/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"ຕັ້ງເປັນພາບພື້ນຫຼັງ"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"ບໍ່ສາມາດໂຫຼດຮູບໄດ້"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"ບໍ່ສາມາດໂຫຼດຮູບເປັນພາບພື້ນຫຼັງໄດ້"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"ບໍ່ສາມາດໂຫຼດຮູບເປັນພາບພື້ນຫຼັງໄດ້"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"ເລືອກ %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"ເລືອກ %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"ເລືອກ %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"ພາບພື້ນຫຼັງ %1$d ໃນ %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"ເລືອກ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"ລຶບ"</string>
+    <string name="pick_image" msgid="3189640419551368385">"ຮູບຂອງຂ້ອຍ"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"ພາບພື້ນຫຼັງ"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ຕັດພາບພື້ນຫຼັງ"</string>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000..98c9a36
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Nustatyti ekrano foną"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Nepavyko įkelti vaizdo"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Nepavyko įkelti vaizdo kaip ekrano fono"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Nepavyko nustatyti vaizdo kaip ekrano fono"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Pasirinkta: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Pasirinkta: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Pasirinkta: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d ekrano fonas iš %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Pasirinkta: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Ištrinti"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mano nuotraukos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Ekrano fonai"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Apkirpti ekrano foną"</string>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000..ff7876c
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Iestatīt fona tapeti"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Nevarēja ielādēt attēlu."</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Nevarēja ielādēt attēlu kā fona tapeti."</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Nevarēja iestatīt attēlu kā fona tapeti."</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Atlasīts: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Atlasīta: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Atlasītas: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d. fona tapete no %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Atlasīta: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Dzēst"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mani fotoattēli"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fona tapetes"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Apgriezt fona tapeti"</string>
+</resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
new file mode 100644
index 0000000..13b38cd
--- /dev/null
+++ b/res/values-mk-rMK/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Подеси тапет"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Сликата не можеше да се вчита"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Сликата не можеше да се вчита како тапет"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Сликата не може да се постави како тапет"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Избрано %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Избрано %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Избрано %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Тапет %1$d од %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Избран <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Избриши"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Моите фотографии"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Тапети"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Исечи тапет"</string>
+</resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
new file mode 100644
index 0000000..5831b36
--- /dev/null
+++ b/res/values-ml-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"വാൾപേപ്പർ സജ്ജീകരിക്കുക"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"ചിത്രം ലോഡുചെയ്യാനായില്ല"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"വാൾപേപ്പറായി ചിത്രം ലോഡുചെയ്യാനായില്ല"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"വാൾപേപ്പറായി ചിത്രം ലോഡുചെയ്യാനായില്ല"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d തിരഞ്ഞെടുത്തു"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d തിരഞ്ഞെടുത്തു"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d തിരഞ്ഞെടുത്തു"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d / %2$d വാൾപേപ്പർ"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> തിരഞ്ഞെടുത്തു"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"ഇല്ലാതാക്കുക"</string>
+    <string name="pick_image" msgid="3189640419551368385">"എന്റെ ഫോട്ടോകൾ"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"വാൾപേപ്പറുകൾ"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"വാൾപേപ്പറിന്റെ വലുപ്പം മാറ്റൽ"</string>
+</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..9995547
--- /dev/null
+++ b/res/values-mn-rMN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Ханын зургийг тохируулах"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Зургийг ачаалж чадсангүй"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Зургийг ханын зураг болгож ачаалж чадсангүй"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Зургийг ханын зураг болгож чадсангүй"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d сонгогдсон"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d сонгогдсон"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d сонгогдсон"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$d ханын цаасны %1$d нь"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> сонгогдсон"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Устгах"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Миний зураг"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Ханын зураг"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Ханын зургийг тайрах"</string>
+</resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
new file mode 100644
index 0000000..d740fd2
--- /dev/null
+++ b/res/values-mr-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"वॉलपेपर सेट करा"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"प्रतिमा लोड करू शकलो नाही"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"वॉलपेपर म्हणून प्रतिमा लोड करू शकलो नाही"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"प्रतिमा वॉलपेपर म्हणून सेट करू शकलो नाही"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d निवडले"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d निवडले"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d निवडले"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$d पैकी %1$d वॉलपेपर"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> निवडले"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"हटवा"</string>
+    <string name="pick_image" msgid="3189640419551368385">"माझे फोटो"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"वॉलपेपर"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"वॉलपेपर कापा"</string>
+</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..759e48c
--- /dev/null
+++ b/res/values-ms-rMY/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Tetapkan kertas dinding"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Tidak dapat memuatkan imej"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Tidak dapat memuatkan imej sebagai kertas dinding"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Tidak dapat menetapkan imej sebagai kertas dinding"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d dipilih"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d dipilih"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d dipilih"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Kertas dinding %1$d daripada %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Memilih <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Padam"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Foto saya"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Kertas dinding"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Pangkas kertas dinding"</string>
+</resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
new file mode 100644
index 0000000..5197b98
--- /dev/null
+++ b/res/values-my-rMM/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"နောက်ခံအား သတ်မှတ်ရန်"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"ပုံရိပ် တင် မရပါ"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"ပုံရိပ်အား နောက်ခံအဖြစ် တင် မရပါ"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"ပုံရိပ်အား နောက်ခံအဖြစ် တင်၍မရပါ"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ရွေးချယ်ပြီး"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ရွေးချယ်ပြီး"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ရွေးချယ်ပြီး"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"နောက်ခံ %1$d မှ %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"ရွေးချယ်ထားသော <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"ဖျက်ပါ"</string>
+    <string name="pick_image" msgid="3189640419551368385">"ကျွန်ုပ်၏ ဓာတ်ပုံများ"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"နောက်ခံများ"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"နောက်ခံအား ဖြတ်ခြင်း"</string>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..8125b53
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Angi bakgrunn"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Kunne ikke laste inn bildet"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Kunne ikke laste inn bildet som bakgrunn"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Kunne ikke angi bildet som bakgrunn"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d valgt"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d valgt"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d valgt"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Bakgrunn %1$d av %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Valgt <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Slett"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mine bilder"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Bakgrunner"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Beskjær bakgrunnen"</string>
+</resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000..b77a1c5
--- /dev/null
+++ b/res/values-ne-rNP/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"वालपेपर मिलाउनुहोस्"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"तस्बिर लोड गर्न सकिएन"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"तस्बिरलाई वालपेपरका रूपमा लोड गर्न सकिएन"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"छविलाई वालपेपरको रूपमा सेट गर्न सकिएन"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d चयन भयो"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d चयन भयो"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d चयन भयो"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$d को %1$d वालपेपर"</string>
+    <string name="announce_selection" msgid="123723511662250539">"चयन गरिएको <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"मेट्नुहोस्"</string>
+    <string name="pick_image" msgid="3189640419551368385">"मेरा तस्बिरहरू"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"वालपेपरहरु"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"वालपेपर काँटछाट गर्नुहोस्"</string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..dc78305
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Achtergrond instellen"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Kan afbeelding niet laden"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Kan afbeelding niet laden als achtergrond"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Kan afbeelding niet instellen als achtergrond"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d geselecteerd"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d geselecteerd"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d geselecteerd"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Achtergrond %1$d van %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> is geselecteerd"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Verwijderen"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mijn foto\'s"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Achtergronden"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Achtergrond bijsnijden"</string>
+</resources>
diff --git a/res/values-nodpi/wallpapers.xml b/res/values-nodpi/wallpapers.xml
new file mode 100644
index 0000000..1e340e4
--- /dev/null
+++ b/res/values-nodpi/wallpapers.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2009 Google Inc.
+ *
+ * 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>
+    <string-array name="wallpapers" translatable="false">
+    </string-array>
+</resources>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
new file mode 100644
index 0000000..86cdf22
--- /dev/null
+++ b/res/values-pa-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"ਵਾਲਪੇਪਰ ਸੈਟ ਕਰੋ"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"ਚਿੱਤਰ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"ਵਾਲਪੇਪਰ ਦੇ ਤੌਰ ਤੇ ਚਿੱਤਰ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"ਵਾਲਪੇਪਰ ਦੇ ਤੌਰ ਤੇ ਚਿੱਤਰ ਸੈਟ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ਚੁਣਿਆ ਗਿਆ"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ਚੁਣਿਆ ਗਿਆ"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ਚੁਣਿਆ ਗਿਆ"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"ਵਾਲਪੇਪਰ %2$d ਦਾ %1$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> ਚੁਣਿਆ ਗਿਆ"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"ਮਿਟਾਓ"</string>
+    <string name="pick_image" msgid="3189640419551368385">"ਮੇਰੀਆਂ ਫੋਟੋਆਂ"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"ਵਾਲਪੇਪਰ"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ਵਾਲਪੇਪਰ ਕੱਟੋ"</string>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..9693de4
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Ustaw tapetę"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Nie udało się załadować obrazu"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Nie udało się załadować obrazu jako tapety"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Nie udało się ustawić obrazu jako tapety"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Wybranych %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Wybrana %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Wybrane: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Tapeta %1$d z %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Wybrano <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Usuń"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Moje zdjęcia"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Tapety"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Przytnij tapetę"</string>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..3c4fa9b
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Definir imagem fundo"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Não foi possível carregar a imagem"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Não foi possível carregar a imagem como imagem de fundo"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Não foi possível definir a imagem como imagem de fundo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d selecionadas"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d selecionada"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d selecionadas"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Imagem de fundo %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> selecionada"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Eliminar"</string>
+    <string name="pick_image" msgid="3189640419551368385">"As minhas fotos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Imagens de fundo"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Recortar imagem de fundo"</string>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..2520eed
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Definir plano de fundo"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Não foi possível carregar a imagem"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Não foi possível carregar a imagem como plano de fundo"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Não foi possível definir a imagem como plano de fundo"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d selecionados"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d selecionado"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d selecionados"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Plano de fundo %1$d de %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> selecionado"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Excluir"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Minhas fotos"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Planos de fundo"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Cortar plano de fundo"</string>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000..f5df3ee
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Setați imaginea de fundal"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Nu s-a putut încărca imaginea"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Nu s-a putut încărca imaginea ca fundal"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Nu s-a putut seta ca imagine de fundal"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d selectate"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d selectată"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d selectate"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Imaginea de fundal %1$d din %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"S-a selectat <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Ștergeți"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Fotografiile mele"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Imagini de fundal"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Decupați imaginea de fundal"</string>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..f8c350a
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Установить как обои"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Не удалось загрузить изображение"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Не удалось загрузить изображение"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Не удалось сменить обои"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Выбрано: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Выбрано: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Выбрано: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Обои %1$d из %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Выбран элемент \"<xliff:g id="LABEL">%1$s</xliff:g>\""</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Удалить"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Мои фото"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Обои"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Кадрировать обои"</string>
+</resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
new file mode 100644
index 0000000..3945bdf
--- /dev/null
+++ b/res/values-si-rLK/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"වෝල්පේපරය සකසන්න"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"පින්තූරය පූරණය කිරීමට නොහැකි විය"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"පින්තූරය වෝල්පේපරයක් ලෙස පූරණය කිරීමට නොහැකි විය"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"පින්තූරය බිතුපතක් ලෙස සැකසීමට නොහැකි විය"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d තෝරා ගන්නා ලදි"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d තෝරා ගන්නා ලදි"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d තෝරා ගන්නා ලදි"</item>
+  </plurals>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for wallpaper_accessibility_name (4093221025304876354) -->
+    <skip />
+    <string name="announce_selection" msgid="123723511662250539">"තෝරාගත්තේ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"මකන්න"</string>
+    <string name="pick_image" msgid="3189640419551368385">"මගේ ඡායාරූප"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"වෝල්පේපර"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"වෝල්පේපරය කප්පාදු කිරීම"</string>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000..fb2c819
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Nastaviť tapetu"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Obrázok nie je možné načítať"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Obrázok nie je možné načítať ako tapetu"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Obrázok nie je možné nastaviť ako tapetu"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Počet vybratých položiek: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Počet vybratých položiek: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Počet vybratých položiek: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Tapeta %1$d z %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Vybratá položka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Odstrániť"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Moje fotky"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Tapety"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Orezanie tapety"</string>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000..a7ff089
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Nastavi ozadje"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Slike ni bilo mogoče naložiti"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Slike ni bilo mogoče naložiti kot ozadje"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Slike ni bilo mogoče nastaviti kot ozadje"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Št. izbranih: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Št. izbranih: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Št. izbranih: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%1$d. ozadje od %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Izbrano: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Izbriši"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Moje fotografije"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Ozadja"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Obrezovanje ozadja"</string>
+</resources>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
new file mode 100644
index 0000000..8259d66
--- /dev/null
+++ b/res/values-sq-rAL/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Cakto imazhin e sfondit"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Nuk mund të ngarkonte imazhin"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Nuk mundi të ngarkonte imazhin si imazh sfondi"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Nuk mundi të vendoste imazhin si imazh sfondi."</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Të përzgjedhur: %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Të përzgjedhur: %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Të përzgjedhur: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Imazhi i sfondit: %1$d nga gjithsej %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> u përzgjodh"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Fshi"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Fotografitë e mia"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Imazhet e sfondit"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Prit imazhin e sfondit"</string>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000..6154526
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Подеси позадину"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Није могуће учитати слику"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Није могуће учитати слику као позадину"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Учитавање слике као позадине није успело"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Изабрано је %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Изабрана је %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Изабраних: %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Позадина %1$d од %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Изабрана је <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Избриши"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Моје фотографије"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Позадине"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Опсеци позадину"</string>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000..38062b9
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Ange bakgrund"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Det gick inte att läsa in bilden"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Det gick inte att läsa in bilden som bakgrund"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Det gick inte att ange bilden som bakgrund"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d har valts"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d har valts"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d har valts"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Bakgrund %1$d av %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> har valts"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Ta bort"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mina foton"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Bakgrunder"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Beskär bakgrund"</string>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000..729a79a
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Weka mandhari"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Haikuweza kupakia picha"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Haikuweza kupakia picha iwe mandhari"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Haikuweza kuweka picha kuwa mandhari"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d zimechaguliwa"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d zimechaguliwa"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d zimechaguliwa"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Mandhari %1$d ya %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> iliyochaguliwa"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Futa"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Picha zangu"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Mandhari"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Punguza mandhari"</string>
+</resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
new file mode 100644
index 0000000..69a9d38
--- /dev/null
+++ b/res/values-ta-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"வால்பேப்பரை அமை"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"படத்தை ஏற்ற முடியவில்லை"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"படத்தை வால்பேப்பராக ஏற்ற முடியவில்லை"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"படத்தை வால்பேப்பராக அமைக்க முடியவில்லை"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d தேர்ந்தெடுக்கப்பட்டன"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d தேர்ந்தெடுக்கப்பட்டது"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d தேர்ந்தெடுக்கப்பட்டன"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"வால்பேப்பர் %1$d / %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> தேர்ந்தெடுக்கப்பட்டது"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"நீக்கு"</string>
+    <string name="pick_image" msgid="3189640419551368385">"எனது படங்கள்"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"வால்பேப்பர்கள்"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"வால்பேப்பரைச் செதுக்கு"</string>
+</resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
new file mode 100644
index 0000000..6fb5fa2
--- /dev/null
+++ b/res/values-te-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"వాల్‌పేపర్‌ను సెట్ చేయి"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"చిత్రాన్ని లోడ్ చేయడం సాధ్యపడలేదు"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"చిత్రాన్ని వాల్‌పేపర్‌గా లోడ్ చేయడం సాధ్యపడలేదు"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"చిత్రాన్ని వాల్‌పేపర్‌గా సెట్ చేయడం సాధ్యపడలేదు"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ఎంచుకోబడింది"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ఎంచుకోబడింది"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ఎంచుకోబడింది"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$dలో %1$dవ వాల్‌పేపర్"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> ఎంచుకోబడింది"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"తొలగించు"</string>
+    <string name="pick_image" msgid="3189640419551368385">"నా ఫోటోలు"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"వాల్‌పేపర్‌లు"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"వాల్‌పేపర్‌ను కత్తిరించండి"</string>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000..c689436
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"ตั้งวอลเปเปอร์"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"ไม่สามารถโหลดรูปภาพ"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"ไม่สามารถโหลดรูปภาพเป็นวอลเปเปอร์"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"ไม่สามารถตั้งรูปภาพเป็นวอลเปเปอร์"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"เลือกไว้ %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"เลือกไว้ %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"เลือกไว้ %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"วอลเปเปอร์ %1$d จาก %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"เลือก <xliff:g id="LABEL">%1$s</xliff:g> แล้ว"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"ลบ"</string>
+    <string name="pick_image" msgid="3189640419551368385">"รูปภาพของฉัน"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"วอลเปเปอร์"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"ครอบตัดวอลเปเปอร์"</string>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000..c760d7f
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Itakda ang wallpaper"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Hindi ma-load ang larawan"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Hindi ma-load ang larawan bilang wallpaper"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Hindi maitakda ang larawan bilang wallpaper"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ang napili"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ang napili"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ang napili"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Wallpaper %1$d ng %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Napili ang <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Tanggalin"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Aking mga larawan"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Mga Wallpaper"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"I-crop ang wallpaper"</string>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000..e9dc1d2
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Duvar kağıdını ayarla"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Resim yüklenemedi"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Resim duvar kağıdı olarak yüklenemedi"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Resim, duvar kağıdı olarak ayarlanamadı"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d tane seçildi"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d tane seçildi"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d tane seçildi"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"%2$d duvar kağıdı arasından duvar kağıdı %1$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> seçildi"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Sil"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Fotoğraflarım"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Duvar kağıtları"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Duvar kağıdını kırp"</string>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000..c6669c0
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Установити фон"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Не вдалося завантажити зображення"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Не вдалося завантажити зображення як фоновий малюнок"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Не вдалося зробити зображення фоновим малюнком"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Вибрано %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Вибрано %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Вибрано %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Фоновий малюнок %1$d з %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"Вибрано <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Видалити"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Мої фото"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Фонові малюнки"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Обрізати фоновий малюнок"</string>
+</resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
new file mode 100644
index 0000000..e240e1a
--- /dev/null
+++ b/res/values-ur-rPK/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"وال پیپر سیٹ کریں"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"تصویر کو لوڈ نہیں کیا جا سکا"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"تصویر کو وال پیپر کے بطور لوڈ نہیں کیا جا سکا"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"تصویر کو بطور وال پیپر سیٹ نہیں کیا جا سکا"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"‏%1$d کو منتخب کیا گیا"</item>
+    <item quantity="one" msgid="8409622005831789373">"‏%1$d کو منتخب کیا گیا"</item>
+    <item quantity="other" msgid="479468347731745357">"‏%1$d کو منتخب کیا گیا"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"‏وال پیپر ‎%1$d از ‎%2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> کو منتخب کیا گیا"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"حذف کریں"</string>
+    <string name="pick_image" msgid="3189640419551368385">"میری تصاویر"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"وال پیپرز"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"وال پیپر کو تراشیں"</string>
+</resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
new file mode 100644
index 0000000..5a79981
--- /dev/null
+++ b/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Fonga rasm o‘rnatish"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Rasm yuklanmadi"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Fon rasmi sifatida rasm yuklanmadi"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Rasmni fon rasmi sifatida o‘rnatib bo‘lmadi"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d ta tanlandi"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d ta tanlandi"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d ta tanlandi"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Fon rasmi %2$ddan %1$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> tanlandi"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"O‘chirish"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Mening rasmlarim"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Fon rasmlari"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Fon rasmini kesish"</string>
+</resources>
diff --git a/res/values-v19/styles.xml b/res/values-v19/styles.xml
new file mode 100644
index 0000000..43d842c
--- /dev/null
+++ b/res/values-v19/styles.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2013 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="WallpaperTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:actionBarStyle">@style/WallpaperCropperActionBar</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBarOverlay">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+    </style>
+</resources>
diff --git a/res/values-v21/styles.xml b/res/values-v21/styles.xml
new file mode 100644
index 0000000..582ab8f
--- /dev/null
+++ b/res/values-v21/styles.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2015 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">
+
+    <style name="WallpaperCropperActionBar" parent="@android:style/Widget.DeviceDefault.ActionBar">
+        <item name="android:displayOptions">showCustom</item>
+        <item name="android:background">#88000000</item>
+        <item name="android:contentInsetEnd">0dp</item>
+        <item name="android:contentInsetLeft">0dp</item>
+        <item name="android:contentInsetRight">0dp</item>
+        <item name="android:contentInsetStart">0dp</item>
+    </style>
+
+    <style name="ActionBarSetWallpaperStyle" parent="@android:style/Widget.DeviceDefault.ActionButton">
+        <item name="android:textColor">#ffffffff</item>
+        <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000..a7c636d
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Đặt hình nền"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Không thể tải hình ảnh"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Không thể tải hình ảnh làm hình nền"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Không thể đặt hình ảnh làm hình nền"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"Đã chọn %1$d"</item>
+    <item quantity="one" msgid="8409622005831789373">"Đã chọn %1$d"</item>
+    <item quantity="other" msgid="479468347731745357">"Đã chọn %1$d"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Hình nền %1$d / %2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"<xliff:g id="LABEL">%1$s</xliff:g> được chọn"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Xóa"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Ảnh của tôi"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Hình nền"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Cắt hình nền"</string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..4656ec6
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"设置壁纸"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"无法加载图片"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"无法加载要设为壁纸的图片"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"无法将图片设为壁纸"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"已选择%1$d项"</item>
+    <item quantity="one" msgid="8409622005831789373">"已选择%1$d项"</item>
+    <item quantity="other" msgid="479468347731745357">"已选择%1$d项"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"第%1$d张壁纸,共%2$d张"</string>
+    <string name="announce_selection" msgid="123723511662250539">"已选择<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"删除"</string>
+    <string name="pick_image" msgid="3189640419551368385">"我的照片"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"壁纸"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"剪裁壁纸"</string>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..eb9c327
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"設定桌布"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"無法載入圖片"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"無法載入圖片設為桌布"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"無法將圖片設為桌布"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"已選取 %1$d 張"</item>
+    <item quantity="one" msgid="8409622005831789373">"已選取 %1$d 張"</item>
+    <item quantity="other" msgid="479468347731745357">"已選取 %1$d 張"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"第 %1$d 張桌布,共 %2$d 張"</string>
+    <string name="announce_selection" msgid="123723511662250539">"已選取<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"刪除"</string>
+    <string name="pick_image" msgid="3189640419551368385">"我的相片"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"桌布"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"裁剪桌布"</string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..fda123c
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"設定桌布"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"無法載入圖片"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"無法載入您要設為桌布的圖片"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"無法將圖片設為桌布"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"已選取 %1$d 個"</item>
+    <item quantity="one" msgid="8409622005831789373">"已選取 %1$d 個"</item>
+    <item quantity="other" msgid="479468347731745357">"已選取 %1$d 個"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"第 %1$d 張桌布,共 %2$d 張"</string>
+    <string name="announce_selection" msgid="123723511662250539">"已選取<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"刪除"</string>
+    <string name="pick_image" msgid="3189640419551368385">"我的相片"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"桌布"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"裁剪桌布"</string>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000..1a5b95e
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2013 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_instructions" msgid="3524143401182707094">"Setha isithombe sangemuva"</string>
+    <string name="image_load_fail" msgid="7538534580694411837">"Ayikwazanga ukulayisha isithombe"</string>
+    <string name="wallpaper_load_fail" msgid="4800700444605404650">"Ayikwazanga ukulayisha isithombe njengesithombe sangemuva"</string>
+    <string name="wallpaper_set_fail" msgid="7023180794008631780">"Ayikwazanga ukusetha isithombe njengesithombe sangemuva"</string>
+  <plurals name="number_of_items_selected">
+    <item quantity="zero" msgid="9015111147509924344">"%1$d khethiwe"</item>
+    <item quantity="one" msgid="8409622005831789373">"%1$d khethiwe"</item>
+    <item quantity="other" msgid="479468347731745357">"%1$d khethiwe"</item>
+  </plurals>
+    <string name="wallpaper_accessibility_name" msgid="4093221025304876354">"Isithombe sangemuva esingu-%1$d kwezingu-%2$d"</string>
+    <string name="announce_selection" msgid="123723511662250539">"I-<xliff:g id="LABEL">%1$s</xliff:g> ekhethiwe"</string>
+    <string name="wallpaper_delete" msgid="1459353972739215344">"Susa"</string>
+    <string name="pick_image" msgid="3189640419551368385">"Izithombe zami"</string>
+    <string name="pick_wallpaper" msgid="4628969645948454559">"Izithombe zangemuva"</string>
+    <string name="crop_wallpaper" msgid="4882870800623585836">"Nqampuna isithombe sangemuva"</string>
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..3837859
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,7 @@
+<resources>
+    <string-array name="which_wallpaper_options">
+        <item>@string/which_wallpaper_option_home_screen</item>
+        <item>@string/which_wallpaper_option_lock_screen</item>
+        <item>@string/which_wallpaper_option_home_screen_and_lock_screen</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..adae7cf
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2013, 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>
+    <color name="wallpaper_picker_translucent_gray">#66000000</color>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..0447c6d
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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>
+<!-- Wallpaper picker -->
+    <dimen name="wallpaperThumbnailWidth">106.5dp</dimen>
+    <dimen name="wallpaperThumbnailHeight">94.5dp</dimen>
+    <dimen name="wallpaperItemIconSize">32dp</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..aef6a1a
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2013 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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Button label on Wallpaper picker screen; user selects this button to set a specific wallpaper -->
+    <string name="wallpaper_instructions">Set wallpaper</string>
+    <!-- Error message when an image is selected as a wallpaper,
+         but the wallpaper picker cannot load it -->
+    <string name="image_load_fail">Coudn\'t load image</string>
+    <!-- Error message when an image is selected as a wallpaper,
+         but the wallpaper cropper cannot load it. The user will
+         usually see this when using another app and trying to set
+         an image as the wallpaper -->
+    <string name="wallpaper_load_fail">Couldn\'t load image as wallpaper</string>
+    <!-- Error message when an image is selected as a wallpaper,
+         but something goes wrong when the user clicks "Set wallpaper" -->
+    <string name="wallpaper_set_fail">Couldn\'t set image as wallpaper</string>
+    <!-- Shown when wallpapers are selected in Wallpaper picker -->
+    <!-- String indicating how many media item(s) is(are) selected
+            eg. 1 selected [CHAR LIMIT=30] -->
+    <plurals name="number_of_items_selected">
+        <item quantity="zero">%1$d selected</item>
+        <item quantity="one">%1$d selected</item>
+        <item quantity="other">%1$d selected</item>
+    </plurals>
+    <!-- Accessibility string used as a label for a particular wallpaper in the Wallpaper Picker list.
+         e.g. "Wallpaper 3 of 10" -->
+    <string name="wallpaper_accessibility_name">Wallpaper %1$d of %2$d</string>
+    <!-- Accessibility string used to announce that a wallpaper has been selected. -->
+    <string name="announce_selection">Selected <xliff:g id="label" example="Wallpaper 3 of 10">%1$s</xliff:g></string>
+
+    <!-- Label on button to delete wallpaper(s) -->
+    <string name="wallpaper_delete">Delete</string>
+    <!-- Label on button in Wallpaper Picker to pick an image -->
+    <string name="pick_image">My photos</string>
+    <!-- Option in "Select wallpaper from" dialog box -->
+    <string name="pick_wallpaper">Wallpapers</string>
+    <!-- Title of activity for cropping wallpapers -->
+    <string name="crop_wallpaper">Crop wallpaper</string>
+
+    <!-- Option for setting the wallpaper only on the home screen. -->
+    <string name="which_wallpaper_option_home_screen">Home screen</string>
+    <!-- Option for setting the wallpaper only on the lock screen. -->
+    <string name="which_wallpaper_option_lock_screen">Lock screen</string>
+    <!-- Option for setting the wallpaper on both the home screen and lock screen. -->
+    <string name="which_wallpaper_option_home_screen_and_lock_screen">Home screen and lock screen</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..d8efcac
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2013 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">
+    <style name="WallpaperTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:actionBarStyle">@style/WallpaperCropperActionBar</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBarOverlay">true</item>
+    </style>
+
+    <style name="WallpaperTheme.Picker" parent="WallpaperTheme">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowShowWallpaper">true</item>
+    </style>
+
+    <style name="WallpaperCropperActionBar" parent="@android:style/Widget.DeviceDefault.ActionBar">
+        <item name="android:displayOptions">showCustom</item>
+        <item name="android:background">#88000000</item>
+    </style>
+
+    <style name="ActionBarSetWallpaperStyle" parent="@android:style/Widget.DeviceDefault.ActionButton">
+        <item name="android:textColor">#ffffffff</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
+</resources>
diff --git a/src/com/android/gallery3d/common/ExifOrientation.java b/src/com/android/gallery3d/common/ExifOrientation.java
new file mode 100644
index 0000000..ad4370c
--- /dev/null
+++ b/src/com/android/gallery3d/common/ExifOrientation.java
@@ -0,0 +1,145 @@
+/**
+ * Copyright (C) 2015 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.gallery3d.common;
+
+import android.content.Context;
+import android.media.ExifInterface;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ExifOrientation {
+    private static final String TAG = "ExifOrientation";
+    private static final boolean DEBUG = false;
+
+    private static final short SOI =  (short) 0xFFD8;   // start of input
+    private static final short APP0 = (short) 0xFFE0;
+    private static final short APPF = (short) 0xFFEF;
+    private static final short APP1 = (short) 0xFFE1;
+    private static final short SOS = (short) 0xFFDA;    // start of stream
+    private static final short EOI = (short) 0xFFD9;    // end of input
+
+    // The header is available in first 64 bytes, so reading upto 128 bytes
+    // should be more than enough.
+    private static final int MAX_BYTES_TO_READ = 128 * 1024;
+
+    /**
+     * Parses the rotation of the JPEG image from the input stream.
+     */
+    public static final int readRotation(InputStream in, Context context) {
+        // Since the platform implementation only takes file input, create a temporary file
+        // with just the image header.
+        File tempFile = null;
+        DataOutputStream tempOut = null;
+
+        try {
+        DataInputStream din = new DataInputStream(in);
+            int pos = 0;
+            if (din.readShort() == SOI) {
+                pos += 2;
+
+                short marker = din.readShort();
+                pos += 2;
+
+                while ((marker >= APP0 && marker <= APPF) && pos < MAX_BYTES_TO_READ) {
+                    int length = din.readUnsignedShort();
+                    if (length < 2) {
+                        throw new IOException("Invalid header size");
+                    }
+
+                    // We only want APP1 headers
+                    if (length > 2) {
+                        if (marker == APP1) {
+                            // Copy the header
+                            if (tempFile == null) {
+                                tempFile = File.createTempFile(TAG, ".jpg", context.getCacheDir());
+                                tempOut = new DataOutputStream(new FileOutputStream(tempFile));
+                                tempOut.writeShort(SOI);
+                            }
+
+                            tempOut.writeShort(marker);
+                            tempOut.writeShort(length);
+
+                            byte[] header = new byte[length - 2];
+                            din.read(header);
+                            tempOut.write(header);
+                        } else {
+                            din.skip(length - 2);
+                        }
+                    }
+                    pos += length;
+
+                    marker = din.readShort();
+                    pos += 2;
+                }
+
+                if (tempOut != null) {
+                    // Write empty image data.
+                    tempOut.writeShort(SOS);
+                    // Write the frame size as 2. Since this includes the size bytes as well
+                    // (short = 2 bytes), it implies there is 0 byte of image data.
+                    tempOut.writeShort(2);
+
+                    // End of input
+                    tempOut.writeShort(EOI);
+                    tempOut.close();
+
+                    return readRotation(tempFile.getAbsolutePath());
+                }
+            }
+        } catch (IOException e) {
+            if (DEBUG) {
+                Log.d(TAG, "Error parsing input stream", e);
+            }
+        } finally {
+            Utils.closeSilently(in);
+            Utils.closeSilently(tempOut);
+            if (tempFile != null) {
+                tempFile.delete();
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Parses the rotation of the JPEG image.
+     */
+    public static final int readRotation(String filePath) {
+        try {
+            ExifInterface exif = new ExifInterface(filePath);
+            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
+                case ExifInterface.ORIENTATION_ROTATE_90:
+                    return 90;
+                case ExifInterface.ORIENTATION_ROTATE_270:
+                    return 270;
+                case ExifInterface.ORIENTATION_ROTATE_180:
+                    return 180;
+                default:
+                    return 0;
+            }
+        } catch (IOException e) {
+            if (DEBUG) {
+                Log.d(TAG, "Error reading file", e);
+            }
+        }
+        return 0;
+    }
+}
diff --git a/src/com/android/gallery3d/common/Utils.java b/src/com/android/gallery3d/common/Utils.java
new file mode 100644
index 0000000..a795c88
--- /dev/null
+++ b/src/com/android/gallery3d/common/Utils.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.common;
+
+import android.database.Cursor;
+import android.graphics.RectF;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+public class Utils {
+    private static final String TAG = "Utils";
+
+    // Throws AssertionError if the input is false.
+    public static void assertTrue(boolean cond) {
+        if (!cond) {
+            throw new AssertionError();
+        }
+    }
+
+    // Returns the next power of two.
+    // Returns the input if it is already power of 2.
+    // Throws IllegalArgumentException if the input is <= 0 or
+    // the answer overflows.
+    public static int nextPowerOf2(int n) {
+        if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n);
+        n -= 1;
+        n |= n >> 16;
+        n |= n >> 8;
+        n |= n >> 4;
+        n |= n >> 2;
+        n |= n >> 1;
+        return n + 1;
+    }
+
+    // Returns the previous power of two.
+    // Returns the input if it is already power of 2.
+    // Throws IllegalArgumentException if the input is <= 0
+    public static int prevPowerOf2(int n) {
+        if (n <= 0) throw new IllegalArgumentException();
+        return Integer.highestOneBit(n);
+    }
+
+    // Returns the input value x clamped to the range [min, max].
+    public static int clamp(int x, int min, int max) {
+        if (x > max) return max;
+        if (x < min) return min;
+        return x;
+    }
+
+    public static int ceilLog2(float value) {
+        int i;
+        for (i = 0; i < 31; i++) {
+            if ((1 << i) >= value) break;
+        }
+        return i;
+    }
+
+    public static int floorLog2(float value) {
+        int i;
+        for (i = 0; i < 31; i++) {
+            if ((1 << i) > value) break;
+        }
+        return i - 1;
+    }
+
+    public static void closeSilently(Closeable c) {
+        if (c == null) return;
+        try {
+            c.close();
+        } catch (IOException t) {
+            Log.w(TAG, "close fail ", t);
+        }
+    }
+
+    public static void closeSilently(ParcelFileDescriptor fd) {
+        try {
+            if (fd != null) fd.close();
+        } catch (Throwable t) {
+            Log.w(TAG, "fail to close", t);
+        }
+    }
+
+    public static void closeSilently(Cursor cursor) {
+        try {
+            if (cursor != null) cursor.close();
+        } catch (Throwable t) {
+            Log.w(TAG, "fail to close", t);
+        }
+    }
+
+    public static RectF getMaxCropRect(
+            int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
+        RectF cropRect = new RectF();
+        // Get a crop rect that will fit this
+        if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
+             cropRect.top = 0;
+             cropRect.bottom = inHeight;
+             cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
+             cropRect.right = inWidth - cropRect.left;
+             if (leftAligned) {
+                 cropRect.right -= cropRect.left;
+                 cropRect.left = 0;
+             }
+        } else {
+            cropRect.left = 0;
+            cropRect.right = inWidth;
+            cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
+            cropRect.bottom = inHeight - cropRect.top;
+        }
+        return cropRect;
+    }
+
+    /**
+     * Find the min x that 1 / x >= scale
+     */
+    public static int computeSampleSizeLarger(float scale) {
+        int initialSize = (int) Math.floor(1f / scale);
+        if (initialSize <= 1) return 1;
+        return initialSize <= 8 ? prevPowerOf2(initialSize) : (initialSize / 8 * 8);
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java
new file mode 100644
index 0000000..7270e88
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+
+import java.util.WeakHashMap;
+
+// BasicTexture is a Texture corresponds to a real GL texture.
+// The state of a BasicTexture indicates whether its data is loaded to GL memory.
+// If a BasicTexture is loaded into GL memory, it has a GL texture id.
+public abstract class BasicTexture implements Texture {
+
+    private static final String TAG = "BasicTexture";
+    protected static final int UNSPECIFIED = -1;
+
+    protected static final int STATE_UNLOADED = 0;
+    protected static final int STATE_LOADED = 1;
+    protected static final int STATE_ERROR = -1;
+
+    // Log a warning if a texture is larger along a dimension
+    private static final int MAX_TEXTURE_SIZE = 4096;
+
+    protected int mId = -1;
+    protected int mState;
+
+    protected int mWidth = UNSPECIFIED;
+    protected int mHeight = UNSPECIFIED;
+
+    protected int mTextureWidth;
+    protected int mTextureHeight;
+
+    protected GLCanvas mCanvasRef = null;
+    private static WeakHashMap<BasicTexture, Object> sAllTextures
+            = new WeakHashMap<BasicTexture, Object>();
+    private static ThreadLocal sInFinalizer = new ThreadLocal();
+
+    protected BasicTexture(GLCanvas canvas, int id, int state) {
+        setAssociatedCanvas(canvas);
+        mId = id;
+        mState = state;
+        synchronized (sAllTextures) {
+            sAllTextures.put(this, null);
+        }
+    }
+
+    protected BasicTexture() {
+        this(null, 0, STATE_UNLOADED);
+    }
+
+    protected void setAssociatedCanvas(GLCanvas canvas) {
+        mCanvasRef = canvas;
+    }
+
+    /**
+     * Sets the content size of this texture. In OpenGL, the actual texture
+     * size must be of power of 2, the size of the content may be smaller.
+     */
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+        mTextureWidth = width > 0 ? Utils.nextPowerOf2(width) : 0;
+        mTextureHeight = height > 0 ? Utils.nextPowerOf2(height) : 0;
+        if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
+            Log.w(TAG, String.format("texture is too large: %d x %d",
+                    mTextureWidth, mTextureHeight), new Exception());
+        }
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    // Returns the width rounded to the next power of 2.
+    public int getTextureWidth() {
+        return mTextureWidth;
+    }
+
+    // Returns the height rounded to the next power of 2.
+    public int getTextureHeight() {
+        return mTextureHeight;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y) {
+        canvas.drawTexture(this, x, y, getWidth(), getHeight());
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+        canvas.drawTexture(this, x, y, w, h);
+    }
+
+    // onBind is called before GLCanvas binds this texture.
+    // It should make sure the data is uploaded to GL memory.
+    abstract protected boolean onBind(GLCanvas canvas);
+
+    public boolean isLoaded() {
+        return mState == STATE_LOADED;
+    }
+
+    // recycle() is called when the texture will never be used again,
+    // so it can free all resources.
+    public void recycle() {
+        freeResource();
+    }
+
+    // yield() is called when the texture will not be used temporarily,
+    // so it can free some resources.
+    // The default implementation unloads the texture from GL memory, so
+    // the subclass should make sure it can reload the texture to GL memory
+    // later, or it will have to override this method.
+    public void yield() {
+        freeResource();
+    }
+
+    private void freeResource() {
+        GLCanvas canvas = mCanvasRef;
+        if (canvas != null && mId != -1) {
+            canvas.unloadTexture(this);
+            mId = -1; // Don't free it again.
+        }
+        mState = STATE_UNLOADED;
+        setAssociatedCanvas(null);
+    }
+
+    @Override
+    protected void finalize() {
+        sInFinalizer.set(BasicTexture.class);
+        recycle();
+        sInFinalizer.set(null);
+    }
+
+    public static void yieldAllTextures() {
+        synchronized (sAllTextures) {
+            for (BasicTexture t : sAllTextures.keySet()) {
+                t.yield();
+            }
+        }
+    }
+
+    public static void invalidateAllTextures() {
+        synchronized (sAllTextures) {
+            for (BasicTexture t : sAllTextures.keySet()) {
+                t.mState = STATE_UNLOADED;
+                t.setAssociatedCanvas(null);
+            }
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
new file mode 100644
index 0000000..bb69b68
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.common.Utils;
+
+// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
+//
+// The texture does not own the Bitmap. The user should make sure the Bitmap
+// is valid during the texture's lifetime. When the texture is recycled, it
+// does not free the Bitmap.
+public class BitmapTexture extends UploadedTexture {
+    protected Bitmap mContentBitmap;
+
+    public BitmapTexture(Bitmap bitmap) {
+        super();
+        Utils.assertTrue(bitmap != null && !bitmap.isRecycled());
+        mContentBitmap = bitmap;
+    }
+
+    @Override
+    protected void onFreeBitmap(Bitmap bitmap) {
+        // Do nothing.
+    }
+
+    @Override
+    protected Bitmap onGetBitmap() {
+        return mContentBitmap;
+    }
+
+    public Bitmap getBitmap() {
+        return mContentBitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLCanvas.java b/src/com/android/gallery3d/glrenderer/GLCanvas.java
new file mode 100644
index 0000000..2bda8d2
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLCanvas.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+
+//
+// GLCanvas gives a convenient interface to draw using OpenGL.
+//
+// When a rectangle is specified in this interface, it means the region
+// [x, x+width) * [y, y+height)
+//
+public interface GLCanvas {
+
+    public GLId getGLId();
+
+    // Tells GLCanvas the size of the underlying GL surface. This should be
+    // called before first drawing and when the size of GL surface is changed.
+    // This is called by GLRoot and should not be called by the clients
+    // who only want to draw on the GLCanvas. Both width and height must be
+    // nonnegative.
+    public abstract void setSize(int width, int height);
+
+    // Clear the drawing buffers. This should only be used by GLRoot.
+    public abstract void clearBuffer();
+
+    public abstract void translate(float x, float y);
+
+    public abstract void rotate(float angle, float x, float y, float z);
+
+    // Same as save(), but only save those specified in saveFlags.
+    public abstract void save(int saveFlags);
+
+    public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
+    public static final int SAVE_FLAG_MATRIX = 0x02;
+
+    // Pops from the top of the stack as current configuration state (matrix,
+    // alpha, and clip). This call balances a previous call to save(), and is
+    // used to remove all modifications to the configuration state since the
+    // last save call.
+    public abstract void restore();
+
+    // Draws a texture to the specified rectangle.
+    public abstract void drawTexture(BasicTexture texture, int x, int y, int width, int height);
+
+    // Draws the source rectangle part of the texture to the target rectangle.
+    public abstract void drawTexture(BasicTexture texture, RectF source, RectF target);
+
+    // Unloads the specified texture from the canvas. The resource allocated
+    // to draw the texture will be released. The specified texture will return
+    // to the unloaded state. This function should be called only from
+    // BasicTexture or its descendant
+    public abstract boolean unloadTexture(BasicTexture texture);
+
+    // Delete the textures and buffers in GL side. This function should only be
+    // called in the GL thread.
+    public abstract void deleteRecycledResources();
+
+    /**
+     * Sets texture parameters to use GL_CLAMP_TO_EDGE for both
+     * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
+     * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
+     * bindTexture() must be called prior to this.
+     *
+     * @param texture The texture to set parameters on.
+     */
+    public abstract void setTextureParameters(BasicTexture texture);
+
+    /**
+     * Initializes the texture to a size by calling texImage2D on it.
+     *
+     * @param texture The texture to initialize the size.
+     * @param format The texture format (e.g. GL_RGBA)
+     * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+     */
+    public abstract void initializeTextureSize(BasicTexture texture, int format, int type);
+
+    /**
+     * Initializes the texture to a size by calling texImage2D on it.
+     *
+     * @param texture The texture to initialize the size.
+     * @param bitmap The bitmap to initialize the bitmap with.
+     */
+    public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap);
+
+    /**
+     * Calls glTexSubImage2D to upload a bitmap to the texture.
+     *
+     * @param texture The target texture to write to.
+     * @param xOffset Specifies a texel offset in the x direction within the
+     *            texture array.
+     * @param yOffset Specifies a texel offset in the y direction within the
+     *            texture array.
+     * @param format The texture format (e.g. GL_RGBA)
+     * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+     */
+    public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset,
+            Bitmap bitmap,
+            int format, int type);
+
+    /**
+     * Generates buffers and uploads the buffer data.
+     *
+     * @param buffer The buffer to upload
+     * @return The buffer ID that was generated.
+     */
+    public abstract int uploadBuffer(java.nio.FloatBuffer buffer);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
new file mode 100644
index 0000000..0da3bae
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLES20Canvas implements GLCanvas {
+    // ************** Constants **********************
+    private static final String TAG = GLES20Canvas.class.getSimpleName();
+    private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
+
+    private static final int COORDS_PER_VERTEX = 2;
+    private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE;
+
+    private static final int COUNT_FILL_VERTEX = 4;
+    private static final int OFFSET_FILL_RECT = 0;
+
+    private static final int GL_TARGET = GL11.GL_TEXTURE_2D;
+
+    private static final float[] BOX_COORDINATES = {
+            0, 0, // Fill rectangle
+            1, 0,
+            0, 1,
+            1, 1,
+            0, 0, // Draw line
+            1, 1,
+            0, 0, // Draw rectangle outline
+            0, 1,
+            1, 1,
+            1, 0,
+    };
+
+    private static final String POSITION_ATTRIBUTE = "aPosition";
+    private static final String MATRIX_UNIFORM = "uMatrix";
+    private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix";
+    private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler";
+    private static final String ALPHA_UNIFORM = "uAlpha";
+
+    private static final String TEXTURE_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "  vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n"
+            + "}\n";
+
+    private static final String TEXTURE_FRAGMENT_SHADER = ""
+            + "precision mediump float;\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "uniform float " + ALPHA_UNIFORM + ";\n"
+            + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
+            + "  gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
+            + "}\n";
+
+    private static final int INITIAL_RESTORE_STATE_SIZE = 8;
+    private static final int MATRIX_SIZE = 16;
+
+    // Keep track of restore state
+    private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
+    private IntArray mSaveFlags = new IntArray();
+
+    private int mCurrentMatrixIndex = 0;
+
+    // Viewport size
+    private int mWidth;
+    private int mHeight;
+
+    // Projection matrix
+    private float[] mProjectionMatrix = new float[MATRIX_SIZE];
+
+    // GL programs
+    private int mTextureProgram;
+
+    // GL buffer containing BOX_COORDINATES
+    private int mBoxCoordinates;
+
+    // Handle indices -- common
+    private static final int INDEX_POSITION = 0;
+    private static final int INDEX_MATRIX = 1;
+
+    // Handle indices -- texture
+    private static final int INDEX_TEXTURE_MATRIX = 2;
+    private static final int INDEX_TEXTURE_SAMPLER = 3;
+    private static final int INDEX_ALPHA = 4;
+
+    private abstract static class ShaderParameter {
+        public int handle;
+        protected final String mName;
+
+        public ShaderParameter(String name) {
+            mName = name;
+        }
+
+        public abstract void loadHandle(int program);
+    }
+
+    private static class UniformShaderParameter extends ShaderParameter {
+        public UniformShaderParameter(String name) {
+            super(name);
+        }
+
+        @Override
+        public void loadHandle(int program) {
+            handle = GLES20.glGetUniformLocation(program, mName);
+            checkError();
+        }
+    }
+
+    private static class AttributeShaderParameter extends ShaderParameter {
+        public AttributeShaderParameter(String name) {
+            super(name);
+        }
+
+        @Override
+        public void loadHandle(int program) {
+            handle = GLES20.glGetAttribLocation(program, mName);
+            checkError();
+        }
+    }
+
+    private ShaderParameter[] mTextureParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+
+    private final IntArray mUnboundTextures = new IntArray();
+
+    // Temporary variables used within calculations
+    private final float[] mTempMatrix = new float[32];
+    private final RectF mTempSourceRect = new RectF();
+    private final RectF mTempTargetRect = new RectF();
+    private final float[] mTempTextureMatrix = new float[MATRIX_SIZE];
+    private final int[] mTempIntArray = new int[1];
+
+    private static final GLId mGLId = new GLES20IdImpl();
+
+    public GLES20Canvas() {
+        Matrix.setIdentityM(mTempTextureMatrix, 0);
+        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+
+        FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
+        mBoxCoordinates = uploadBuffer(boxBuffer);
+
+        int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
+        int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
+
+        mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
+                mTextureParameters);
+        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+        checkError();
+    }
+
+    private static FloatBuffer createBuffer(float[] values) {
+        // First create an nio buffer, then create a VBO from it.
+        int size = values.length * FLOAT_SIZE;
+        FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
+                .asFloatBuffer();
+        buffer.put(values, 0, values.length).position(0);
+        return buffer;
+    }
+
+    private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
+        int program = GLES20.glCreateProgram();
+        checkError();
+        if (program == 0) {
+            throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
+        }
+        GLES20.glAttachShader(program, vertexShader);
+        checkError();
+        GLES20.glAttachShader(program, fragmentShader);
+        checkError();
+        GLES20.glLinkProgram(program);
+        checkError();
+        int[] mLinkStatus = mTempIntArray;
+        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
+        if (mLinkStatus[0] != GLES20.GL_TRUE) {
+            Log.e(TAG, "Could not link program: ");
+            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+            GLES20.glDeleteProgram(program);
+            program = 0;
+        }
+        for (int i = 0; i < params.length; i++) {
+            params[i].loadHandle(program);
+        }
+        return program;
+    }
+
+    private static int loadShader(int type, String shaderCode) {
+        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+        int shader = GLES20.glCreateShader(type);
+
+        // add the source code to the shader and compile it
+        GLES20.glShaderSource(shader, shaderCode);
+        checkError();
+        GLES20.glCompileShader(shader);
+        checkError();
+
+        return shader;
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+        GLES20.glViewport(0, 0, mWidth, mHeight);
+        checkError();
+        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+        Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
+        Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
+        Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
+    }
+
+    @Override
+    public void clearBuffer() {
+        GLES20.glClearColor(0f, 0f, 0f, 1f);
+        checkError();
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        checkError();
+    }
+
+    // This is a faster version of translate(x, y, z) because
+    // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
+    // (3) we unroll the loop
+    @Override
+    public void translate(float x, float y) {
+        int index = mCurrentMatrixIndex;
+        float[] m = mMatrices;
+        m[index + 12] += m[index + 0] * x + m[index + 4] * y;
+        m[index + 13] += m[index + 1] * x + m[index + 5] * y;
+        m[index + 14] += m[index + 2] * x + m[index + 6] * y;
+        m[index + 15] += m[index + 3] * x + m[index + 7] * y;
+    }
+
+    @Override
+    public void rotate(float angle, float x, float y, float z) {
+        if (angle == 0f) {
+            return;
+        }
+        float[] temp = mTempMatrix;
+        Matrix.setRotateM(temp, 0, angle, x, y, z);
+        float[] matrix = mMatrices;
+        int index = mCurrentMatrixIndex;
+        Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0);
+        System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE);
+    }
+
+    @Override
+    public void save(int saveFlags) {
+        boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+        if (saveMatrix) {
+            int currentIndex = mCurrentMatrixIndex;
+            mCurrentMatrixIndex += MATRIX_SIZE;
+            if (mMatrices.length <= mCurrentMatrixIndex) {
+                mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2);
+            }
+            System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE);
+        }
+        mSaveFlags.add(saveFlags);
+    }
+
+    @Override
+    public void restore() {
+        int restoreFlags = mSaveFlags.removeLast();
+        boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+        if (restoreMatrix) {
+            mCurrentMatrixIndex -= MATRIX_SIZE;
+        }
+    }
+
+    private void setPosition(ShaderParameter[] params, int offset) {
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
+        checkError();
+        GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
+                GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
+        checkError();
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+        checkError();
+    }
+
+    private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
+            float height) {
+        setMatrix(params, x, y, width, height);
+        int positionHandle = params[INDEX_POSITION].handle;
+        GLES20.glEnableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glDrawArrays(type, 0, count);
+        checkError();
+        GLES20.glDisableVertexAttribArray(positionHandle);
+        checkError();
+    }
+
+    private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
+        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
+        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
+        Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
+        GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
+        checkError();
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, int x, int y, int width, int height) {
+        if (width <= 0 || height <= 0) {
+            return;
+        }
+        copyTextureCoordinates(texture, mTempSourceRect);
+        mTempTargetRect.set(x, y, x + width, y + height);
+        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+    }
+
+    private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) {
+        outRect.set(0, 0, texture.getWidth(), texture.getHeight());
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) {
+            return;
+        }
+        mTempSourceRect.set(source);
+        mTempTargetRect.set(target);
+
+        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+    }
+
+    private void drawTextureRect(BasicTexture texture, RectF source, RectF target) {
+        setTextureMatrix(source);
+        drawTextureRect(texture, mTempTextureMatrix, target);
+    }
+
+    private void setTextureMatrix(RectF source) {
+        mTempTextureMatrix[0] = source.width();
+        mTempTextureMatrix[5] = source.height();
+        mTempTextureMatrix[12] = source.left;
+        mTempTextureMatrix[13] = source.top;
+    }
+
+    // This function changes the source coordinate to the texture coordinates.
+    // It also clips the source and target coordinates if it is beyond the
+    // bound of the texture.
+    private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) {
+        int width = texture.getWidth();
+        int height = texture.getHeight();
+        int texWidth = texture.getTextureWidth();
+        int texHeight = texture.getTextureHeight();
+        // Convert to texture coordinates
+        source.left /= texWidth;
+        source.right /= texWidth;
+        source.top /= texHeight;
+        source.bottom /= texHeight;
+
+        // Clip if the rendering range is beyond the bound of the texture.
+        float xBound = (float) width / texWidth;
+        if (source.right > xBound) {
+            target.right = target.left + target.width() * (xBound - source.left) / source.width();
+            source.right = xBound;
+        }
+        float yBound = (float) height / texHeight;
+        if (source.bottom > yBound) {
+            target.bottom = target.top + target.height() * (yBound - source.top) / source.height();
+            source.bottom = yBound;
+        }
+    }
+
+    private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) {
+        ShaderParameter[] params = prepareTexture(texture);
+        setPosition(params, OFFSET_FILL_RECT);
+        GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0);
+        checkError();
+        draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
+                target.width(), target.height());
+    }
+
+    private ShaderParameter[] prepareTexture(BasicTexture texture) {
+        ShaderParameter[] params;
+        int program;
+        params = mTextureParameters;
+        program = mTextureProgram;
+        prepareTexture(texture, program, params);
+        return params;
+    }
+
+    private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
+        deleteRecycledResources();
+        GLES20.glUseProgram(program);
+        checkError();
+        GLES20.glDisable(GLES20.GL_BLEND);
+        checkError();
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        checkError();
+        texture.onBind(this);
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
+        checkError();
+        GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
+        checkError();
+        GLES20.glUniform1f(params[INDEX_ALPHA].handle, 1);
+        checkError();
+    }
+
+    @Override
+    public boolean unloadTexture(BasicTexture texture) {
+        boolean unload = texture.isLoaded();
+        if (unload) {
+            synchronized (mUnboundTextures) {
+                mUnboundTextures.add(texture.getId());
+            }
+        }
+        return unload;
+    }
+
+    @Override
+    public void deleteRecycledResources() {
+        synchronized (mUnboundTextures) {
+            IntArray ids = mUnboundTextures;
+            if (mUnboundTextures.size() > 0) {
+                mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+        }
+    }
+
+    @Override
+    public void setTextureParameters(BasicTexture texture) {
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
+        checkError();
+        GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+    }
+
+    @Override
+    public void initializeTextureSize(BasicTexture texture, int format, int type) {
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
+        checkError();
+        int width = texture.getTextureWidth();
+        int height = texture.getTextureHeight();
+        GLES20.glTexImage2D(GL_TARGET, 0, format, width, height, 0, format, type, null);
+    }
+
+    @Override
+    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
+        checkError();
+        GLUtils.texImage2D(GL_TARGET, 0, bitmap, 0);
+    }
+
+    @Override
+    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+            int format, int type) {
+        GLES20.glBindTexture(GL_TARGET, texture.getId());
+        checkError();
+        GLUtils.texSubImage2D(GL_TARGET, 0, xOffset, yOffset, bitmap, format, type);
+    }
+
+    @Override
+    public int uploadBuffer(FloatBuffer buf) {
+        return uploadBuffer(buf, FLOAT_SIZE);
+    }
+
+    private int uploadBuffer(Buffer buffer, int elementSize) {
+        mGLId.glGenBuffers(1, mTempIntArray, 0);
+        checkError();
+        int bufferId = mTempIntArray[0];
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
+        checkError();
+        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
+                GLES20.GL_STATIC_DRAW);
+        checkError();
+        return bufferId;
+    }
+
+    public static void checkError() {
+        int error = GLES20.glGetError();
+        if (error != 0) {
+            Throwable t = new Throwable();
+            Log.e(TAG, "GL error: " + error, t);
+        }
+    }
+
+    @Override
+    public GLId getGLId() {
+        return mGLId;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
new file mode 100644
index 0000000..6cd7149
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
@@ -0,0 +1,42 @@
+package com.android.gallery3d.glrenderer;
+
+import android.opengl.GLES20;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+public class GLES20IdImpl implements GLId {
+    private final int[] mTempIntArray = new int[1];
+
+    @Override
+    public int generateTexture() {
+        GLES20.glGenTextures(1, mTempIntArray, 0);
+        GLES20Canvas.checkError();
+        return mTempIntArray[0];
+    }
+
+    @Override
+    public void glGenBuffers(int n, int[] buffers, int offset) {
+        GLES20.glGenBuffers(n, buffers, offset);
+        GLES20Canvas.checkError();
+    }
+
+    @Override
+    public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+        GLES20.glDeleteTextures(n, textures, offset);
+        GLES20Canvas.checkError();
+    }
+
+
+    @Override
+    public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+        GLES20.glDeleteBuffers(n, buffers, offset);
+        GLES20Canvas.checkError();
+    }
+
+    @Override
+    public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+        GLES20.glDeleteFramebuffers(n, buffers, offset);
+        GLES20Canvas.checkError();
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLId.java b/src/com/android/gallery3d/glrenderer/GLId.java
new file mode 100644
index 0000000..3cec558
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLId.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+// This mimics corresponding GL functions.
+public interface GLId {
+    public int generateTexture();
+
+    public void glGenBuffers(int n, int[] buffers, int offset);
+
+    public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
+
+    public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
+
+    public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
+}
diff --git a/src/com/android/gallery3d/glrenderer/IntArray.java b/src/com/android/gallery3d/glrenderer/IntArray.java
new file mode 100644
index 0000000..f123624
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/IntArray.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+public class IntArray {
+    private static final int INIT_CAPACITY = 8;
+
+    private int mData[] = new int[INIT_CAPACITY];
+    private int mSize = 0;
+
+    public void add(int value) {
+        if (mData.length == mSize) {
+            int temp[] = new int[mSize + mSize];
+            System.arraycopy(mData, 0, temp, 0, mSize);
+            mData = temp;
+        }
+        mData[mSize++] = value;
+    }
+
+    public int removeLast() {
+        mSize--;
+        return mData[mSize];
+    }
+
+    public int size() {
+        return mSize;
+    }
+
+    // For testing only
+    public int[] toArray(int[] result) {
+        if (result == null || result.length < mSize) {
+            result = new int[mSize];
+        }
+        System.arraycopy(mData, 0, result, 0, mSize);
+        return result;
+    }
+
+    public int[] getInternalArray() {
+        return mData;
+    }
+
+    public void clear() {
+        mSize = 0;
+        if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/Texture.java b/src/com/android/gallery3d/glrenderer/Texture.java
new file mode 100644
index 0000000..e71a379
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/Texture.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+
+// Texture is a rectangular image which can be drawn on GLCanvas.
+// The isOpaque() function gives a hint about whether the texture is opaque,
+// so the drawing can be done faster.
+//
+// This is the current texture hierarchy:
+//
+// Texture
+// -- BasicTexture
+//    -- UploadedTexture
+//       -- BitmapTexture
+//       -- Tile
+//
+public interface Texture {
+    public int getWidth();
+    public int getHeight();
+    public void draw(GLCanvas canvas, int x, int y);
+    public void draw(GLCanvas canvas, int x, int y, int w, int h);
+}
diff --git a/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
new file mode 100644
index 0000000..607e2a9
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.opengl.GLUtils;
+import android.util.Pair;
+
+import com.android.gallery3d.common.Utils;
+
+import java.util.HashMap;
+
+// UploadedTextures use a Bitmap for the content of the texture.
+//
+// Subclasses should implement onGetBitmap() to provide the Bitmap and
+// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
+// is not needed anymore.
+//
+// isContentValid() is meaningful only when the isLoaded() returns true.
+// It means whether the content needs to be updated.
+//
+// The user of this class should call recycle() when the texture is not
+// needed anymore.
+//
+// By default an UploadedTexture is opaque (so it can be drawn faster without
+// blending). The user or subclass can override it using setOpaque().
+public abstract class UploadedTexture extends BasicTexture {
+
+    // To prevent keeping allocation the borders, we store those used borders here.
+    // Since the length will be power of two, it won't use too much memory.
+    private static HashMap<BorderKey, Bitmap> sBorderLines = new HashMap<BorderKey, Bitmap>();
+
+    private static class BorderKey extends Pair<Config, Integer> {
+        public BorderKey(Config config, boolean vertical, int length) {
+            super(config, vertical ? length : -length);
+        }
+    }
+
+    private boolean mContentValid = true;
+    protected Bitmap mBitmap;
+
+    protected UploadedTexture() {
+        super(null, 0, STATE_UNLOADED);
+    }
+
+    private static Bitmap getBorderLine(boolean vertical, Config config, int length) {
+        BorderKey key = new BorderKey(config, vertical, length);
+        Bitmap bitmap = sBorderLines.get(key);
+        if (bitmap == null) {
+            bitmap = vertical
+                    ? Bitmap.createBitmap(1, length, config)
+                    : Bitmap.createBitmap(length, 1, config);
+            sBorderLines.put(key, bitmap);
+        }
+        return bitmap;
+    }
+
+    private Bitmap getBitmap() {
+        if (mBitmap == null) {
+            mBitmap = onGetBitmap();
+            int w = mBitmap.getWidth();
+            int h = mBitmap.getHeight();
+            if (mWidth == UNSPECIFIED) {
+                setSize(w, h);
+            }
+        }
+        return mBitmap;
+    }
+
+    private void freeBitmap() {
+        Utils.assertTrue(mBitmap != null);
+        onFreeBitmap(mBitmap);
+        mBitmap = null;
+    }
+
+    @Override
+    public int getWidth() {
+        if (mWidth == UNSPECIFIED) getBitmap();
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        if (mWidth == UNSPECIFIED) getBitmap();
+        return mHeight;
+    }
+
+    protected abstract Bitmap onGetBitmap();
+
+    protected abstract void onFreeBitmap(Bitmap bitmap);
+
+    protected void invalidateContent() {
+        if (mBitmap != null) freeBitmap();
+        mContentValid = false;
+        mWidth = UNSPECIFIED;
+        mHeight = UNSPECIFIED;
+    }
+
+    /**
+     * Whether the content on GPU is valid.
+     */
+    public boolean isContentValid() {
+        return isLoaded() && mContentValid;
+    }
+
+    /**
+     * Updates the content on GPU's memory.
+     * @param canvas
+     */
+    public void updateContent(GLCanvas canvas) {
+        if (!isLoaded()) {
+            uploadToCanvas(canvas);
+        } else if (!mContentValid) {
+            Bitmap bitmap = getBitmap();
+            int format = GLUtils.getInternalFormat(bitmap);
+            int type = GLUtils.getType(bitmap);
+            canvas.texSubImage2D(this, 0, 0, bitmap, format, type);
+            freeBitmap();
+            mContentValid = true;
+        }
+    }
+
+    private void uploadToCanvas(GLCanvas canvas) {
+        Bitmap bitmap = getBitmap();
+        if (bitmap != null) {
+            try {
+                int bWidth = bitmap.getWidth();
+                int bHeight = bitmap.getHeight();
+                int texWidth = getTextureWidth();
+                int texHeight = getTextureHeight();
+
+                Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
+
+                // Upload the bitmap to a new texture.
+                mId = canvas.getGLId().generateTexture();
+                canvas.setTextureParameters(this);
+
+                if (bWidth == texWidth && bHeight == texHeight) {
+                    canvas.initializeTexture(this, bitmap);
+                } else {
+                    int format = GLUtils.getInternalFormat(bitmap);
+                    int type = GLUtils.getType(bitmap);
+                    Config config = bitmap.getConfig();
+
+                    canvas.initializeTextureSize(this, format, type);
+                    canvas.texSubImage2D(this, 0, 0, bitmap, format, type);
+
+                    // Right border
+                    if (bWidth < texWidth) {
+                        Bitmap line = getBorderLine(true, config, texHeight);
+                        canvas.texSubImage2D(this, bWidth, 0, line, format, type);
+                    }
+
+                    // Bottom border
+                    if (bHeight < texHeight) {
+                        Bitmap line = getBorderLine(false, config, texWidth);
+                        canvas.texSubImage2D(this, 0, bHeight, line, format, type);
+                    }
+                }
+            } finally {
+                freeBitmap();
+            }
+            // Update texture state.
+            setAssociatedCanvas(canvas);
+            mState = STATE_LOADED;
+            mContentValid = true;
+        } else {
+            mState = STATE_ERROR;
+            throw new RuntimeException("Texture load fail, no bitmap");
+        }
+    }
+
+    @Override
+    protected boolean onBind(GLCanvas canvas) {
+        updateContent(canvas);
+        return isContentValid();
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        if (mBitmap != null) freeBitmap();
+    }
+}
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
new file mode 100644
index 0000000..7870b26
--- /dev/null
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2013 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.photos;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.opengl.GLUtils;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.gallery3d.common.ExifOrientation;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.photos.views.TiledImageRenderer;
+import com.android.wallpaperpicker.common.InputStreamProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+interface SimpleBitmapRegionDecoder {
+    int getWidth();
+    int getHeight();
+    Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options);
+}
+
+class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder {
+    BitmapRegionDecoder mDecoder;
+    private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) {
+        mDecoder = decoder;
+    }
+
+    public static SimpleBitmapRegionDecoderWrapper newInstance(
+            InputStream is, boolean isShareable) {
+        try {
+            BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable);
+            if (d != null) {
+                return new SimpleBitmapRegionDecoderWrapper(d);
+            }
+        } catch (IOException e) {
+            Log.w("BitmapRegionTileSource", "getting decoder failed", e);
+            return null;
+        }
+        return null;
+    }
+    public int getWidth() {
+        return mDecoder.getWidth();
+    }
+    public int getHeight() {
+        return mDecoder.getHeight();
+    }
+    public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
+        return mDecoder.decodeRegion(wantRegion, options);
+    }
+}
+
+class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder {
+    Bitmap mBuffer;
+    Canvas mTempCanvas;
+    Paint mTempPaint;
+    private DumbBitmapRegionDecoder(Bitmap b) {
+        mBuffer = b;
+    }
+    public static DumbBitmapRegionDecoder newInstance(InputStream is) {
+        Bitmap b = BitmapFactory.decodeStream(is);
+        if (b != null) {
+            return new DumbBitmapRegionDecoder(b);
+        }
+        return null;
+    }
+    public int getWidth() {
+        return mBuffer.getWidth();
+    }
+    public int getHeight() {
+        return mBuffer.getHeight();
+    }
+    public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
+        if (mTempCanvas == null) {
+            mTempCanvas = new Canvas();
+            mTempPaint = new Paint();
+            mTempPaint.setFilterBitmap(true);
+        }
+        int sampleSize = Math.max(options.inSampleSize, 1);
+        Bitmap newBitmap = Bitmap.createBitmap(
+                wantRegion.width() / sampleSize,
+                wantRegion.height() / sampleSize,
+                Bitmap.Config.ARGB_8888);
+        mTempCanvas.setBitmap(newBitmap);
+        mTempCanvas.save();
+        mTempCanvas.scale(1f / sampleSize, 1f / sampleSize);
+        mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint);
+        mTempCanvas.restore();
+        mTempCanvas.setBitmap(null);
+        return newBitmap;
+    }
+}
+
+/**
+ * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
+ * {@link BitmapRegionDecoder} to wrap a local file
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
+
+    private static final String TAG = "BitmapRegionTileSource";
+
+    private static final int GL_SIZE_LIMIT = 2048;
+    // This must be no larger than half the size of the GL_SIZE_LIMIT
+    // due to decodePreview being allowed to be up to 2x the size of the target
+    private static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
+
+    public static abstract class BitmapSource {
+        private SimpleBitmapRegionDecoder mDecoder;
+        private Bitmap mPreview;
+        private int mRotation;
+        public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
+        private State mState = State.NOT_LOADED;
+
+        /** Returns whether loading was successful. */
+        public boolean loadInBackground(InBitmapProvider bitmapProvider) {
+            mRotation = getExifRotation();
+            mDecoder = loadBitmapRegionDecoder();
+            if (mDecoder == null) {
+                mState = State.ERROR_LOADING;
+                return false;
+            } else {
+                int width = mDecoder.getWidth();
+                int height = mDecoder.getHeight();
+
+                BitmapFactory.Options opts = new BitmapFactory.Options();
+                opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                opts.inPreferQualityOverSpeed = true;
+
+                float scale = (float) MAX_PREVIEW_SIZE / Math.max(width, height);
+                opts.inSampleSize = Utils.computeSampleSizeLarger(scale);
+                opts.inJustDecodeBounds = false;
+                opts.inMutable = true;
+
+                if (bitmapProvider != null) {
+                    int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
+                    Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
+                    if (reusableBitmap != null) {
+                        // Try loading with reusable bitmap
+                        opts.inBitmap = reusableBitmap;
+                        try {
+                            mPreview = loadPreviewBitmap(opts);
+                        } catch (IllegalArgumentException e) {
+                            Log.d(TAG, "Unable to reuse bitmap", e);
+                            opts.inBitmap = null;
+                            mPreview = null;
+                        }
+                    }
+                }
+                if (mPreview == null) {
+                    mPreview = loadPreviewBitmap(opts);
+                }
+                if (mPreview == null) {
+                    mState = State.ERROR_LOADING;
+                    return false;
+                }
+
+                // Verify that the bitmap can be used on GL surface
+                try {
+                    GLUtils.getInternalFormat(mPreview);
+                    GLUtils.getType(mPreview);
+                    mState = State.LOADED;
+                } catch (IllegalArgumentException e) {
+                    Log.d(TAG, "Image cannot be rendered on a GL surface", e);
+                    mState = State.ERROR_LOADING;
+                }
+                return mState == State.LOADED;
+            }
+        }
+
+        public State getLoadingState() {
+            return mState;
+        }
+
+        public SimpleBitmapRegionDecoder getBitmapRegionDecoder() {
+            return mDecoder;
+        }
+
+        public Bitmap getPreviewBitmap() {
+            return mPreview;
+        }
+
+        public int getRotation() {
+            return mRotation;
+        }
+
+        public abstract int getExifRotation();
+        public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
+        public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
+
+        public interface InBitmapProvider {
+            Bitmap forPixelCount(int count);
+        }
+    }
+
+    public static class InputStreamSource extends BitmapSource {
+        private final InputStreamProvider mStreamProvider;
+        private final Context mContext;
+
+        public InputStreamSource(Context context, Uri uri) {
+            this(InputStreamProvider.fromUri(context, uri), context);
+        }
+
+        public InputStreamSource(Resources res, int resId, Context context) {
+            this(InputStreamProvider.fromResource(res, resId), context);
+        }
+
+        public InputStreamSource(InputStreamProvider streamProvider, Context context) {
+            mStreamProvider = streamProvider;
+            mContext = context;
+        }
+
+        @Override
+        public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
+            try {
+                InputStream is = mStreamProvider.newStreamNotNull();
+                SimpleBitmapRegionDecoder regionDecoder =
+                        SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
+                Utils.closeSilently(is);
+                if (regionDecoder == null) {
+                    is = mStreamProvider.newStreamNotNull();
+                    regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
+                    Utils.closeSilently(is);
+                }
+                return regionDecoder;
+            } catch (IOException e) {
+                Log.e("InputStreamSource", "Failed to load stream", e);
+                return null;
+            }
+        }
+
+        @Override
+        public int getExifRotation() {
+            return mStreamProvider.getRotationFromExif(mContext);
+        }
+
+        @Override
+        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
+            try {
+                InputStream is = mStreamProvider.newStreamNotNull();
+                Bitmap b = BitmapFactory.decodeStream(is, null, options);
+                Utils.closeSilently(is);
+                return b;
+            } catch (IOException | OutOfMemoryError e) {
+                Log.e("InputStreamSource", "Failed to load stream", e);
+                return null;
+            }
+        }
+    }
+
+    public static class FilePathBitmapSource extends InputStreamSource {
+        private String mPath;
+        public FilePathBitmapSource(File file, Context context) {
+            super(context, Uri.fromFile(file));
+            mPath = file.getAbsolutePath();
+        }
+
+        @Override
+        public int getExifRotation() {
+            return ExifOrientation.readRotation(mPath);
+        }
+    }
+
+    SimpleBitmapRegionDecoder mDecoder;
+    int mWidth;
+    int mHeight;
+    int mTileSize;
+    private BasicTexture mPreview;
+    private final int mRotation;
+
+    // For use only by getTile
+    private Rect mWantRegion = new Rect();
+    private BitmapFactory.Options mOptions;
+
+    public BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage) {
+        mTileSize = TiledImageRenderer.suggestedTileSize(context);
+        mRotation = source.getRotation();
+        mDecoder = source.getBitmapRegionDecoder();
+        if (mDecoder != null) {
+            mWidth = mDecoder.getWidth();
+            mHeight = mDecoder.getHeight();
+            mOptions = new BitmapFactory.Options();
+            mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
+            mOptions.inPreferQualityOverSpeed = true;
+            mOptions.inTempStorage = tempStorage;
+
+            Bitmap preview = source.getPreviewBitmap();
+            if (preview != null &&
+                    preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
+                    mPreview = new BitmapTexture(preview);
+            } else {
+                Log.w(TAG, String.format(
+                        "Failed to create preview of apropriate size! "
+                        + " in: %dx%d, out: %dx%d",
+                        mWidth, mHeight,
+                        preview == null ? -1 : preview.getWidth(),
+                        preview == null ? -1 : preview.getHeight()));
+            }
+        }
+    }
+
+    public Bitmap getBitmap() {
+        return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
+    }
+
+    @Override
+    public int getTileSize() {
+        return mTileSize;
+    }
+
+    @Override
+    public int getImageWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getImageHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public BasicTexture getPreview() {
+        return mPreview;
+    }
+
+    @Override
+    public int getRotation() {
+        return mRotation;
+    }
+
+    @Override
+    public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+        int tileSize = getTileSize();
+        int t = tileSize << level;
+        mWantRegion.set(x, y, x + t, y + t);
+
+        if (bitmap == null) {
+            bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+        }
+
+        mOptions.inSampleSize = (1 << level);
+        mOptions.inBitmap = bitmap;
+
+        try {
+            bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
+        } finally {
+            if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
+                mOptions.inBitmap = null;
+            }
+        }
+
+        if (bitmap == null) {
+            Log.w("BitmapRegionTileSource", "fail in decoding region");
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java
new file mode 100644
index 0000000..0ba2fc0
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageRenderer.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2013 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.photos.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.support.v4.util.Pools.Pool;
+import android.support.v4.util.Pools.SynchronizedPool;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
+
+/**
+ * Handles laying out, decoding, and drawing of tiles in GL
+ */
+public class TiledImageRenderer {
+    public static final int SIZE_UNKNOWN = -1;
+
+    private static final String TAG = "TiledImageRenderer";
+    private static final int UPLOAD_LIMIT = 1;
+
+    /*
+     *  This is the tile state in the CPU side.
+     *  Life of a Tile:
+     *      ACTIVATED (initial state)
+     *              --> IN_QUEUE - by queueForDecode()
+     *              --> RECYCLED - by recycleTile()
+     *      IN_QUEUE --> DECODING - by decodeTile()
+     *               --> RECYCLED - by recycleTile)
+     *      DECODING --> RECYCLING - by recycleTile()
+     *               --> DECODED  - by decodeTile()
+     *               --> DECODE_FAIL - by decodeTile()
+     *      RECYCLING --> RECYCLED - by decodeTile()
+     *      DECODED --> ACTIVATED - (after the decoded bitmap is uploaded)
+     *      DECODED --> RECYCLED - by recycleTile()
+     *      DECODE_FAIL -> RECYCLED - by recycleTile()
+     *      RECYCLED --> ACTIVATED - by obtainTile()
+     */
+    private static final int STATE_ACTIVATED = 0x01;
+    private static final int STATE_IN_QUEUE = 0x02;
+    private static final int STATE_DECODING = 0x04;
+    private static final int STATE_DECODED = 0x08;
+    private static final int STATE_DECODE_FAIL = 0x10;
+    private static final int STATE_RECYCLING = 0x20;
+    private static final int STATE_RECYCLED = 0x40;
+
+    private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64);
+
+    // TILE_SIZE must be 2^N
+    private int mTileSize;
+
+    private TileSource mModel;
+    private BasicTexture mPreview;
+    protected int mLevelCount;  // cache the value of mScaledBitmaps.length
+
+    // The mLevel variable indicates which level of bitmap we should use.
+    // Level 0 means the original full-sized bitmap, and a larger value means
+    // a smaller scaled bitmap (The width and height of each scaled bitmap is
+    // half size of the previous one). If the value is in [0, mLevelCount), we
+    // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value
+    // is mLevelCount
+    private int mLevel = 0;
+
+    private int mOffsetX;
+    private int mOffsetY;
+
+    private int mUploadQuota;
+    private boolean mRenderComplete;
+
+    private final RectF mSourceRect = new RectF();
+    private final RectF mTargetRect = new RectF();
+
+    private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>();
+
+    // The following three queue are guarded by mQueueLock
+    private final Object mQueueLock = new Object();
+    private final TileQueue mRecycledQueue = new TileQueue();
+    private final TileQueue mUploadQueue = new TileQueue();
+    private final TileQueue mDecodeQueue = new TileQueue();
+
+    // The width and height of the full-sized bitmap
+    protected int mImageWidth = SIZE_UNKNOWN;
+    protected int mImageHeight = SIZE_UNKNOWN;
+
+    protected int mCenterX;
+    protected int mCenterY;
+    protected float mScale;
+    protected int mRotation;
+
+    private boolean mLayoutTiles;
+
+    // Temp variables to avoid memory allocation
+    private final Rect mTileRange = new Rect();
+    private final Rect mActiveRange[] = {new Rect(), new Rect()};
+
+    private TileDecoder mTileDecoder;
+    private boolean mBackgroundTileUploaded;
+
+    private int mViewWidth, mViewHeight;
+    private View mParent;
+
+    /**
+     * Interface for providing tiles to a {@link TiledImageRenderer}
+     */
+    public static interface TileSource {
+
+        /**
+         * If the source does not care about the tile size, it should use
+         * {@link TiledImageRenderer#suggestedTileSize(Context)}
+         */
+        public int getTileSize();
+        public int getImageWidth();
+        public int getImageHeight();
+        public int getRotation();
+
+        /**
+         * Return a Preview image if available. This will be used as the base layer
+         * if higher res tiles are not yet available
+         */
+        public BasicTexture getPreview();
+
+        /**
+         * The tile returned by this method can be specified this way: Assuming
+         * the image size is (width, height), first take the intersection of (0,
+         * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
+         * in extending the region, we found some part of the region is outside
+         * the image, those pixels are filled with black.
+         *
+         * If level > 0, it does the same operation on a down-scaled version of
+         * the original image (down-scaled by a factor of 2^level), but (x, y)
+         * still refers to the coordinate on the original image.
+         *
+         * The method would be called by the decoder thread.
+         */
+        public Bitmap getTile(int level, int x, int y, Bitmap reuse);
+    }
+
+    public static int suggestedTileSize(Context context) {
+        return isHighResolution(context) ? 512 : 256;
+    }
+
+    private static boolean isHighResolution(Context context) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        return metrics.heightPixels > 2048 ||  metrics.widthPixels > 2048;
+    }
+
+    public TiledImageRenderer(View parent) {
+        mParent = parent;
+        mTileDecoder = new TileDecoder();
+        mTileDecoder.start();
+    }
+
+    public int getViewWidth() {
+        return mViewWidth;
+    }
+
+    public int getViewHeight() {
+        return mViewHeight;
+    }
+
+    private void invalidate() {
+        mParent.postInvalidate();
+    }
+
+    public void setModel(TileSource model, int rotation) {
+        if (mModel != model) {
+            mModel = model;
+            notifyModelInvalidated();
+        }
+        if (mRotation != rotation) {
+            mRotation = rotation;
+            mLayoutTiles = true;
+        }
+    }
+
+    private void calculateLevelCount() {
+        if (mPreview != null) {
+            mLevelCount = Math.max(0, Utils.ceilLog2(
+                mImageWidth / (float) mPreview.getWidth()));
+        } else {
+            int levels = 1;
+            int maxDim = Math.max(mImageWidth, mImageHeight);
+            int t = mTileSize;
+            while (t < maxDim) {
+                t <<= 1;
+                levels++;
+            }
+            mLevelCount = levels;
+        }
+    }
+
+    public void notifyModelInvalidated() {
+        invalidateTiles();
+        if (mModel == null) {
+            mImageWidth = 0;
+            mImageHeight = 0;
+            mLevelCount = 0;
+            mPreview = null;
+        } else {
+            mImageWidth = mModel.getImageWidth();
+            mImageHeight = mModel.getImageHeight();
+            mPreview = mModel.getPreview();
+            mTileSize = mModel.getTileSize();
+            calculateLevelCount();
+        }
+        mLayoutTiles = true;
+    }
+
+    public void setViewSize(int width, int height) {
+        mViewWidth = width;
+        mViewHeight = height;
+    }
+
+    public void setPosition(int centerX, int centerY, float scale) {
+        if (mCenterX == centerX && mCenterY == centerY
+                && mScale == scale) {
+            return;
+        }
+        mCenterX = centerX;
+        mCenterY = centerY;
+        mScale = scale;
+        mLayoutTiles = true;
+    }
+
+    // Prepare the tiles we want to use for display.
+    //
+    // 1. Decide the tile level we want to use for display.
+    // 2. Decide the tile levels we want to keep as texture (in addition to
+    //    the one we use for display).
+    // 3. Recycle unused tiles.
+    // 4. Activate the tiles we want.
+    private void layoutTiles() {
+        if (mViewWidth == 0 || mViewHeight == 0 || !mLayoutTiles) {
+            return;
+        }
+        mLayoutTiles = false;
+
+        // The tile levels we want to keep as texture is in the range
+        // [fromLevel, endLevel).
+        int fromLevel;
+        int endLevel;
+
+        // We want to use a texture larger than or equal to the display size.
+        mLevel = Utils.clamp(Utils.floorLog2(1f / mScale), 0, mLevelCount);
+
+        // We want to keep one more tile level as texture in addition to what
+        // we use for display. So it can be faster when the scale moves to the
+        // next level. We choose the level closest to the current scale.
+        if (mLevel != mLevelCount) {
+            Rect range = mTileRange;
+            getRange(range, mCenterX, mCenterY, mLevel, mScale, mRotation);
+            mOffsetX = Math.round(mViewWidth / 2f + (range.left - mCenterX) * mScale);
+            mOffsetY = Math.round(mViewHeight / 2f + (range.top - mCenterY) * mScale);
+            fromLevel = mScale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel;
+        } else {
+            // Activate the tiles of the smallest two levels.
+            fromLevel = mLevel - 2;
+            mOffsetX = Math.round(mViewWidth / 2f - mCenterX * mScale);
+            mOffsetY = Math.round(mViewHeight / 2f - mCenterY * mScale);
+        }
+
+        fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2));
+        endLevel = Math.min(fromLevel + 2, mLevelCount);
+
+        Rect range[] = mActiveRange;
+        for (int i = fromLevel; i < endLevel; ++i) {
+            getRange(range[i - fromLevel], mCenterX, mCenterY, i, mRotation);
+        }
+
+        // If rotation is transient, don't update the tile.
+        if (mRotation % 90 != 0) {
+            return;
+        }
+
+        synchronized (mQueueLock) {
+            mDecodeQueue.clean();
+            mUploadQueue.clean();
+            mBackgroundTileUploaded = false;
+
+            // Recycle unused tiles: if the level of the active tile is outside the
+            // range [fromLevel, endLevel) or not in the visible range.
+            int n = mActiveTiles.size();
+            for (int i = 0; i < n; i++) {
+                Tile tile = mActiveTiles.valueAt(i);
+                int level = tile.mTileLevel;
+                if (level < fromLevel || level >= endLevel
+                        || !range[level - fromLevel].contains(tile.mX, tile.mY)) {
+                    mActiveTiles.removeAt(i);
+                    i--;
+                    n--;
+                    recycleTile(tile);
+                }
+            }
+        }
+
+        for (int i = fromLevel; i < endLevel; ++i) {
+            int size = mTileSize << i;
+            Rect r = range[i - fromLevel];
+            for (int y = r.top, bottom = r.bottom; y < bottom; y += size) {
+                for (int x = r.left, right = r.right; x < right; x += size) {
+                    activateTile(x, y, i);
+                }
+            }
+        }
+        invalidate();
+    }
+
+    private void invalidateTiles() {
+        synchronized (mQueueLock) {
+            mDecodeQueue.clean();
+            mUploadQueue.clean();
+
+            // TODO(xx): disable decoder
+            int n = mActiveTiles.size();
+            for (int i = 0; i < n; i++) {
+                Tile tile = mActiveTiles.valueAt(i);
+                recycleTile(tile);
+            }
+            mActiveTiles.clear();
+        }
+    }
+
+    private void getRange(Rect out, int cX, int cY, int level, int rotation) {
+        getRange(out, cX, cY, level, 1f / (1 << (level + 1)), rotation);
+    }
+
+    // If the bitmap is scaled by the given factor "scale", return the
+    // rectangle containing visible range. The left-top coordinate returned is
+    // aligned to the tile boundary.
+    //
+    // (cX, cY) is the point on the original bitmap which will be put in the
+    // center of the ImageViewer.
+    private void getRange(Rect out,
+            int cX, int cY, int level, float scale, int rotation) {
+
+        double radians = Math.toRadians(-rotation);
+        double w = mViewWidth;
+        double h = mViewHeight;
+
+        double cos = Math.cos(radians);
+        double sin = Math.sin(radians);
+        int width = (int) Math.ceil(Math.max(
+                Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h)));
+        int height = (int) Math.ceil(Math.max(
+                Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h)));
+
+        int left = (int) Math.floor(cX - width / (2f * scale));
+        int top = (int) Math.floor(cY - height / (2f * scale));
+        int right = (int) Math.ceil(left + width / scale);
+        int bottom = (int) Math.ceil(top + height / scale);
+
+        // align the rectangle to tile boundary
+        int size = mTileSize << level;
+        left = Math.max(0, size * (left / size));
+        top = Math.max(0, size * (top / size));
+        right = Math.min(mImageWidth, right);
+        bottom = Math.min(mImageHeight, bottom);
+
+        out.set(left, top, right, bottom);
+    }
+
+    public void freeTextures() {
+        mLayoutTiles = true;
+
+        mTileDecoder.finishAndWait();
+        synchronized (mQueueLock) {
+            mUploadQueue.clean();
+            mDecodeQueue.clean();
+            Tile tile = mRecycledQueue.pop();
+            while (tile != null) {
+                tile.recycle();
+                tile = mRecycledQueue.pop();
+            }
+        }
+
+        int n = mActiveTiles.size();
+        for (int i = 0; i < n; i++) {
+            Tile texture = mActiveTiles.valueAt(i);
+            texture.recycle();
+        }
+        mActiveTiles.clear();
+        mTileRange.set(0, 0, 0, 0);
+
+        while (sTilePool.acquire() != null) {}
+    }
+
+    public boolean draw(GLCanvas canvas) {
+        layoutTiles();
+        uploadTiles(canvas);
+
+        mUploadQuota = UPLOAD_LIMIT;
+        mRenderComplete = true;
+
+        int level = mLevel;
+        int rotation = mRotation;
+        int flags = 0;
+        if (rotation != 0) {
+            flags |= GLCanvas.SAVE_FLAG_MATRIX;
+        }
+
+        if (flags != 0) {
+            canvas.save(flags);
+            if (rotation != 0) {
+                int centerX = mViewWidth / 2, centerY = mViewHeight / 2;
+                canvas.translate(centerX, centerY);
+                canvas.rotate(rotation, 0, 0, 1);
+                canvas.translate(-centerX, -centerY);
+            }
+        }
+        try {
+            if (level != mLevelCount) {
+                int size = (mTileSize << level);
+                float length = size * mScale;
+                Rect r = mTileRange;
+
+                for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) {
+                    float y = mOffsetY + i * length;
+                    for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) {
+                        float x = mOffsetX + j * length;
+                        drawTile(canvas, tx, ty, level, x, y, length);
+                    }
+                }
+            } else if (mPreview != null) {
+                mPreview.draw(canvas, mOffsetX, mOffsetY,
+                        Math.round(mImageWidth * mScale),
+                        Math.round(mImageHeight * mScale));
+            }
+        } finally {
+            if (flags != 0) {
+                canvas.restore();
+            }
+        }
+
+        if (mRenderComplete) {
+            if (!mBackgroundTileUploaded) {
+                uploadBackgroundTiles(canvas);
+            }
+        } else {
+            invalidate();
+        }
+        return mRenderComplete || mPreview != null;
+    }
+
+    private void uploadBackgroundTiles(GLCanvas canvas) {
+        mBackgroundTileUploaded = true;
+        int n = mActiveTiles.size();
+        for (int i = 0; i < n; i++) {
+            Tile tile = mActiveTiles.valueAt(i);
+            if (!tile.isContentValid()) {
+                queueForDecode(tile);
+            }
+        }
+    }
+
+   private void queueForDecode(Tile tile) {
+       synchronized (mQueueLock) {
+           if (tile.mTileState == STATE_ACTIVATED) {
+               tile.mTileState = STATE_IN_QUEUE;
+               if (mDecodeQueue.push(tile)) {
+                   mQueueLock.notifyAll();
+               }
+           }
+       }
+    }
+
+    private void decodeTile(Tile tile) {
+        synchronized (mQueueLock) {
+            if (tile.mTileState != STATE_IN_QUEUE) {
+                return;
+            }
+            tile.mTileState = STATE_DECODING;
+        }
+        boolean decodeComplete = tile.decode();
+        synchronized (mQueueLock) {
+            if (tile.mTileState == STATE_RECYCLING) {
+                tile.mTileState = STATE_RECYCLED;
+                if (tile.mDecodedTile != null) {
+                    sTilePool.release(tile.mDecodedTile);
+                    tile.mDecodedTile = null;
+                }
+                mRecycledQueue.push(tile);
+                return;
+            }
+            tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL;
+            if (!decodeComplete) {
+                return;
+            }
+            mUploadQueue.push(tile);
+        }
+        invalidate();
+    }
+
+    private Tile obtainTile(int x, int y, int level) {
+        synchronized (mQueueLock) {
+            Tile tile = mRecycledQueue.pop();
+            if (tile != null) {
+                tile.mTileState = STATE_ACTIVATED;
+                tile.update(x, y, level);
+                return tile;
+            }
+            return new Tile(x, y, level);
+        }
+    }
+
+    private void recycleTile(Tile tile) {
+        synchronized (mQueueLock) {
+            if (tile.mTileState == STATE_DECODING) {
+                tile.mTileState = STATE_RECYCLING;
+                return;
+            }
+            tile.mTileState = STATE_RECYCLED;
+            if (tile.mDecodedTile != null) {
+                sTilePool.release(tile.mDecodedTile);
+                tile.mDecodedTile = null;
+            }
+            mRecycledQueue.push(tile);
+        }
+    }
+
+    private void activateTile(int x, int y, int level) {
+        long key = makeTileKey(x, y, level);
+        Tile tile = mActiveTiles.get(key);
+        if (tile != null) {
+            if (tile.mTileState == STATE_IN_QUEUE) {
+                tile.mTileState = STATE_ACTIVATED;
+            }
+            return;
+        }
+        tile = obtainTile(x, y, level);
+        mActiveTiles.put(key, tile);
+    }
+
+    private Tile getTile(int x, int y, int level) {
+        return mActiveTiles.get(makeTileKey(x, y, level));
+    }
+
+    private static long makeTileKey(int x, int y, int level) {
+        long result = x;
+        result = (result << 16) | y;
+        result = (result << 16) | level;
+        return result;
+    }
+
+    private void uploadTiles(GLCanvas canvas) {
+        int quota = UPLOAD_LIMIT;
+        Tile tile = null;
+        while (quota > 0) {
+            synchronized (mQueueLock) {
+                tile = mUploadQueue.pop();
+            }
+            if (tile == null) {
+                break;
+            }
+            if (!tile.isContentValid()) {
+                if (tile.mTileState == STATE_DECODED) {
+                    tile.updateContent(canvas);
+                    --quota;
+                } else {
+                    Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState);
+                }
+            }
+        }
+        if (tile != null) {
+            invalidate();
+        }
+    }
+
+    // Draw the tile to a square at canvas that locates at (x, y) and
+    // has a side length of length.
+    private void drawTile(GLCanvas canvas,
+            int tx, int ty, int level, float x, float y, float length) {
+        RectF source = mSourceRect;
+        RectF target = mTargetRect;
+        target.set(x, y, x + length, y + length);
+        source.set(0, 0, mTileSize, mTileSize);
+
+        Tile tile = getTile(tx, ty, level);
+        if (tile != null) {
+            if (!tile.isContentValid()) {
+                if (tile.mTileState == STATE_DECODED) {
+                    if (mUploadQuota > 0) {
+                        --mUploadQuota;
+                        tile.updateContent(canvas);
+                    } else {
+                        mRenderComplete = false;
+                    }
+                } else if (tile.mTileState != STATE_DECODE_FAIL){
+                    mRenderComplete = false;
+                    queueForDecode(tile);
+                }
+            }
+            if (drawTile(tile, canvas, source, target)) {
+                return;
+            }
+        }
+        if (mPreview != null) {
+            int size = mTileSize << level;
+            float scaleX = (float) mPreview.getWidth() / mImageWidth;
+            float scaleY = (float) mPreview.getHeight() / mImageHeight;
+            source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX,
+                    (ty + size) * scaleY);
+            canvas.drawTexture(mPreview, source, target);
+        }
+    }
+
+    private boolean drawTile(
+            Tile tile, GLCanvas canvas, RectF source, RectF target) {
+        while (true) {
+            if (tile.isContentValid()) {
+                canvas.drawTexture(tile, source, target);
+                return true;
+            }
+
+            // Parent can be divided to four quads and tile is one of the four.
+            Tile parent = tile.getParentTile();
+            if (parent == null) {
+                return false;
+            }
+            if (tile.mX == parent.mX) {
+                source.left /= 2f;
+                source.right /= 2f;
+            } else {
+                source.left = (mTileSize + source.left) / 2f;
+                source.right = (mTileSize + source.right) / 2f;
+            }
+            if (tile.mY == parent.mY) {
+                source.top /= 2f;
+                source.bottom /= 2f;
+            } else {
+                source.top = (mTileSize + source.top) / 2f;
+                source.bottom = (mTileSize + source.bottom) / 2f;
+            }
+            tile = parent;
+        }
+    }
+
+    private class Tile extends UploadedTexture {
+        public int mX;
+        public int mY;
+        public int mTileLevel;
+        public Tile mNext;
+        public Bitmap mDecodedTile;
+        public volatile int mTileState = STATE_ACTIVATED;
+
+        public Tile(int x, int y, int level) {
+            mX = x;
+            mY = y;
+            mTileLevel = level;
+        }
+
+        @Override
+        protected void onFreeBitmap(Bitmap bitmap) {
+            sTilePool.release(bitmap);
+        }
+
+        boolean decode() {
+            // Get a tile from the original image. The tile is down-scaled
+            // by (1 << mTilelevel) from a region in the original image.
+            try {
+                Bitmap reuse = sTilePool.acquire();
+                if (reuse != null && reuse.getWidth() != mTileSize) {
+                    reuse = null;
+                }
+                mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse);
+            } catch (Throwable t) {
+                Log.w(TAG, "fail to decode tile", t);
+            }
+            return mDecodedTile != null;
+        }
+
+        @Override
+        protected Bitmap onGetBitmap() {
+            Utils.assertTrue(mTileState == STATE_DECODED);
+
+            // We need to override the width and height, so that we won't
+            // draw beyond the boundaries.
+            int rightEdge = ((mImageWidth - mX) >> mTileLevel);
+            int bottomEdge = ((mImageHeight - mY) >> mTileLevel);
+            setSize(Math.min(mTileSize, rightEdge), Math.min(mTileSize, bottomEdge));
+
+            Bitmap bitmap = mDecodedTile;
+            mDecodedTile = null;
+            mTileState = STATE_ACTIVATED;
+            return bitmap;
+        }
+
+        // We override getTextureWidth() and getTextureHeight() here, so the
+        // texture can be re-used for different tiles regardless of the actual
+        // size of the tile (which may be small because it is a tile at the
+        // boundary).
+        @Override
+        public int getTextureWidth() {
+            return mTileSize;
+        }
+
+        @Override
+        public int getTextureHeight() {
+            return mTileSize;
+        }
+
+        public void update(int x, int y, int level) {
+            mX = x;
+            mY = y;
+            mTileLevel = level;
+            invalidateContent();
+        }
+
+        public Tile getParentTile() {
+            if (mTileLevel + 1 == mLevelCount) {
+                return null;
+            }
+            int size = mTileSize << (mTileLevel + 1);
+            int x = size * (mX / size);
+            int y = size * (mY / size);
+            return getTile(x, y, mTileLevel + 1);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("tile(%s, %s, %s / %s)",
+                    mX / mTileSize, mY / mTileSize, mLevel, mLevelCount);
+        }
+    }
+
+    private static class TileQueue {
+        private Tile mHead;
+
+        public Tile pop() {
+            Tile tile = mHead;
+            if (tile != null) {
+                mHead = tile.mNext;
+            }
+            return tile;
+        }
+
+        public boolean push(Tile tile) {
+            if (contains(tile)) {
+                Log.w(TAG, "Attempting to add a tile already in the queue!");
+                return false;
+            }
+            boolean wasEmpty = mHead == null;
+            tile.mNext = mHead;
+            mHead = tile;
+            return wasEmpty;
+        }
+
+        private boolean contains(Tile tile) {
+            Tile other = mHead;
+            while (other != null) {
+                if (other == tile) {
+                    return true;
+                }
+                other = other.mNext;
+            }
+            return false;
+        }
+
+        public void clean() {
+            mHead = null;
+        }
+    }
+
+    private class TileDecoder extends Thread {
+
+        public void finishAndWait() {
+            interrupt();
+            try {
+                join();
+            } catch (InterruptedException e) {
+                Log.w(TAG, "Interrupted while waiting for TileDecoder thread to finish!");
+            }
+        }
+
+        private Tile waitForTile() throws InterruptedException {
+            synchronized (mQueueLock) {
+                while (true) {
+                    Tile tile = mDecodeQueue.pop();
+                    if (tile != null) {
+                        return tile;
+                    }
+                    mQueueLock.wait();
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (!isInterrupted()) {
+                    Tile tile = waitForTile();
+                    decodeTile(tile);
+                }
+            } catch (InterruptedException ex) {
+                // We were finished
+            }
+        }
+
+    }
+}
diff --git a/src/com/android/photos/views/TiledImageView.java b/src/com/android/photos/views/TiledImageView.java
new file mode 100644
index 0000000..85a8394
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageView.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2013 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.photos.views;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLSurfaceView.Renderer;
+import android.util.AttributeSet;
+import android.view.Choreographer;
+import android.view.Choreographer.FrameCallback;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLES20Canvas;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}.
+ */
+public class TiledImageView extends FrameLayout {
+
+    private GLSurfaceView mGLSurfaceView;
+    private boolean mInvalPending = false;
+    private FrameCallback mFrameCallback;
+
+    protected static class ImageRendererWrapper {
+        // Guarded by locks
+        public float scale;
+        public int centerX, centerY;
+        public int rotation;
+        public TileSource source;
+        Runnable isReadyCallback;
+
+        // GL thread only
+        TiledImageRenderer image;
+    }
+
+    private float[] mValues = new float[9];
+
+    // -------------------------
+    // Guarded by mLock
+    // -------------------------
+    protected Object mLock = new Object();
+    protected ImageRendererWrapper mRenderer;
+
+    public TiledImageView(Context context) {
+        this(context, null);
+    }
+
+    public TiledImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mRenderer = new ImageRendererWrapper();
+        mRenderer.image = new TiledImageRenderer(this);
+        mGLSurfaceView = new GLSurfaceView(context);
+        mGLSurfaceView.setEGLContextClientVersion(2);
+        mGLSurfaceView.setRenderer(new TileRenderer());
+        mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+        addView(mGLSurfaceView, new LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        // need to update inner view's visibility because it seems like we're causing it to draw
+        // from {@link #dispatchDraw} or {@link #invalidate} even if we are invisible.
+        mGLSurfaceView.setVisibility(visibility);
+    }
+
+    public void destroy() {
+        mGLSurfaceView.queueEvent(mFreeTextures);
+    }
+
+    private Runnable mFreeTextures = new Runnable() {
+
+        @Override
+        public void run() {
+            mRenderer.image.freeTextures();
+        }
+    };
+
+    public void onPause() {
+        mGLSurfaceView.onPause();
+    }
+
+    public void onResume() {
+        mGLSurfaceView.onResume();
+    }
+
+    public void setTileSource(TileSource source, Runnable isReadyCallback) {
+        synchronized (mLock) {
+            mRenderer.source = source;
+            mRenderer.isReadyCallback = isReadyCallback;
+            mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
+            mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
+            mRenderer.rotation = source != null ? source.getRotation() : 0;
+            mRenderer.scale = 0;
+            updateScaleIfNecessaryLocked(mRenderer);
+        }
+        invalidate();
+    }
+
+    public TileSource getTileSource() {
+        return mRenderer.source;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right,
+            int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        synchronized (mLock) {
+            updateScaleIfNecessaryLocked(mRenderer);
+        }
+    }
+
+    private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
+        if (renderer == null || renderer.source == null
+                || renderer.scale > 0 || getWidth() == 0) {
+            return;
+        }
+        renderer.scale = Math.min(
+                (float) getWidth() / (float) renderer.source.getImageWidth(),
+                (float) getHeight() / (float) renderer.source.getImageHeight());
+    }
+
+    @Override
+    public void invalidate() {
+        invalOnVsync();
+    }
+
+    private void invalOnVsync() {
+        if (!mInvalPending) {
+            mInvalPending = true;
+            if (mFrameCallback == null) {
+                mFrameCallback = new FrameCallback() {
+                    @Override
+                    public void doFrame(long frameTimeNanos) {
+                        mInvalPending = false;
+                        mGLSurfaceView.requestRender();
+                    }
+                };
+            }
+            Choreographer.getInstance().postFrameCallback(mFrameCallback);
+        }
+    }
+
+    private class TileRenderer implements Renderer {
+
+        private GLES20Canvas mCanvas;
+
+        @Override
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            mCanvas = new GLES20Canvas();
+            BasicTexture.invalidateAllTextures();
+            mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
+        }
+
+        @Override
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            mCanvas.setSize(width, height);
+            mRenderer.image.setViewSize(width, height);
+        }
+
+        @Override
+        public void onDrawFrame(GL10 gl) {
+            mCanvas.clearBuffer();
+            Runnable readyCallback;
+            synchronized (mLock) {
+                readyCallback = mRenderer.isReadyCallback;
+                mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
+                mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
+                        mRenderer.scale);
+            }
+            boolean complete = mRenderer.image.draw(mCanvas);
+            if (complete && readyCallback != null) {
+                synchronized (mLock) {
+                    // Make sure we don't trample on a newly set callback/source
+                    // if it changed while we were rendering
+                    if (mRenderer.isReadyCallback == readyCallback) {
+                        mRenderer.isReadyCallback = null;
+                    }
+                }
+                if (readyCallback != null) {
+                    post(readyCallback);
+                }
+            }
+        }
+
+    }
+}
diff --git a/src/com/android/wallpaperpicker/AlphaDisableableButton.java b/src/com/android/wallpaperpicker/AlphaDisableableButton.java
new file mode 100644
index 0000000..cc249e4
--- /dev/null
+++ b/src/com/android/wallpaperpicker/AlphaDisableableButton.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 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.wallpaperpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * A Button which becomes translucent when it is disabled
+ */
+public class AlphaDisableableButton extends Button {
+    public static float DISABLED_ALPHA_VALUE = 0.4f;
+    public AlphaDisableableButton(Context context) {
+        this(context, null);
+    }
+
+    public AlphaDisableableButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AlphaDisableableButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        if(enabled) {
+            setAlpha(1.0f);
+        } else {
+            setAlpha(DISABLED_ALPHA_VALUE);
+        }
+    }
+}
diff --git a/src/com/android/wallpaperpicker/CheckableFrameLayout.java b/src/com/android/wallpaperpicker/CheckableFrameLayout.java
new file mode 100644
index 0000000..3163315
--- /dev/null
+++ b/src/com/android/wallpaperpicker/CheckableFrameLayout.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 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.wallpaperpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.FrameLayout;
+
+public class CheckableFrameLayout extends FrameLayout implements Checkable {
+    private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
+    boolean mChecked;
+
+    public CheckableFrameLayout(Context context) {
+        super(context);
+    }
+
+    public CheckableFrameLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CheckableFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public boolean isChecked() {
+        return mChecked;
+    }
+
+    public void setChecked(boolean checked) {
+        if (checked != mChecked) {
+            mChecked = checked;
+            refreshDrawableState();
+        }
+    }
+
+    public void toggle() {
+        setChecked(!mChecked);
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (isChecked()) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+}
diff --git a/src/com/android/wallpaperpicker/CropView.java b/src/com/android/wallpaperpicker/CropView.java
new file mode 100644
index 0000000..7f37c2a
--- /dev/null
+++ b/src/com/android/wallpaperpicker/CropView.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2013 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.wallpaperpicker;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import com.android.photos.views.TiledImageRenderer.TileSource;
+import com.android.photos.views.TiledImageView;
+
+public class CropView extends TiledImageView implements OnScaleGestureListener {
+
+    private ScaleGestureDetector mScaleGestureDetector;
+    private long mTouchDownTime;
+    private float mFirstX, mFirstY;
+    private float mLastX, mLastY;
+    private float mCenterX, mCenterY;
+    private float mMinScale;
+    private boolean mTouchEnabled = true;
+    private RectF mTempEdges = new RectF();
+    private float[] mTempPoint = new float[] { 0, 0 };
+    private float[] mTempCoef = new float[] { 0, 0 };
+    private float[] mTempAdjustment = new float[] { 0, 0 };
+    private float[] mTempImageDims = new float[] { 0, 0 };
+    private float[] mTempRendererCenter = new float[] { 0, 0 };
+    TouchCallback mTouchCallback;
+    Matrix mRotateMatrix;
+    Matrix mInverseRotateMatrix;
+
+    public interface TouchCallback {
+        void onTouchDown();
+        void onTap();
+        void onTouchUp();
+    }
+
+    public CropView(Context context) {
+        this(context, null);
+    }
+
+    public CropView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mScaleGestureDetector = new ScaleGestureDetector(context, this);
+        mRotateMatrix = new Matrix();
+        mInverseRotateMatrix = new Matrix();
+    }
+
+    private float[] getImageDims() {
+        final float imageWidth = mRenderer.source.getImageWidth();
+        final float imageHeight = mRenderer.source.getImageHeight();
+        float[] imageDims = mTempImageDims;
+        imageDims[0] = imageWidth;
+        imageDims[1] = imageHeight;
+        mRotateMatrix.mapPoints(imageDims);
+        imageDims[0] = Math.abs(imageDims[0]);
+        imageDims[1] = Math.abs(imageDims[1]);
+        return imageDims;
+    }
+
+    private void getEdgesHelper(RectF edgesOut) {
+        final float width = getWidth();
+        final float height = getHeight();
+        final float[] imageDims = getImageDims();
+        final float imageWidth = imageDims[0];
+        final float imageHeight = imageDims[1];
+
+        float initialCenterX = mRenderer.source.getImageWidth() / 2f;
+        float initialCenterY = mRenderer.source.getImageHeight() / 2f;
+
+        float[] rendererCenter = mTempRendererCenter;
+        rendererCenter[0] = mCenterX - initialCenterX;
+        rendererCenter[1] = mCenterY - initialCenterY;
+        mRotateMatrix.mapPoints(rendererCenter);
+        rendererCenter[0] += imageWidth / 2;
+        rendererCenter[1] += imageHeight / 2;
+
+        final float scale = mRenderer.scale;
+        float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
+                * scale + width / 2f;
+        float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
+                * scale + height / 2f;
+        float leftEdge = centerX - imageWidth / 2f * scale;
+        float rightEdge = centerX + imageWidth / 2f * scale;
+        float topEdge = centerY - imageHeight / 2f * scale;
+        float bottomEdge = centerY + imageHeight / 2f * scale;
+
+        edgesOut.left = leftEdge;
+        edgesOut.right = rightEdge;
+        edgesOut.top = topEdge;
+        edgesOut.bottom = bottomEdge;
+    }
+
+    public int getImageRotation() {
+        return mRenderer.rotation;
+    }
+
+    public RectF getCrop() {
+        final RectF edges = mTempEdges;
+        getEdgesHelper(edges);
+        final float scale = mRenderer.scale;
+
+        float cropLeft = -edges.left / scale;
+        float cropTop = -edges.top / scale;
+        float cropRight = cropLeft + getWidth() / scale;
+        float cropBottom = cropTop + getHeight() / scale;
+
+        return new RectF(cropLeft, cropTop, cropRight, cropBottom);
+    }
+
+    public Point getSourceDimensions() {
+        return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
+    }
+
+    public void setTileSource(TileSource source, Runnable isReadyCallback) {
+        super.setTileSource(source, isReadyCallback);
+        mCenterX = mRenderer.centerX;
+        mCenterY = mRenderer.centerY;
+        mRotateMatrix.reset();
+        mRotateMatrix.setRotate(mRenderer.rotation);
+        mInverseRotateMatrix.reset();
+        mInverseRotateMatrix.setRotate(-mRenderer.rotation);
+        updateMinScale(getWidth(), getHeight(), source, true);
+    }
+
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        updateMinScale(w, h, mRenderer.source, false);
+    }
+
+    public void setScale(float scale) {
+        synchronized (mLock) {
+            mRenderer.scale = scale;
+        }
+    }
+
+    private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
+        synchronized (mLock) {
+            if (resetScale) {
+                mRenderer.scale = 1;
+            }
+            if (source != null) {
+                final float[] imageDims = getImageDims();
+                final float imageWidth = imageDims[0];
+                final float imageHeight = imageDims[1];
+                mMinScale = Math.max(w / imageWidth, h / imageHeight);
+                mRenderer.scale =
+                        Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale);
+            }
+        }
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        return true;
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        // Don't need the lock because this will only fire inside of
+        // onTouchEvent
+        mRenderer.scale *= detector.getScaleFactor();
+        mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
+        invalidate();
+        return true;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+    }
+
+    /**
+     * Offsets wallpaper preview according to the state it will be displayed in upon returning home.
+     * @param offset Ranges from 0 to 1, where 0 is the leftmost parallax and 1 is the rightmost.
+     */
+    public void setParallaxOffset(float offset, RectF crop) {
+        offset = Math.max(0, Math.min(offset, 1)); // Make sure the offset is in the correct range.
+        float screenWidth = getWidth() / mRenderer.scale;
+        mCenterX = screenWidth / 2 + offset * (crop.width() - screenWidth) + crop.left;
+        updateCenter();
+    }
+
+    public void moveToLeft() {
+        if (getWidth() == 0 || getHeight() == 0) {
+            final ViewTreeObserver observer = getViewTreeObserver();
+            observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+                    public void onGlobalLayout() {
+                        moveToLeft();
+                        getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                    }
+                });
+        }
+        final RectF edges = mTempEdges;
+        getEdgesHelper(edges);
+        final float scale = mRenderer.scale;
+        mCenterX += Math.ceil(edges.left / scale);
+        updateCenter();
+    }
+
+    private void updateCenter() {
+        mRenderer.centerX = Math.round(mCenterX);
+        mRenderer.centerY = Math.round(mCenterY);
+    }
+
+    public void setTouchEnabled(boolean enabled) {
+        mTouchEnabled = enabled;
+    }
+
+    public void setTouchCallback(TouchCallback cb) {
+        mTouchCallback = cb;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int action = event.getActionMasked();
+        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+        // Determine focal point
+        float sumX = 0, sumY = 0;
+        final int count = event.getPointerCount();
+        for (int i = 0; i < count; i++) {
+            if (skipIndex == i)
+                continue;
+            sumX += event.getX(i);
+            sumY += event.getY(i);
+        }
+        final int div = pointerUp ? count - 1 : count;
+        float x = sumX / div;
+        float y = sumY / div;
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            mFirstX = x;
+            mFirstY = y;
+            mTouchDownTime = System.currentTimeMillis();
+            if (mTouchCallback != null) {
+                mTouchCallback.onTouchDown();
+            }
+        } else if (action == MotionEvent.ACTION_UP) {
+            ViewConfiguration config = ViewConfiguration.get(getContext());
+
+            float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
+            float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
+            long now = System.currentTimeMillis();
+            if (mTouchCallback != null) {
+                // only do this if it's a small movement
+                if (squaredDist < slop &&
+                        now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
+                    mTouchCallback.onTap();
+                }
+                mTouchCallback.onTouchUp();
+            }
+        }
+
+        if (!mTouchEnabled) {
+            return true;
+        }
+
+        synchronized (mLock) {
+            mScaleGestureDetector.onTouchEvent(event);
+            switch (action) {
+                case MotionEvent.ACTION_MOVE:
+                    float[] point = mTempPoint;
+                    point[0] = (mLastX - x) / mRenderer.scale;
+                    point[1] = (mLastY - y) / mRenderer.scale;
+                    mInverseRotateMatrix.mapPoints(point);
+                    mCenterX += point[0];
+                    mCenterY += point[1];
+                    updateCenter();
+                    invalidate();
+                    break;
+            }
+            if (mRenderer.source != null) {
+                // Adjust position so that the wallpaper covers the entire area
+                // of the screen
+                final RectF edges = mTempEdges;
+                getEdgesHelper(edges);
+                final float scale = mRenderer.scale;
+
+                float[] coef = mTempCoef;
+                coef[0] = 1;
+                coef[1] = 1;
+                mRotateMatrix.mapPoints(coef);
+                float[] adjustment = mTempAdjustment;
+                mTempAdjustment[0] = 0;
+                mTempAdjustment[1] = 0;
+                if (edges.left > 0) {
+                    adjustment[0] = edges.left / scale;
+                } else if (edges.right < getWidth()) {
+                    adjustment[0] = (edges.right - getWidth()) / scale;
+                }
+                if (edges.top > 0) {
+                    adjustment[1] = (float) Math.ceil(edges.top / scale);
+                } else if (edges.bottom < getHeight()) {
+                    adjustment[1] = (edges.bottom - getHeight()) / scale;
+                }
+                for (int dim = 0; dim <= 1; dim++) {
+                    if (coef[dim] > 0) adjustment[dim] = (float) Math.ceil(adjustment[dim]);
+                }
+
+                mInverseRotateMatrix.mapPoints(adjustment);
+                mCenterX += adjustment[0];
+                mCenterY += adjustment[1];
+                updateCenter();
+            }
+        }
+
+        mLastX = x;
+        mLastY = y;
+        return true;
+    }
+}
diff --git a/src/com/android/wallpaperpicker/DrawableTileSource.java b/src/com/android/wallpaperpicker/DrawableTileSource.java
new file mode 100644
index 0000000..6902044
--- /dev/null
+++ b/src/com/android/wallpaperpicker/DrawableTileSource.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 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.wallpaperpicker;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.photos.views.TiledImageRenderer;
+
+public class DrawableTileSource implements TiledImageRenderer.TileSource {
+    private static final int GL_SIZE_LIMIT = 2048;
+    // This must be no larger than half the size of the GL_SIZE_LIMIT
+    // due to decodePreview being allowed to be up to 2x the size of the target
+    public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
+
+    private int mTileSize;
+    private int mPreviewSize;
+    private Drawable mDrawable;
+    private BitmapTexture mPreview;
+
+    public DrawableTileSource(Context context, Drawable d, int previewSize) {
+        mTileSize = TiledImageRenderer.suggestedTileSize(context);
+        mDrawable = d;
+        mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
+    }
+
+    @Override
+    public int getTileSize() {
+        return mTileSize;
+    }
+
+    @Override
+    public int getImageWidth() {
+        return mDrawable.getIntrinsicWidth();
+    }
+
+    @Override
+    public int getImageHeight() {
+        return mDrawable.getIntrinsicHeight();
+    }
+
+    @Override
+    public int getRotation() {
+        return 0;
+    }
+
+    @Override
+    public BasicTexture getPreview() {
+        if (mPreviewSize == 0) {
+            return null;
+        }
+        if (mPreview == null){
+            float width = getImageWidth();
+            float height = getImageHeight();
+            while (width > MAX_PREVIEW_SIZE || height > MAX_PREVIEW_SIZE) {
+                width /= 2;
+                height /= 2;
+            }
+            Bitmap b = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(b);
+            mDrawable.setBounds(new Rect(0, 0, (int) width, (int) height));
+            mDrawable.draw(c);
+            c.setBitmap(null);
+            mPreview = new BitmapTexture(b);
+        }
+        return mPreview;
+    }
+
+    @Override
+    public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+        int tileSize = getTileSize();
+        if (bitmap == null) {
+            bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+        }
+        Canvas c = new Canvas(bitmap);
+        Rect bounds = new Rect(0, 0, getImageWidth(), getImageHeight());
+        bounds.offset(-x, -y);
+        mDrawable.setBounds(bounds);
+        mDrawable.draw(c);
+        c.setBitmap(null);
+        return bitmap;
+    }
+}
diff --git a/src/com/android/wallpaperpicker/SavedWallpaperImages.java b/src/com/android/wallpaperpicker/SavedWallpaperImages.java
new file mode 100644
index 0000000..7498cb6
--- /dev/null
+++ b/src/com/android/wallpaperpicker/SavedWallpaperImages.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2013 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.wallpaperpicker;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.wallpaperpicker.tileinfo.FileWallpaperInfo;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SavedWallpaperImages {
+
+    private static String TAG = "SavedWallpaperImages";
+
+    public static class SavedWallpaperInfo extends FileWallpaperInfo {
+
+        private int mDbId;
+
+        public SavedWallpaperInfo(int dbId, File target, Drawable thumb) {
+            super(target, thumb);
+            mDbId = dbId;
+        }
+
+        @Override
+        public void onDelete(WallpaperPickerActivity a) {
+            a.getSavedImages().deleteImage(mDbId);
+        }
+    }
+
+    private final ImageDb mDb;
+    private final Context mContext;
+
+    public SavedWallpaperImages(Context context) {
+        // We used to store the saved images in the cache directory, but that meant they'd get
+        // deleted sometimes-- move them to the data directory
+        ImageDb.moveFromCacheDirectoryIfNecessary(context);
+        mDb = new ImageDb(context);
+        mContext = context;
+    }
+
+    public List<SavedWallpaperInfo> loadThumbnailsAndImageIdList() {
+        List<SavedWallpaperInfo> result = new ArrayList<SavedWallpaperInfo>();
+
+        SQLiteDatabase db = mDb.getReadableDatabase();
+        Cursor c = db.query(ImageDb.TABLE_NAME,
+                new String[] { ImageDb.COLUMN_ID,
+                    ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
+                    ImageDb.COLUMN_IMAGE_FILENAME}, // cols to return
+                null, // select query
+                null, // args to select query
+                null,
+                null,
+                ImageDb.COLUMN_ID + " DESC",
+                null);
+
+        while (c.moveToNext()) {
+            String filename = c.getString(1);
+            File file = new File(mContext.getFilesDir(), filename);
+
+            Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
+            if (thumb != null) {
+                result.add(new SavedWallpaperInfo(c.getInt(0),
+                        new File(mContext.getFilesDir(), c.getString(2)),
+                        new BitmapDrawable(mContext.getResources(), thumb)));
+            }
+        }
+        c.close();
+        return result;
+    }
+
+    public void deleteImage(int id) {
+        SQLiteDatabase db = mDb.getWritableDatabase();
+
+        Cursor result = db.query(ImageDb.TABLE_NAME,
+                new String[] { ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
+                    ImageDb.COLUMN_IMAGE_FILENAME }, // cols to return
+                ImageDb.COLUMN_ID + " = ?", // select query
+                new String[] { Integer.toString(id) }, // args to select query
+                null,
+                null,
+                null,
+                null);
+        if (result.moveToFirst()) {
+            new File(mContext.getFilesDir(), result.getString(0)).delete();
+            new File(mContext.getFilesDir(), result.getString(1)).delete();
+        }
+        result.close();
+
+        db.delete(ImageDb.TABLE_NAME,
+                ImageDb.COLUMN_ID + " = ?", // SELECT query
+                new String[] {
+                    Integer.toString(id) // args to SELECT query
+                });
+    }
+
+    public void writeImage(Bitmap thumbnail, byte[] imageBytes) {
+        try {
+            File imageFile = File.createTempFile("wallpaper", "", mContext.getFilesDir());
+            FileOutputStream imageFileStream =
+                    mContext.openFileOutput(imageFile.getName(), Context.MODE_PRIVATE);
+            imageFileStream.write(imageBytes);
+            imageFileStream.close();
+
+            File thumbFile = File.createTempFile("wallpaperthumb", "", mContext.getFilesDir());
+            FileOutputStream thumbFileStream =
+                    mContext.openFileOutput(thumbFile.getName(), Context.MODE_PRIVATE);
+            thumbnail.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+            thumbFileStream.close();
+
+            SQLiteDatabase db = mDb.getWritableDatabase();
+            ContentValues values = new ContentValues();
+            values.put(ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME, thumbFile.getName());
+            values.put(ImageDb.COLUMN_IMAGE_FILENAME, imageFile.getName());
+            db.insert(ImageDb.TABLE_NAME, null, values);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed writing images to storage " + e);
+        }
+    }
+
+    private static class ImageDb extends SQLiteOpenHelper {
+        final static int DB_VERSION = 1;
+        final static String TABLE_NAME = "saved_wallpaper_images";
+        final static String COLUMN_ID = "id";
+        final static String COLUMN_IMAGE_THUMBNAIL_FILENAME = "image_thumbnail";
+        final static String COLUMN_IMAGE_FILENAME = "image";
+
+        public ImageDb(Context context) {
+            super(context, context.getDatabasePath(WallpaperFiles.WALLPAPER_IMAGES_DB).getPath(),
+                    null, DB_VERSION);
+        }
+
+        public static void moveFromCacheDirectoryIfNecessary(Context context) {
+            // We used to store the saved images in the cache directory, but that meant they'd get
+            // deleted sometimes-- move them to the data directory
+            File oldSavedImagesFile = new File(context.getCacheDir(),
+                  WallpaperFiles.WALLPAPER_IMAGES_DB);
+            File savedImagesFile = context.getDatabasePath(WallpaperFiles.WALLPAPER_IMAGES_DB);
+            if (oldSavedImagesFile.exists()) {
+                oldSavedImagesFile.renameTo(savedImagesFile);
+            }
+        }
+        @Override
+        public void onCreate(SQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+                    COLUMN_ID + " INTEGER NOT NULL, " +
+                    COLUMN_IMAGE_THUMBNAIL_FILENAME + " TEXT NOT NULL, " +
+                    COLUMN_IMAGE_FILENAME + " TEXT NOT NULL, " +
+                    "PRIMARY KEY (" + COLUMN_ID + " ASC) " +
+                    ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (oldVersion != newVersion) {
+                // Delete all the records; they'll be repopulated as this is a cache
+                db.execSQL("DELETE FROM " + TABLE_NAME);
+            }
+        }
+    }
+}
diff --git a/src/com/android/wallpaperpicker/ToggleOnTapCallback.java b/src/com/android/wallpaperpicker/ToggleOnTapCallback.java
new file mode 100644
index 0000000..d063c2c
--- /dev/null
+++ b/src/com/android/wallpaperpicker/ToggleOnTapCallback.java
@@ -0,0 +1,65 @@
+package com.android.wallpaperpicker;
+
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * Callback that toggles the visibility of the target view when crop view is tapped.
+ */
+public class ToggleOnTapCallback implements CropView.TouchCallback {
+
+    private final View mViewtoToggle;
+
+    private ViewPropertyAnimator mAnim;
+    private boolean mIgnoreNextTap;
+
+    public ToggleOnTapCallback(View viewtoHide) {
+        mViewtoToggle = viewtoHide;
+    }
+
+    @Override
+    public void onTouchDown() {
+        if (mAnim != null) {
+            mAnim.cancel();
+        }
+        if (mViewtoToggle.getAlpha() == 1f) {
+            mIgnoreNextTap = true;
+        }
+
+        mAnim = mViewtoToggle.animate();
+        mAnim.alpha(0f)
+            .setDuration(150)
+            .withEndAction(new Runnable() {
+                public void run() {
+                    mViewtoToggle.setVisibility(View.INVISIBLE);
+                }
+            });
+
+        mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
+        mAnim.start();
+    }
+
+    @Override
+    public void onTouchUp() {
+        mIgnoreNextTap = false;
+    }
+
+    @Override
+    public void onTap() {
+        boolean ignoreTap = mIgnoreNextTap;
+        mIgnoreNextTap = false;
+        if (!ignoreTap) {
+            if (mAnim != null) {
+                mAnim.cancel();
+            }
+            mViewtoToggle.setVisibility(View.VISIBLE);
+            mAnim = mViewtoToggle.animate();
+            mAnim.alpha(1f)
+                 .setDuration(150)
+                 .setInterpolator(new DecelerateInterpolator(0.75f));
+            mAnim.start();
+        }
+    }
+}
diff --git a/src/com/android/wallpaperpicker/WallpaperCropActivity.java b/src/com/android/wallpaperpicker/WallpaperCropActivity.java
new file mode 100644
index 0000000..bdb601b
--- /dev/null
+++ b/src/com/android/wallpaperpicker/WallpaperCropActivity.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2013 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.wallpaperpicker;
+
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.wallpaperpicker.common.CropAndSetWallpaperTask;
+import com.android.gallery3d.common.Utils;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource.InBitmapProvider;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+import com.android.wallpaperpicker.common.DialogUtils;
+import com.android.wallpaperpicker.common.InputStreamProvider;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+public class WallpaperCropActivity extends Activity implements Handler.Callback {
+    private static final String LOGTAG = "WallpaperCropActivity";
+
+    private static final int MSG_LOAD_IMAGE = 1;
+
+    protected CropView mCropView;
+    protected View mProgressView;
+    protected View mSetWallpaperButton;
+
+    private HandlerThread mLoaderThread;
+    private Handler mLoaderHandler;
+    private LoadRequest mCurrentLoadRequest;
+    private byte[] mTempStorageForDecoding = new byte[16 * 1024];
+    // A weak-set of reusable bitmaps
+    private Set<Bitmap> mReusableBitmaps =
+            Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+
+    private final DialogInterface.OnCancelListener mOnDialogCancelListener =
+            new DialogInterface.OnCancelListener() {
+                @Override
+                public void onCancel(DialogInterface dialog) {
+                    showActionBarAndTiles();
+                }
+            };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mLoaderThread = new HandlerThread("wallpaper_loader");
+        mLoaderThread.start();
+        mLoaderHandler = new Handler(mLoaderThread.getLooper(), this);
+
+        init();
+        if (!enableRotation()) {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        }
+    }
+
+    protected void init() {
+        setContentView(R.layout.wallpaper_cropper);
+
+        mCropView = (CropView) findViewById(R.id.cropView);
+        mProgressView = findViewById(R.id.loading);
+
+        Intent cropIntent = getIntent();
+        final Uri imageUri = cropIntent.getData();
+
+        if (imageUri == null) {
+            Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
+            finish();
+            return;
+        }
+
+        // Action bar
+        // Show the custom action bar view
+        final ActionBar actionBar = getActionBar();
+        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
+        actionBar.getCustomView().setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        actionBar.hide();
+                        // Never fade on finish because we return to the app that started us (e.g.
+                        // Photos), not the home screen.
+                        cropImageAndSetWallpaper(imageUri, null, false /* shouldFadeOutOnFinish */);
+                    }
+                });
+        mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
+
+        // Load image in background
+        final BitmapRegionTileSource.InputStreamSource bitmapSource =
+                new BitmapRegionTileSource.InputStreamSource(this, imageUri);
+        mSetWallpaperButton.setEnabled(false);
+        Runnable onLoad = new Runnable() {
+            public void run() {
+                if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
+                    Toast.makeText(WallpaperCropActivity.this, R.string.wallpaper_load_fail,
+                            Toast.LENGTH_LONG).show();
+                    finish();
+                } else {
+                    mSetWallpaperButton.setEnabled(true);
+                }
+            }
+        };
+        setCropViewTileSource(bitmapSource, true, false, null, onLoad);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mCropView != null) {
+            mCropView.destroy();
+        }
+        if (mLoaderThread != null) {
+            mLoaderThread.quit();
+        }
+        super.onDestroy();
+    }
+
+    /**
+     * This is called on {@link #mLoaderThread}
+     */
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    @Override
+    public boolean handleMessage(Message msg) {
+        if (msg.what == MSG_LOAD_IMAGE) {
+            final LoadRequest req = (LoadRequest) msg.obj;
+            final boolean loadSuccess;
+
+            if (req.src == null) {
+                Drawable defaultWallpaper = WallpaperManager.getInstance(this)
+                        .getBuiltInDrawable(mCropView.getWidth(), mCropView.getHeight(),
+                                false, 0.5f, 0.5f);
+
+                if (defaultWallpaper == null) {
+                    loadSuccess = false;
+                    Log.w(LOGTAG, "Null default wallpaper encountered.");
+                } else {
+                    loadSuccess = true;
+                    req.result = new DrawableTileSource(this,
+                            defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
+                }
+            } else {
+                try {
+                    req.src.loadInBackground(new InBitmapProvider() {
+
+                        @Override
+                        public Bitmap forPixelCount(int count) {
+                            Bitmap bitmapToReuse = null;
+                            // Find the smallest bitmap that satisfies the pixel count limit
+                            synchronized (mReusableBitmaps) {
+                                int currentBitmapSize = Integer.MAX_VALUE;
+                                for (Bitmap b : mReusableBitmaps) {
+                                    int bitmapSize = b.getWidth() * b.getHeight();
+                                    if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) {
+                                        bitmapToReuse = b;
+                                        currentBitmapSize = bitmapSize;
+                                    }
+                                }
+
+                                if (bitmapToReuse != null) {
+                                    mReusableBitmaps.remove(bitmapToReuse);
+                                }
+                            }
+                            return bitmapToReuse;
+                        }
+                    });
+                } catch (SecurityException securityException) {
+                    if (isActivityDestroyed()) {
+                        // Temporarily granted permissions are revoked when the activity
+                        // finishes, potentially resulting in a SecurityException here.
+                        // Even though {@link #isDestroyed} might also return true in different
+                        // situations where the configuration changes, we are fine with
+                        // catching these cases here as well.
+                        return true;
+                    } else {
+                        // otherwise it had a different cause and we throw it further
+                        throw securityException;
+                    }
+                }
+
+                req.result = new BitmapRegionTileSource(WallpaperCropActivity.this, req.src,
+                        mTempStorageForDecoding);
+                loadSuccess = req.src.getLoadingState() == BitmapSource.State.LOADED;
+            }
+
+            runOnUiThread(new Runnable() {
+
+                @Override
+                public void run() {
+                    if (req == mCurrentLoadRequest) {
+                        onLoadRequestComplete(req, loadSuccess);
+                    } else {
+                        addReusableBitmap(req.result);
+                    }
+                }
+            });
+            return true;
+        }
+        return false;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public boolean isActivityDestroyed() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed();
+    }
+
+    private void addReusableBitmap(TileSource src) {
+        synchronized (mReusableBitmaps) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+                && src instanceof BitmapRegionTileSource) {
+                Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
+                if (preview != null && preview.isMutable()) {
+                    mReusableBitmaps.add(preview);
+                }
+            }
+        }
+    }
+
+    public DialogInterface.OnCancelListener getOnDialogCancelListener() {
+        return mOnDialogCancelListener;
+    }
+
+    private void showActionBarAndTiles() {
+        getActionBar().show();
+        View wallpaperStrip = findViewById(R.id.wallpaper_strip);
+        if (wallpaperStrip != null) {
+            wallpaperStrip.setVisibility(View.VISIBLE);
+        }
+    }
+
+    protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+        mCurrentLoadRequest = null;
+        if (success) {
+            TileSource oldSrc = mCropView.getTileSource();
+            mCropView.setTileSource(req.result, null);
+            mCropView.setTouchEnabled(req.touchEnabled);
+            if (req.moveToLeft) {
+                mCropView.moveToLeft();
+            }
+            if (req.scaleAndOffsetProvider != null) {
+                TileSource src = req.result;
+                Point wallpaperSize = WallpaperUtils.getDefaultWallpaperSize(
+                        getResources(), getWindowManager());
+                RectF crop = Utils.getMaxCropRect(src.getImageWidth(), src.getImageHeight(),
+                        wallpaperSize.x, wallpaperSize.y, false /* leftAligned */);
+                mCropView.setScale(req.scaleAndOffsetProvider.getScale(wallpaperSize, crop));
+                mCropView.setParallaxOffset(req.scaleAndOffsetProvider.getParallaxOffset(), crop);
+            }
+
+            // Free last image
+            if (oldSrc != null) {
+                // Call yield instead of recycle, as we only want to free GL resource.
+                // We can still reuse the bitmap for decoding any other image.
+                oldSrc.getPreview().yield();
+            }
+            addReusableBitmap(oldSrc);
+        }
+        if (req.postExecute != null) {
+            req.postExecute.run();
+        }
+        mProgressView.setVisibility(View.GONE);
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
+            boolean moveToLeft, CropViewScaleAndOffsetProvider scaleAndOffsetProvider,
+            Runnable postExecute) {
+        final LoadRequest req = new LoadRequest();
+        req.moveToLeft = moveToLeft;
+        req.src = bitmapSource;
+        req.touchEnabled = touchEnabled;
+        req.postExecute = postExecute;
+        req.scaleAndOffsetProvider = scaleAndOffsetProvider;
+        mCurrentLoadRequest = req;
+
+        // Remove any pending requests
+        mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
+        Message.obtain(mLoaderHandler, MSG_LOAD_IMAGE, req).sendToTarget();
+
+        // We don't want to show the spinner every time we load an image, because that would be
+        // annoying; instead, only start showing the spinner if loading the image has taken
+        // longer than 1 sec (ie 1000 ms)
+        mProgressView.postDelayed(new Runnable() {
+            public void run() {
+                if (mCurrentLoadRequest == req) {
+                    mProgressView.setVisibility(View.VISIBLE);
+                }
+            }
+        }, 1000);
+    }
+
+
+    public boolean enableRotation() {
+        return true;
+    }
+
+    public void cropImageAndSetWallpaper(Resources res, int resId, boolean shouldFadeOutOnFinish) {
+        // crop this image and scale it down to the default wallpaper size for
+        // this device
+        InputStreamProvider streamProvider = InputStreamProvider.fromResource(res, resId);
+        Point inSize = mCropView.getSourceDimensions();
+        Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+                getWindowManager());
+        RectF crop = Utils.getMaxCropRect(
+                inSize.x, inSize.y, outSize.x, outSize.y, false);
+        // Passing 0, 0 will cause launcher to revert to using the
+        // default wallpaper size
+        CropAndFinishHandler onEndCrop = new CropAndFinishHandler(new Point(0, 0),
+                shouldFadeOutOnFinish);
+        CropAndSetWallpaperTask cropTask = new CropAndSetWallpaperTask(
+                streamProvider, this, crop, streamProvider.getRotationFromExif(this),
+                outSize.x, outSize.y, onEndCrop);
+        DialogUtils.executeCropTaskAfterPrompt(this, cropTask, getOnDialogCancelListener());
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public void cropImageAndSetWallpaper(Uri uri,
+            CropAndSetWallpaperTask.OnBitmapCroppedHandler onBitmapCroppedHandler,
+            boolean shouldFadeOutOnFinish) {
+        // Get the crop
+        boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+
+        Display d = getWindowManager().getDefaultDisplay();
+
+        Point displaySize = new Point();
+        d.getSize(displaySize);
+        boolean isPortrait = displaySize.x < displaySize.y;
+
+        Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+                getWindowManager());
+        // Get the crop
+        RectF cropRect = mCropView.getCrop();
+
+        Point inSize = mCropView.getSourceDimensions();
+
+        int cropRotation = mCropView.getImageRotation();
+        float cropScale = mCropView.getWidth() / (float) cropRect.width();
+
+        Matrix rotateMatrix = new Matrix();
+        rotateMatrix.setRotate(cropRotation);
+        float[] rotatedInSize = new float[] { inSize.x, inSize.y };
+        rotateMatrix.mapPoints(rotatedInSize);
+        rotatedInSize[0] = Math.abs(rotatedInSize[0]);
+        rotatedInSize[1] = Math.abs(rotatedInSize[1]);
+
+        // due to rounding errors in the cropview renderer the edges can be slightly offset
+        // therefore we ensure that the boundaries are sanely defined
+        cropRect.left = Math.max(0, cropRect.left);
+        cropRect.right = Math.min(rotatedInSize[0], cropRect.right);
+        cropRect.top = Math.max(0, cropRect.top);
+        cropRect.bottom = Math.min(rotatedInSize[1], cropRect.bottom);
+
+        // ADJUST CROP WIDTH
+        // Extend the crop all the way to the right, for parallax
+        // (or all the way to the left, in RTL)
+        float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
+        // Cap the amount of extra width
+        float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width();
+        extraSpace = Math.min(extraSpace, maxExtraSpace);
+
+        if (ltr) {
+            cropRect.right += extraSpace;
+        } else {
+            cropRect.left -= extraSpace;
+        }
+
+        // ADJUST CROP HEIGHT
+        if (isPortrait) {
+            cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale;
+        } else { // LANDSCAPE
+            float extraPortraitHeight =
+                    defaultWallpaperSize.y / cropScale - cropRect.height();
+            float expandHeight =
+                    Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
+                            extraPortraitHeight / 2);
+            cropRect.top -= expandHeight;
+            cropRect.bottom += expandHeight;
+        }
+
+        final int outWidth = (int) Math.round(cropRect.width() * cropScale);
+        final int outHeight = (int) Math.round(cropRect.height() * cropScale);
+        CropAndFinishHandler onEndCrop = new CropAndFinishHandler(new Point(outWidth, outHeight),
+                shouldFadeOutOnFinish);
+
+        CropAndSetWallpaperTask cropTask = new CropAndSetWallpaperTask(
+                InputStreamProvider.fromUri(this, uri), this,
+                cropRect, cropRotation, outWidth, outHeight, onEndCrop) {
+            @Override
+            protected void onPreExecute() {
+                // Give some feedback so user knows something is happening.
+                mProgressView.setVisibility(View.VISIBLE);
+            }
+        };
+        if (onBitmapCroppedHandler != null) {
+            cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
+        }
+        DialogUtils.executeCropTaskAfterPrompt(this, cropTask, getOnDialogCancelListener());
+    }
+
+    public void setBoundsAndFinish(Point bounds, boolean overrideTransition) {
+        WallpaperUtils.saveWallpaperDimensions(bounds.x, bounds.y, this);
+        setResult(Activity.RESULT_OK);
+        finish();
+        if (overrideTransition) {
+            overridePendingTransition(0, R.anim.fade_out);
+        }
+    }
+
+    public class CropAndFinishHandler implements CropAndSetWallpaperTask.OnEndCropHandler {
+        private final Point mBounds;
+        private boolean mShouldFadeOutOnFinish;
+
+        /**
+         * @param shouldFadeOutOnFinish Whether the wallpaper picker should override the default
+         * exit animation to fade out instead. This should only be set to true if the wallpaper
+         * preview will exactly match the actual wallpaper on the page we are returning to.
+         */
+        public CropAndFinishHandler(Point bounds, boolean shouldFadeOutOnFinish) {
+            mBounds = bounds;
+            mShouldFadeOutOnFinish = shouldFadeOutOnFinish;
+        }
+
+        @Override
+        public void run(boolean cropSucceeded) {
+            setBoundsAndFinish(mBounds, cropSucceeded && mShouldFadeOutOnFinish);
+        }
+    }
+
+    static class LoadRequest {
+        BitmapSource src;
+        boolean touchEnabled;
+        boolean moveToLeft;
+        Runnable postExecute;
+        CropViewScaleAndOffsetProvider scaleAndOffsetProvider;
+
+        TileSource result;
+    }
+
+    public interface CropViewScaleAndOffsetProvider {
+        float getScale(Point wallpaperSize, RectF crop);
+        float getParallaxOffset();
+    }
+}
diff --git a/src/com/android/wallpaperpicker/WallpaperFiles.java b/src/com/android/wallpaperpicker/WallpaperFiles.java
new file mode 100644
index 0000000..604f0ac
--- /dev/null
+++ b/src/com/android/wallpaperpicker/WallpaperFiles.java
@@ -0,0 +1,15 @@
+package com.android.wallpaperpicker;
+
+/**
+ * Central list of files the WallpaperPicker writes to the application data directory.
+ */
+public class WallpaperFiles {
+
+  public static final String DEFAULT_WALLPAPER_THUMBNAIL = "default_thumb2.jpg";
+  public static final String DEFAULT_WALLPAPER_THUMBNAIL_OLD = "default_thumb.jpg";
+  public static final String WALLPAPER_CROP_PREFERENCES_KEY =
+          "com.android.launcher3.WallpaperCropActivity";
+
+  public static final String WALLPAPER_IMAGES_DB = "saved_wallpaper_images.db";
+
+}
diff --git a/src/com/android/wallpaperpicker/WallpaperPickerActivity.java b/src/com/android/wallpaperpicker/WallpaperPickerActivity.java
new file mode 100644
index 0000000..f303f93
--- /dev/null
+++ b/src/com/android/wallpaperpicker/WallpaperPickerActivity.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2013 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.wallpaperpicker;
+
+import android.animation.LayoutTransition;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.WindowManager;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+
+import com.android.wallpaperpicker.tileinfo.DefaultWallpaperInfo;
+import com.android.wallpaperpicker.tileinfo.LiveWallpaperInfo;
+import com.android.wallpaperpicker.tileinfo.PickImageInfo;
+import com.android.wallpaperpicker.tileinfo.ResourceWallpaperInfo;
+import com.android.wallpaperpicker.tileinfo.ThirdPartyWallpaperInfo;
+import com.android.wallpaperpicker.tileinfo.UriWallpaperInfo;
+import com.android.wallpaperpicker.tileinfo.WallpaperTileInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WallpaperPickerActivity extends WallpaperCropActivity
+        implements OnClickListener, OnLongClickListener, ActionMode.Callback {
+    static final String TAG = "WallpaperPickerActivity";
+
+    public static final int IMAGE_PICK = 5;
+    public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
+    private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
+    private static final String SELECTED_INDEX = "SELECTED_INDEX";
+    private static final int FLAG_POST_DELAY_MILLIS = 200;
+
+    private View mSelectedTile;
+
+    private LinearLayout mWallpapersView;
+    private HorizontalScrollView mWallpaperScrollContainer;
+    private View mWallpaperStrip;
+
+    private ActionMode mActionMode;
+
+    ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
+    private SavedWallpaperImages mSavedImages;
+    private int mSelectedIndex = -1;
+    private float mWallpaperParallaxOffset;
+
+    /**
+     * shows the system wallpaper behind the window and hides the {@link #mCropView} if visible
+     * @param visible should the system wallpaper be shown
+     */
+    protected void setSystemWallpaperVisiblity(final boolean visible) {
+        // hide our own wallpaper preview if necessary
+        if(!visible) {
+            mCropView.setVisibility(View.VISIBLE);
+        } else {
+            changeWallpaperFlags(visible);
+        }
+        // the change of the flag must be delayed in order to avoid flickering,
+        // a simple post / double post does not suffice here
+        mCropView.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if(!visible) {
+                    changeWallpaperFlags(visible);
+                } else {
+                    mCropView.setVisibility(View.INVISIBLE);
+                }
+            }
+        }, FLAG_POST_DELAY_MILLIS);
+    }
+
+    private void changeWallpaperFlags(boolean visible) {
+        int desiredWallpaperFlag = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
+        int currentWallpaperFlag = getWindow().getAttributes().flags
+                & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        if (desiredWallpaperFlag != currentWallpaperFlag) {
+            getWindow().setFlags(desiredWallpaperFlag,
+                    WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
+        }
+    }
+
+    @Override
+    protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+        super.onLoadRequestComplete(req, success);
+        if (success) {
+            setSystemWallpaperVisiblity(false);
+        }
+    }
+
+    /**
+     * called by onCreate; this is sub-classed to overwrite WallpaperCropActivity
+     */
+    protected void init() {
+        setContentView(R.layout.wallpaper_picker);
+
+        mCropView = (CropView) findViewById(R.id.cropView);
+        mCropView.setVisibility(View.INVISIBLE);
+
+        mProgressView = findViewById(R.id.loading);
+        mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
+        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+        mCropView.setTouchCallback(new ToggleOnTapCallback(mWallpaperStrip));
+
+        mWallpaperParallaxOffset = getIntent().getFloatExtra(
+                WallpaperUtils.EXTRA_WALLPAPER_OFFSET, 0);
+
+        mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
+        // Populate the saved wallpapers
+        mSavedImages = new SavedWallpaperImages(this);
+        populateWallpapers(mWallpapersView, mSavedImages.loadThumbnailsAndImageIdList(), true);
+
+        // Populate the built-in wallpapers
+        ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
+        populateWallpapers(mWallpapersView, wallpapers, false);
+
+        // Load live wallpapers asynchronously
+        new LiveWallpaperInfo.LoaderTask(this) {
+
+            @Override
+            protected void onPostExecute(List<LiveWallpaperInfo> result) {
+                populateWallpapers((LinearLayout) findViewById(R.id.live_wallpaper_list),
+                        result, false);
+                initializeScrollForRtl();
+                updateTileIndices();
+            }
+        }.execute();
+
+        // Populate the third-party wallpaper pickers
+        populateWallpapers((LinearLayout) findViewById(R.id.third_party_wallpaper_list),
+                ThirdPartyWallpaperInfo.getAll(this), false /* addLongPressHandler */);
+
+        // Add a tile for the Gallery
+        LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
+        masterWallpaperList.addView(
+                createTileView(masterWallpaperList, new PickImageInfo(), false), 0);
+
+        // Select the first item; wait for a layout pass so that we initialize the dimensions of
+        // cropView or the defaultWallpaperView first
+        mCropView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                if ((right - left) > 0 && (bottom - top) > 0) {
+                    if (mSelectedIndex >= 0 && mSelectedIndex < mWallpapersView.getChildCount()) {
+                        onClick(mWallpapersView.getChildAt(mSelectedIndex));
+                        setSystemWallpaperVisiblity(false);
+                    }
+                    v.removeOnLayoutChangeListener(this);
+                }
+            }
+        });
+
+        updateTileIndices();
+
+        // Update the scroll for RTL
+        initializeScrollForRtl();
+
+        // Create smooth layout transitions for when items are deleted
+        final LayoutTransition transitioner = new LayoutTransition();
+        transitioner.setDuration(200);
+        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
+        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
+        mWallpapersView.setLayoutTransition(transitioner);
+
+        // Action bar
+        // Show the custom action bar view
+        final ActionBar actionBar = getActionBar();
+        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
+        actionBar.getCustomView().setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        // Ensure that a tile is selected and loaded.
+                        if (mSelectedTile != null && mCropView.getTileSource() != null) {
+                            // Prevent user from selecting any new tile.
+                            mWallpaperStrip.setVisibility(View.GONE);
+                            actionBar.hide();
+
+                            WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
+                            info.onSave(WallpaperPickerActivity.this);
+                        } else {
+                            // This case shouldn't be possible, since "Set wallpaper" is disabled
+                            // until user clicks on a title.
+                            Log.w(TAG, "\"Set wallpaper\" was clicked when no tile was selected");
+                        }
+                    }
+                });
+        mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
+    }
+
+    /**
+     * Called when a wallpaper tile is clicked
+     */
+    @Override
+    public void onClick(View v) {
+        if (mActionMode != null) {
+            // When CAB is up, clicking toggles the item instead
+            if (v.isLongClickable()) {
+                onLongClick(v);
+            }
+            return;
+        }
+        WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
+        if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
+            selectTile(v);
+            setWallpaperButtonEnabled(true);
+        }
+        info.onClick(this);
+    }
+
+    /**
+     * Called when a view is long clicked
+     */
+    @Override
+    public boolean onLongClick(View v) {
+        CheckableFrameLayout c = (CheckableFrameLayout) v;
+        c.toggle();
+
+        if (mActionMode != null) {
+            mActionMode.invalidate();
+        } else {
+            // Start the CAB using the ActionMode.Callback defined below
+            mActionMode = startActionMode(this);
+            int childCount = mWallpapersView.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                mWallpapersView.getChildAt(i).setSelected(false);
+            }
+        }
+        return true;
+    }
+
+    public void setWallpaperButtonEnabled(boolean enabled) {
+        mSetWallpaperButton.setEnabled(enabled);
+    }
+
+    public float getWallpaperParallaxOffset() {
+        return mWallpaperParallaxOffset;
+    }
+
+    public void selectTile(View v) {
+        if (mSelectedTile != null) {
+            mSelectedTile.setSelected(false);
+            mSelectedTile = null;
+        }
+        mSelectedTile = v;
+        v.setSelected(true);
+        mSelectedIndex = mWallpapersView.indexOfChild(v);
+        // TODO: Remove this once the accessibility framework and
+        // services have better support for selection state.
+        v.announceForAccessibility(
+            getString(R.string.announce_selection, v.getContentDescription()));
+    }
+
+    private void initializeScrollForRtl() {
+        if (WallpaperUtils.isRtl(getResources())) {
+            final ViewTreeObserver observer = mWallpaperScrollContainer.getViewTreeObserver();
+            observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+                public void onGlobalLayout() {
+                    LinearLayout masterWallpaperList =
+                            (LinearLayout) findViewById(R.id.master_wallpaper_list);
+                    mWallpaperScrollContainer.scrollTo(masterWallpaperList.getWidth(), 0);
+                    mWallpaperScrollContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                }
+            });
+        }
+    }
+
+    public void onStop() {
+        super.onStop();
+        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+        if (mWallpaperStrip.getAlpha() < 1f) {
+            mWallpaperStrip.setAlpha(1f);
+            mWallpaperStrip.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public void onSaveInstanceState(Bundle outState) {
+        outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
+        outState.putInt(SELECTED_INDEX, mSelectedIndex);
+    }
+
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
+        for (Uri uri : uris) {
+            addTemporaryWallpaperTile(uri, true);
+        }
+        mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1);
+    }
+
+    private void updateTileIndices() {
+        LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
+        final int childCount = masterWallpaperList.getChildCount();
+        final Resources res = getResources();
+
+        // Do two passes; the first pass gets the total number of tiles
+        int numTiles = 0;
+        for (int passNum = 0; passNum < 2; passNum++) {
+            int tileIndex = 0;
+            for (int i = 0; i < childCount; i++) {
+                View child = masterWallpaperList.getChildAt(i);
+                LinearLayout subList;
+
+                int subListStart;
+                int subListEnd;
+                if (child.getTag() instanceof WallpaperTileInfo) {
+                    subList = masterWallpaperList;
+                    subListStart = i;
+                    subListEnd = i + 1;
+                } else { // if (child instanceof LinearLayout) {
+                    subList = (LinearLayout) child;
+                    subListStart = 0;
+                    subListEnd = subList.getChildCount();
+                }
+
+                for (int j = subListStart; j < subListEnd; j++) {
+                    WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag();
+                    if (info.isNamelessWallpaper()) {
+                        if (passNum == 0) {
+                            numTiles++;
+                        } else {
+                            CharSequence label = res.getString(
+                                    R.string.wallpaper_accessibility_name, ++tileIndex, numTiles);
+                            info.onIndexUpdated(label);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void addTemporaryWallpaperTile(final Uri uri, boolean fromRestore) {
+
+        // Add a tile for the image picked from Gallery, reusing the existing tile if there is one.
+        View imageTile = null;
+        int indexOfExistingTile = 0;
+        for (; indexOfExistingTile < mWallpapersView.getChildCount(); indexOfExistingTile++) {
+            View thumbnail = mWallpapersView.getChildAt(indexOfExistingTile);
+            Object tag = thumbnail.getTag();
+            if (tag instanceof UriWallpaperInfo && ((UriWallpaperInfo) tag).mUri.equals(uri)) {
+                imageTile = thumbnail;
+                break;
+            }
+        }
+        final UriWallpaperInfo info;
+        if (imageTile != null) {
+            // Always move the existing wallpaper to the front so user can see it without scrolling.
+            mWallpapersView.removeViewAt(indexOfExistingTile);
+            info = (UriWallpaperInfo) imageTile.getTag();
+        } else {
+            // This is the first time this temporary wallpaper has been added
+            info = new UriWallpaperInfo(uri);
+            imageTile = createTileView(mWallpapersView, info, true);
+            mTempWallpaperTiles.add(uri);
+        }
+        mWallpapersView.addView(imageTile, 0);
+        info.loadThumbnaleAsync(this);
+
+        updateTileIndices();
+        if (!fromRestore) {
+            onClick(imageTile);
+        }
+    }
+
+    private void populateWallpapers(ViewGroup parent, List<? extends WallpaperTileInfo> wallpapers,
+            boolean addLongPressHandler) {
+        for (WallpaperTileInfo info : wallpapers) {
+            parent.addView(createTileView(parent, info, addLongPressHandler));
+        }
+    }
+
+    private View createTileView(ViewGroup parent, WallpaperTileInfo info, boolean addLongPress) {
+        View view = info.createView(this, getLayoutInflater(), parent);
+        view.setTag(info);
+
+        if (addLongPress) {
+            view.setOnLongClickListener(this);
+        }
+        view.setOnClickListener(this);
+        return view;
+    }
+
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == IMAGE_PICK && resultCode == Activity.RESULT_OK) {
+            if (data != null && data.getData() != null) {
+                Uri uri = data.getData();
+                addTemporaryWallpaperTile(uri, false);
+            }
+        } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY
+                && resultCode == Activity.RESULT_OK) {
+            // Something was set on the third-party activity.
+            setResult(Activity.RESULT_OK);
+            finish();
+        }
+    }
+
+    public ArrayList<WallpaperTileInfo> findBundledWallpapers() {
+        final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);
+        Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
+        if (r != null) {
+            try {
+                Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
+                addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+
+        // Add an entry for the default wallpaper (stored in system resources)
+        WallpaperTileInfo defaultWallpaperInfo = DefaultWallpaperInfo.get(this);
+        if (defaultWallpaperInfo != null) {
+            bundled.add(0, defaultWallpaperInfo);
+        }
+        return bundled;
+    }
+
+    public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
+        return new Pair<>(getApplicationInfo(), R.array.wallpapers);
+    }
+
+    public void addWallpapers(ArrayList<WallpaperTileInfo> known, Resources res,
+            String packageName, int listResId) {
+        final String[] extras = res.getStringArray(listResId);
+        for (String extra : extras) {
+            int resId = res.getIdentifier(extra, "drawable", packageName);
+            if (resId != 0) {
+                final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName);
+
+                if (thumbRes != 0) {
+                    ResourceWallpaperInfo wallpaperInfo =
+                            new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
+                    known.add(wallpaperInfo);
+                    // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
+                }
+            } else {
+                Log.e(TAG, "Couldn't find wallpaper " + extra);
+            }
+        }
+    }
+
+    public SavedWallpaperImages getSavedImages() {
+        return mSavedImages;
+    }
+
+    public void startActivityForResultSafely(Intent intent, int requestCode) {
+        startActivityForResult(intent, requestCode);
+    }
+
+    // CAB for deleting items
+    /**
+     * Called when the action mode is created; startActionMode() was called
+     */
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate a menu resource providing context menu items
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.cab_delete_wallpapers, menu);
+        return true;
+    }
+
+    /**
+     * Called each time the action mode is shown. Always called after onCreateActionMode,
+     * but may be called multiple times if the mode is invalidated.
+     */
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        int childCount = mWallpapersView.getChildCount();
+        int numCheckedItems = 0;
+        for (int i = 0; i < childCount; i++) {
+            CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+            if (c.isChecked()) {
+                numCheckedItems++;
+            }
+        }
+
+        if (numCheckedItems == 0) {
+            mode.finish();
+            return true;
+        } else {
+            mode.setTitle(getResources().getQuantityString(
+                    R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
+            return true;
+        }
+    }
+
+    /**
+     * Called when the user selects a contextual menu item
+     */
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        int itemId = item.getItemId();
+        if (itemId == R.id.menu_delete) {
+            int childCount = mWallpapersView.getChildCount();
+            ArrayList<View> viewsToRemove = new ArrayList<View>();
+            boolean selectedTileRemoved = false;
+            for (int i = 0; i < childCount; i++) {
+                CheckableFrameLayout c =
+                        (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+                if (c.isChecked()) {
+                    WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
+                    info.onDelete(WallpaperPickerActivity.this);
+                    viewsToRemove.add(c);
+                    if (i == mSelectedIndex) {
+                        selectedTileRemoved = true;
+                    }
+                }
+            }
+            for (View v : viewsToRemove) {
+                mWallpapersView.removeView(v);
+            }
+            if (selectedTileRemoved) {
+                mSelectedIndex = -1;
+                mSelectedTile = null;
+                setSystemWallpaperVisiblity(true);
+            }
+            updateTileIndices();
+            mode.finish(); // Action picked, so close the CAB
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Called when the user exits the action mode
+     */
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        int childCount = mWallpapersView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+            c.setChecked(false);
+        }
+        if (mSelectedTile != null) {
+            mSelectedTile.setSelected(true);
+        }
+        mActionMode = null;
+    }
+}
diff --git a/src/com/android/wallpaperpicker/WallpaperUtils.java b/src/com/android/wallpaperpicker/WallpaperUtils.java
new file mode 100644
index 0000000..1532190
--- /dev/null
+++ b/src/com/android/wallpaperpicker/WallpaperUtils.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 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.wallpaperpicker;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Build;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * Utility methods for wallpaper management.
+ */
+public final class WallpaperUtils {
+
+    public static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
+    public static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
+
+    // An intent extra to indicate the horizontal scroll of the wallpaper.
+    public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
+
+    public static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+    public static void saveWallpaperDimensions(int width, int height, Activity activity) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            // From Kitkat onwards, ImageWallpaper does not care about the
+            // desired width and desired height of the wallpaper.
+            return;
+        }
+        String spKey = WallpaperFiles.WALLPAPER_CROP_PREFERENCES_KEY;
+        SharedPreferences sp = activity.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
+        SharedPreferences.Editor editor = sp.edit();
+        if (width != 0 && height != 0) {
+            editor.putInt(WALLPAPER_WIDTH_KEY, width);
+            editor.putInt(WALLPAPER_HEIGHT_KEY, height);
+        } else {
+            editor.remove(WALLPAPER_WIDTH_KEY);
+            editor.remove(WALLPAPER_HEIGHT_KEY);
+        }
+        editor.commit();
+        suggestWallpaperDimensionPreK(activity, true);
+    }
+
+    public static void suggestWallpaperDimensionPreK(
+            Activity activity, boolean fallBackToDefaults) {
+        final Point defaultWallpaperSize = getDefaultWallpaperSize(
+                activity.getResources(), activity.getWindowManager());
+
+        SharedPreferences sp = activity.getSharedPreferences(
+              WallpaperFiles.WALLPAPER_CROP_PREFERENCES_KEY, Context.MODE_MULTI_PROCESS);
+        // If we have saved a wallpaper width/height, use that instead
+        int width = sp.getInt(WALLPAPER_WIDTH_KEY, -1);
+        int height = sp.getInt(WALLPAPER_HEIGHT_KEY, -1);
+
+        if (width == -1 || height == -1) {
+            if (!fallBackToDefaults) {
+                return;
+            } else {
+                width = defaultWallpaperSize.x;
+                height = defaultWallpaperSize.y;
+            }
+        }
+
+        WallpaperManager wm = WallpaperManager.getInstance(activity);
+        if (width != wm.getDesiredMinimumWidth() || height != wm.getDesiredMinimumHeight()) {
+            wm.suggestDesiredDimensions(width, height);
+        }
+    }
+
+    public static void suggestWallpaperDimension(Activity activity) {
+        // Only live wallpapers care about desired size. Update the size to what launcher expects.
+        final Point size = getDefaultWallpaperSize(
+                activity.getResources(), activity.getWindowManager());
+        WallpaperManager wm = WallpaperManager.getInstance(activity);
+        if (size.x != wm.getDesiredMinimumWidth() || size.y != wm.getDesiredMinimumHeight()) {
+            wm.suggestDesiredDimensions(size.x, size.y);
+        }
+    }
+
+    /**
+     * As a ratio of screen height, the total distance we want the parallax effect to span
+     * horizontally
+     */
+    private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
+        float aspectRatio = width / (float) height;
+
+        // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
+        // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
+        // We will use these two data points to extrapolate how much the wallpaper parallax effect
+        // to span (ie travel) at any aspect ratio:
+
+        final float ASPECT_RATIO_LANDSCAPE = 16/10f;
+        final float ASPECT_RATIO_PORTRAIT = 10/16f;
+        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
+        final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
+
+        // To find out the desired width at different aspect ratios, we use the following two
+        // formulas, where the coefficient on x is the aspect ratio (width/height):
+        //   (16/10)x + y = 1.5
+        //   (10/16)x + y = 1.2
+        // We solve for x and y and end up with a final formula:
+        final float x =
+            (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
+            (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
+        final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
+        return x * aspectRatio + y;
+    }
+
+    private static Point sDefaultWallpaperSize;
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
+        if (sDefaultWallpaperSize == null) {
+            Point realSize = new Point();
+            windowManager.getDefaultDisplay().getRealSize(realSize);
+            int maxDim = Math.max(realSize.x, realSize.y);
+            int minDim = Math.min(realSize.x, realSize.y);
+
+            // We need to ensure that there is enough extra space in the wallpaper
+            // for the intended parallax effects
+            final int defaultWidth, defaultHeight;
+            if (res.getConfiguration().smallestScreenWidthDp >= 720) {
+                defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+                defaultHeight = maxDim;
+            } else {
+                defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+                defaultHeight = maxDim;
+            }
+            sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight);
+        }
+        return sDefaultWallpaperSize;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static boolean isRtl(Resources res) {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
+                (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+    }
+}
diff --git a/src/com/android/wallpaperpicker/common/CropAndSetWallpaperTask.java b/src/com/android/wallpaperpicker/common/CropAndSetWallpaperTask.java
new file mode 100644
index 0000000..ac6f18d
--- /dev/null
+++ b/src/com/android/wallpaperpicker/common/CropAndSetWallpaperTask.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (C) 2015 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.wallpaperpicker.common;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.RectF;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.wallpaperpicker.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class CropAndSetWallpaperTask extends AsyncTask<Integer, Void, Boolean> {
+
+    public interface OnBitmapCroppedHandler {
+        void onBitmapCropped(byte[] imageBytes);
+    }
+
+    public interface OnEndCropHandler {
+        void run(boolean cropSucceeded);
+    }
+
+    private static final int DEFAULT_COMPRESS_QUALITY = 90;
+    private static final String TAG = "CropAndSetWallpaperTask";
+
+    private final InputStreamProvider mStreamProvider;
+    private final Context mContext;
+
+    private final RectF mCropBounds;
+    private int mOutWidth, mOutHeight;
+    private int mRotation;
+    private CropAndSetWallpaperTask.OnEndCropHandler mOnEndCropHandler;
+    private CropAndSetWallpaperTask.OnBitmapCroppedHandler mOnBitmapCroppedHandler;
+
+    public CropAndSetWallpaperTask(InputStreamProvider streamProvider, Context context,
+                                   RectF cropBounds, int rotation, int outWidth, int outHeight,
+                                   OnEndCropHandler onEndCropHandler) {
+        mStreamProvider = streamProvider;
+        mContext = context;
+
+        mCropBounds = cropBounds;
+        mRotation = rotation;
+        mOutWidth = outWidth;
+        mOutHeight = outHeight;
+        mOnEndCropHandler = onEndCropHandler;
+    }
+
+    public void setOnBitmapCropped(CropAndSetWallpaperTask.OnBitmapCroppedHandler handler) {
+        mOnBitmapCroppedHandler = handler;
+    }
+
+    public boolean cropBitmap(int whichWallpaper) {
+        Bitmap crop = mStreamProvider.readCroppedBitmap(
+                mCropBounds, mOutWidth, mOutHeight, mRotation);
+        if (crop == null) {
+            return false;
+        }
+
+        boolean failure = false;
+        // Compress to byte array
+        ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+        if (crop.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+            // Set the wallpaper
+            try {
+                byte[] outByteArray = tmpOut.toByteArray();
+                WallpaperManagerCompat.getInstance(mContext).setStream(
+                        new ByteArrayInputStream(outByteArray),
+                        null, true, whichWallpaper);
+                if (mOnBitmapCroppedHandler != null) {
+                    mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
+                }
+            } catch (IOException e) {
+                Log.w(TAG, "cannot write stream to wallpaper", e);
+                failure = true;
+            }
+        } else {
+            Log.w(TAG, "cannot compress bitmap");
+            failure = true;
+        }
+        return !failure; // True if any of the operations failed
+    }
+
+    @Override
+    protected Boolean doInBackground(Integer... whichWallpaper) {
+        return cropBitmap(whichWallpaper[0]);
+    }
+
+    @Override
+    protected void onPostExecute(Boolean cropSucceeded) {
+        if (!cropSucceeded) {
+            Toast.makeText(mContext, R.string.wallpaper_set_fail, Toast.LENGTH_SHORT).show();
+        }
+        if (mOnEndCropHandler != null) {
+            mOnEndCropHandler.run(cropSucceeded);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/common/DialogUtils.java b/src/com/android/wallpaperpicker/common/DialogUtils.java
new file mode 100644
index 0000000..8313535
--- /dev/null
+++ b/src/com/android/wallpaperpicker/common/DialogUtils.java
@@ -0,0 +1,47 @@
+package com.android.wallpaperpicker.common;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.AsyncTask;
+
+import com.android.wallpaperpicker.R;
+
+/**
+ * Utility class used to show dialogs for things like picking which wallpaper to set.
+ */
+public class DialogUtils {
+    /**
+     * Calls cropTask.execute(), once the user has selected which wallpaper to set. On pre-N
+     * devices, the prompt is not displayed since there is no API to set the lockscreen wallpaper.
+     *
+     * TODO: Don't use CropAndSetWallpaperTask on N+, because the new API will handle cropping instead.
+     */
+    public static void executeCropTaskAfterPrompt(
+            Context context, final AsyncTask<Integer, ?, ?> cropTask,
+            DialogInterface.OnCancelListener onCancelListener) {
+        if (Utilities.isAtLeastN()) {
+            new AlertDialog.Builder(context)
+                    .setTitle(R.string.wallpaper_instructions)
+                    .setItems(R.array.which_wallpaper_options, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int selectedItemIndex) {
+                        int whichWallpaper;
+                        if (selectedItemIndex == 0) {
+                            whichWallpaper = WallpaperManagerCompat.FLAG_SET_SYSTEM;
+                        } else if (selectedItemIndex == 1) {
+                            whichWallpaper = WallpaperManagerCompat.FLAG_SET_LOCK;
+                        } else {
+                            whichWallpaper = WallpaperManagerCompat.FLAG_SET_SYSTEM
+                                    | WallpaperManagerCompat.FLAG_SET_LOCK;
+                        }
+                        cropTask.execute(whichWallpaper);
+                    }
+                })
+                .setOnCancelListener(onCancelListener)
+                .show();
+        } else {
+            cropTask.execute(WallpaperManagerCompat.FLAG_SET_SYSTEM);
+        }
+    }
+}
diff --git a/src/com/android/wallpaperpicker/common/InputStreamProvider.java b/src/com/android/wallpaperpicker/common/InputStreamProvider.java
new file mode 100644
index 0000000..72588ad
--- /dev/null
+++ b/src/com/android/wallpaperpicker/common/InputStreamProvider.java
@@ -0,0 +1,263 @@
+package com.android.wallpaperpicker.common;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.gallery3d.common.ExifOrientation;
+import com.android.gallery3d.common.Utils;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An abstraction over input stream creation. Also contains some utility methods
+ * for various bitmap operations.
+ */
+public abstract class InputStreamProvider {
+
+    private static final String TAG = "InputStreamProvider";
+
+    /**
+     * Tries to create a new stream or returns null on failure.
+     */
+    public InputStream newStream() {
+        try {
+            return newStreamNotNull();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Tries to create a new stream or throws an exception on failure.
+     */
+    public abstract InputStream newStreamNotNull() throws IOException;
+
+    /**
+     * Returns the size of the image, if the stream represents an image.
+     */
+    public Point getImageBounds() {
+        InputStream is = newStream();
+        if (is != null) {
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(is, null, options);
+            Utils.closeSilently(is);
+            if (options.outWidth != 0 && options.outHeight != 0) {
+                return new Point(options.outWidth, options.outHeight);
+            }
+        }
+        return null;
+    }
+
+    public Bitmap readCroppedBitmap(RectF cropBounds, int outWidth, int outHeight, int rotation) {
+        // Find crop bounds (scaled to original image size)
+        Rect roundedTrueCrop = new Rect();
+        Matrix rotateMatrix = new Matrix();
+        Point bounds = getImageBounds();
+        if (bounds == null) {
+            Log.w(TAG, "cannot get bounds for image");
+            return null;
+        }
+
+        if (rotation > 0) {
+            rotateMatrix.setRotate(rotation);
+
+            Matrix inverseRotateMatrix = new Matrix();
+            inverseRotateMatrix.setRotate(-rotation);
+
+            cropBounds.roundOut(roundedTrueCrop);
+            cropBounds.set(roundedTrueCrop);
+
+            float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+            rotateMatrix.mapPoints(rotatedBounds);
+            rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+            rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+            cropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
+            inverseRotateMatrix.mapRect(cropBounds);
+            cropBounds.offset(bounds.x/2, bounds.y/2);
+        }
+
+        cropBounds.roundOut(roundedTrueCrop);
+        if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+            Log.w(TAG, "crop has bad values for full size image");
+            return null;
+        }
+
+        // See how much we're reducing the size of the image
+        int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / outWidth,
+                roundedTrueCrop.height() / outHeight));
+        // Attempt to open a region decoder
+        InputStream is = null;
+        BitmapRegionDecoder decoder = null;
+        try {
+            is = newStreamNotNull();
+            decoder = BitmapRegionDecoder.newInstance(is, false);
+        } catch (IOException e) {
+            Log.w(TAG, "cannot open region decoder", e);
+        } finally {
+            Utils.closeSilently(is);
+            is = null;
+        }
+
+        Bitmap crop = null;
+        if (decoder != null) {
+            // Do region decoding to get crop bitmap
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            if (scaleDownSampleSize > 1) {
+                options.inSampleSize = scaleDownSampleSize;
+            }
+            crop = decoder.decodeRegion(roundedTrueCrop, options);
+            decoder.recycle();
+        }
+
+        if (crop == null) {
+            // BitmapRegionDecoder has failed, try to crop in-memory
+            is = newStream();
+            Bitmap fullSize = null;
+            if (is != null) {
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                if (scaleDownSampleSize > 1) {
+                    options.inSampleSize = scaleDownSampleSize;
+                }
+                fullSize = BitmapFactory.decodeStream(is, null, options);
+                Utils.closeSilently(is);
+            }
+            if (fullSize != null) {
+                // Find out the true sample size that was used by the decoder
+                scaleDownSampleSize = bounds.x / fullSize.getWidth();
+                cropBounds.left /= scaleDownSampleSize;
+                cropBounds.top /= scaleDownSampleSize;
+                cropBounds.bottom /= scaleDownSampleSize;
+                cropBounds.right /= scaleDownSampleSize;
+                cropBounds.roundOut(roundedTrueCrop);
+
+                // Adjust values to account for issues related to rounding
+                if (roundedTrueCrop.width() > fullSize.getWidth()) {
+                    // Adjust the width
+                    roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
+                }
+                if (roundedTrueCrop.right > fullSize.getWidth()) {
+                    // Adjust the left and right values.
+                    roundedTrueCrop.offset(-(roundedTrueCrop.right - fullSize.getWidth()), 0);
+                }
+                if (roundedTrueCrop.height() > fullSize.getHeight()) {
+                    // Adjust the height
+                    roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
+                }
+                if (roundedTrueCrop.bottom > fullSize.getHeight()) {
+                    // Adjust the top and bottom values.
+                    roundedTrueCrop.offset(0, -(roundedTrueCrop.bottom - fullSize.getHeight()));
+                }
+
+                crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+                        roundedTrueCrop.top, roundedTrueCrop.width(),
+                        roundedTrueCrop.height());
+            }
+        }
+
+        if (crop == null) {
+            return null;
+        }
+        if (outWidth > 0 && outHeight > 0 || rotation > 0) {
+            float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
+            rotateMatrix.mapPoints(dimsAfter);
+            dimsAfter[0] = Math.abs(dimsAfter[0]);
+            dimsAfter[1] = Math.abs(dimsAfter[1]);
+
+            if (!(outWidth > 0 && outHeight > 0)) {
+                outWidth = Math.round(dimsAfter[0]);
+                outHeight = Math.round(dimsAfter[1]);
+            }
+
+            RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
+            RectF returnRect = new RectF(0, 0, outWidth, outHeight);
+
+            Matrix m = new Matrix();
+            if (rotation == 0) {
+                m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+            } else {
+                Matrix m1 = new Matrix();
+                m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
+                Matrix m2 = new Matrix();
+                m2.setRotate(rotation);
+                Matrix m3 = new Matrix();
+                m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
+                Matrix m4 = new Matrix();
+                m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+
+                Matrix c1 = new Matrix();
+                c1.setConcat(m2, m1);
+                Matrix c2 = new Matrix();
+                c2.setConcat(m4, m3);
+                m.setConcat(c2, c1);
+            }
+
+            Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+                    (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+            if (tmp != null) {
+                Canvas c = new Canvas(tmp);
+                Paint p = new Paint();
+                p.setFilterBitmap(true);
+                c.drawBitmap(crop, m, p);
+                crop = tmp;
+            }
+        }
+        return crop;
+    }
+
+    public int getRotationFromExif(Context context) {
+        InputStream is = null;
+        try {
+            is = newStreamNotNull();
+            return ExifOrientation.readRotation(new BufferedInputStream(is), context);
+        } catch (IOException | NullPointerException e) {
+            Log.w(TAG, "Getting exif data failed", e);
+        } finally {
+            Utils.closeSilently(is);
+        }
+        return 0;
+    }
+
+    public static InputStreamProvider fromUri(final Context context, final Uri uri) {
+        return new InputStreamProvider() {
+            @Override
+            public InputStream newStreamNotNull() throws IOException {
+                return new BufferedInputStream(context.getContentResolver().openInputStream(uri));
+            }
+        };
+    }
+
+    public static InputStreamProvider fromResource(final Resources resources, final int resId) {
+        return new InputStreamProvider() {
+            @Override
+            public InputStream newStreamNotNull() {
+                return new BufferedInputStream(resources.openRawResource(resId));
+            }
+        };
+    }
+
+    public static InputStreamProvider fromBytes(final byte[] bytes) {
+        return new InputStreamProvider() {
+            @Override
+            public InputStream newStreamNotNull() {
+                return new BufferedInputStream(new ByteArrayInputStream(bytes));
+            }
+        };
+    }
+
+}
diff --git a/src/com/android/wallpaperpicker/common/Utilities.java b/src/com/android/wallpaperpicker/common/Utilities.java
new file mode 100644
index 0000000..b71f56c
--- /dev/null
+++ b/src/com/android/wallpaperpicker/common/Utilities.java
@@ -0,0 +1,16 @@
+package com.android.wallpaperpicker.common;
+
+import android.app.WallpaperManager;
+
+public class Utilities {
+
+    public static boolean isAtLeastN() {
+        // TODO: replace this with a more final implementation.
+        try {
+            WallpaperManager.class.getMethod("getWallpaperFile", int.class);
+            return true;
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/wallpaperpicker/common/WallpaperManagerCompat.java b/src/com/android/wallpaperpicker/common/WallpaperManagerCompat.java
new file mode 100644
index 0000000..96df749
--- /dev/null
+++ b/src/com/android/wallpaperpicker/common/WallpaperManagerCompat.java
@@ -0,0 +1,33 @@
+package com.android.wallpaperpicker.common;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class WallpaperManagerCompat {
+    public static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM
+    public static final int FLAG_SET_LOCK = 1 << 1; // TODO: use WallpaperManager.FLAG_SET_LOCK
+
+    private static WallpaperManagerCompat sInstance;
+    private static final Object sInstanceLock = new Object();
+
+    public static WallpaperManagerCompat getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                if (Utilities.isAtLeastN()) {
+                    sInstance = new WallpaperManagerCompatVN(context.getApplicationContext());
+                } else {
+                    sInstance = new WallpaperManagerCompatV16(context.getApplicationContext());
+                }
+            }
+            return sInstance;
+        }
+    }
+
+    public abstract void setStream(InputStream stream, Rect visibleCropHint, boolean allowBackup,
+            int whichWallpaper) throws IOException;
+
+    public abstract void clear(int whichWallpaper) throws IOException;
+}
diff --git a/src/com/android/wallpaperpicker/common/WallpaperManagerCompatV16.java b/src/com/android/wallpaperpicker/common/WallpaperManagerCompatV16.java
new file mode 100644
index 0000000..a320beb
--- /dev/null
+++ b/src/com/android/wallpaperpicker/common/WallpaperManagerCompatV16.java
@@ -0,0 +1,27 @@
+package com.android.wallpaperpicker.common;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Rect;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WallpaperManagerCompatV16 extends WallpaperManagerCompat {
+    protected WallpaperManager mWallpaperManager;
+
+    public WallpaperManagerCompatV16(Context context) {
+        mWallpaperManager = WallpaperManager.getInstance(context.getApplicationContext());
+    }
+
+    @Override
+    public void setStream(InputStream data, Rect visibleCropHint, boolean allowBackup,
+            int whichWallpaper) throws IOException {
+        mWallpaperManager.setStream(data);
+    }
+
+    @Override
+    public void clear(int whichWallpaper) throws IOException {
+        mWallpaperManager.clear();
+    }
+}
diff --git a/src/com/android/wallpaperpicker/common/WallpaperManagerCompatVN.java b/src/com/android/wallpaperpicker/common/WallpaperManagerCompatVN.java
new file mode 100644
index 0000000..2eb9c51
--- /dev/null
+++ b/src/com/android/wallpaperpicker/common/WallpaperManagerCompatVN.java
@@ -0,0 +1,43 @@
+package com.android.wallpaperpicker.common;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Rect;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class WallpaperManagerCompatVN extends WallpaperManagerCompatV16 {
+    public WallpaperManagerCompatVN(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void setStream(final InputStream data, Rect visibleCropHint, boolean allowBackup,
+            int whichWallpaper) throws IOException {
+        try {
+            // TODO: use mWallpaperManager.setStream(data, visibleCropHint, allowBackup, which)
+            // without needing reflection.
+            Method setStream = WallpaperManager.class.getMethod("setStream", InputStream.class,
+                    Rect.class, boolean.class, int.class);
+            setStream.invoke(mWallpaperManager, data, visibleCropHint, allowBackup, whichWallpaper);
+        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+            // Fall back to previous implementation (set both)
+            super.setStream(data, visibleCropHint, allowBackup, whichWallpaper);
+        }
+    }
+
+    @Override
+    public void clear(int whichWallpaper) throws IOException {
+        try {
+            // TODO: use mWallpaperManager.clear(whichWallpaper) without needing reflection.
+            Method clear = WallpaperManager.class.getMethod("clear", int.class);
+            clear.invoke(mWallpaperManager, whichWallpaper);
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            // Fall back to previous implementation (set both)
+            super.clear(whichWallpaper);
+        }
+    }
+}
diff --git a/src/com/android/wallpaperpicker/tileinfo/DefaultWallpaperInfo.java b/src/com/android/wallpaperpicker/tileinfo/DefaultWallpaperInfo.java
new file mode 100644
index 0000000..68a0e68
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/DefaultWallpaperInfo.java
@@ -0,0 +1,225 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.wallpaperpicker.WallpaperCropActivity.CropViewScaleAndOffsetProvider;
+import com.android.wallpaperpicker.WallpaperFiles;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+import com.android.wallpaperpicker.common.CropAndSetWallpaperTask;
+import com.android.wallpaperpicker.common.DialogUtils;
+import com.android.wallpaperpicker.common.InputStreamProvider;
+import com.android.wallpaperpicker.common.WallpaperManagerCompat;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class DefaultWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private static final String TAG = "DefaultWallpaperInfo";
+
+    public DefaultWallpaperInfo(Drawable thumb) {
+        super(thumb);
+    }
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        a.setCropViewTileSource(null, false, false, new CropViewScaleAndOffsetProvider() {
+
+            @Override
+            public float getScale(Point wallpaperSize, RectF crop) {
+                return 1f;
+            }
+
+            @Override
+            public float getParallaxOffset() {
+                return 0.5f;
+            }
+        }, null);
+    }
+
+    @Override
+    public void onSave(final WallpaperPickerActivity a) {
+        CropAndSetWallpaperTask.OnEndCropHandler onEndCropHandler
+                = new CropAndSetWallpaperTask.OnEndCropHandler() {
+            @Override
+            public void run(boolean cropSucceeded) {
+                if (cropSucceeded) {
+                    a.setResult(Activity.RESULT_OK);
+                }
+                a.finish();
+            }
+        };
+        CropAndSetWallpaperTask setWallpaperTask = new CropAndSetWallpaperTask(
+                null, a, null, -1, -1, -1, onEndCropHandler) {
+            @Override
+            protected Boolean doInBackground(Integer... params) {
+                int whichWallpaper = params[0];
+                boolean succeeded;
+                if (whichWallpaper == WallpaperManagerCompat.FLAG_SET_LOCK) {
+                    succeeded = setDefaultOnLock(a);
+                } else {
+                    succeeded = clearWallpaper(a, whichWallpaper);
+                }
+                return succeeded;
+            }
+        };
+
+        DialogUtils.executeCropTaskAfterPrompt(a, setWallpaperTask, a.getOnDialogCancelListener());
+    }
+
+    //TODO: @TargetApi(Build.VERSION_CODES.N)
+    private boolean setDefaultOnLock(WallpaperPickerActivity a) {
+        boolean succeeded = true;
+        try {
+            Bitmap defaultWallpaper = ((BitmapDrawable) WallpaperManager.getInstance(
+                    a.getApplicationContext()).getBuiltInDrawable()).getBitmap();
+            ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+            if (defaultWallpaper.compress(Bitmap.CompressFormat.PNG, 100, tmpOut)) {
+                byte[] outByteArray = tmpOut.toByteArray();
+                WallpaperManagerCompat.getInstance(a.getApplicationContext())
+                        .setStream(new ByteArrayInputStream(outByteArray), null,
+                                true, WallpaperManagerCompat.FLAG_SET_LOCK);
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Setting wallpaper to default threw exception", e);
+            succeeded = false;
+        }
+        return succeeded;
+    }
+
+    private boolean clearWallpaper(WallpaperPickerActivity a, int whichWallpaper) {
+        boolean succeeded = true;
+        try {
+            WallpaperManagerCompat.getInstance(a.getApplicationContext()).clear(whichWallpaper);
+        } catch (IOException e) {
+            Log.w(TAG, "Setting wallpaper to default threw exception", e);
+            succeeded = false;
+        } catch (SecurityException e) {
+            // Happens on Samsung S6, for instance:
+            // "Permission denial: writing to settings requires android.permission.WRITE_SETTINGS"
+            Log.w(TAG, "Setting wallpaper to default threw exception", e);
+            // In this case, clearing worked even though the exception was thrown afterwards.
+            succeeded = true;
+        }
+        return succeeded;
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+
+    /**
+     * @return the system default wallpaper tile or null
+     */
+    public static WallpaperTileInfo get(Context context) {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+                ? getDefaultWallpaper(context) : getPreKKDefaultWallpaperInfo(context);
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    private static DefaultWallpaperInfo getDefaultWallpaper(Context context) {
+        File defaultThumbFile = getDefaultThumbFile(context);
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        Resources res = context.getResources();
+
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            Point defaultThumbSize = getDefaultThumbSize(res);
+            Drawable wallpaperDrawable = WallpaperManager.getInstance(context).getBuiltInDrawable(
+                    defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f);
+            if (wallpaperDrawable != null) {
+                thumb = Bitmap.createBitmap(
+                        defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888);
+                Canvas c = new Canvas(thumb);
+                wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y);
+                wallpaperDrawable.draw(c);
+                c.setBitmap(null);
+            }
+            if (thumb != null) {
+                defaultWallpaperExists = saveDefaultWallpaperThumb(context, thumb);
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new DefaultWallpaperInfo(new BitmapDrawable(res, thumb));
+        }
+        return null;
+    }
+
+    private static ResourceWallpaperInfo getPreKKDefaultWallpaperInfo(Context context) {
+        Resources sysRes = Resources.getSystem();
+        Resources res = context.getResources();
+
+        int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+
+        File defaultThumbFile = getDefaultThumbFile(context);
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            InputStreamProvider streamProvider = InputStreamProvider.fromResource(res, resId);
+            thumb = createThumbnail(
+                    streamProvider, context, streamProvider.getRotationFromExif(context), false);
+            if (thumb != null) {
+                defaultWallpaperExists = saveDefaultWallpaperThumb(context, thumb);
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(res, thumb));
+        }
+        return null;
+    }
+
+    private static File getDefaultThumbFile(Context context) {
+        return new File(context.getFilesDir(), Build.VERSION.SDK_INT
+                + "_" + WallpaperFiles.DEFAULT_WALLPAPER_THUMBNAIL);
+    }
+
+    private static boolean saveDefaultWallpaperThumb(Context c, Bitmap b) {
+        // Delete old thumbnails.
+        new File(c.getFilesDir(), WallpaperFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
+        new File(c.getFilesDir(), WallpaperFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+
+        for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
+            new File(c.getFilesDir(), i + "_" + WallpaperFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+        }
+        File f = getDefaultThumbFile(c);
+        try {
+            f.createNewFile();
+            FileOutputStream thumbFileStream = c.openFileOutput(f.getName(), Context.MODE_PRIVATE);
+            b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+            thumbFileStream.close();
+            return true;
+        } catch (IOException e) {
+            Log.e(TAG, "Error while writing bitmap to file " + e);
+            f.delete();
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/tileinfo/DrawableThumbWallpaperInfo.java b/src/com/android/wallpaperpicker/tileinfo/DrawableThumbWallpaperInfo.java
new file mode 100644
index 0000000..fd0081f
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/DrawableThumbWallpaperInfo.java
@@ -0,0 +1,37 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.wallpaperpicker.R;
+
+/**
+ * WallpaperTileInfo which uses drawable as the thumbnail.
+ */
+public abstract class DrawableThumbWallpaperInfo extends WallpaperTileInfo {
+
+    private final Drawable mThumb;
+
+    DrawableThumbWallpaperInfo(Drawable thumb) {
+        mThumb = thumb;
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_item, parent, false);
+        setThumb(mThumb);
+        return mView;
+    }
+
+    public void setThumb(Drawable thumb) {
+        if (mView != null && thumb != null) {
+            thumb.setDither(true);
+            ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image);
+            image.setImageDrawable(thumb);
+        }
+    }
+}
diff --git a/src/com/android/wallpaperpicker/tileinfo/FileWallpaperInfo.java b/src/com/android/wallpaperpicker/tileinfo/FileWallpaperInfo.java
new file mode 100644
index 0000000..36c51f9
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/FileWallpaperInfo.java
@@ -0,0 +1,96 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.gallery3d.common.Utils;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.wallpaperpicker.R;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+import com.android.wallpaperpicker.common.DialogUtils;
+import com.android.wallpaperpicker.common.InputStreamProvider;
+import com.android.wallpaperpicker.common.WallpaperManagerCompat;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class FileWallpaperInfo extends DrawableThumbWallpaperInfo {
+    private static final String TAG = "FileWallpaperInfo";
+
+    private final File mFile;
+
+    public FileWallpaperInfo(File target, Drawable thumb) {
+        super(thumb);
+        mFile = target;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
+                new BitmapRegionTileSource.FilePathBitmapSource(mFile, a);
+        a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
+
+            @Override
+            public void run() {
+                if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                    a.setWallpaperButtonEnabled(true);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSave(final WallpaperPickerActivity a) {
+        final InputStreamProvider isp = InputStreamProvider.fromUri(a, Uri.fromFile(mFile));
+        AsyncTask<Integer, Void, Point> cropTask = new AsyncTask<Integer, Void, Point>() {
+
+            @Override
+            protected Point doInBackground(Integer... params) {
+                InputStream is = null;
+                try {
+                    Point bounds = isp.getImageBounds();
+                    if (bounds == null) {
+                        Log.w(TAG, "Error loading image bounds");
+                        return null;
+                    }
+                    is = isp.newStreamNotNull();
+                    WallpaperManagerCompat.getInstance(a).setStream(is, null, true, params[0]);
+                    return bounds;
+                } catch (IOException e) {
+                    Log.w(TAG, "cannot write stream to wallpaper", e);
+                } finally {
+                    Utils.closeSilently(is);
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Point bounds) {
+                if (bounds != null) {
+                    a.setBoundsAndFinish(bounds, a.getWallpaperParallaxOffset() == 0f);
+                } else {
+                    Toast.makeText(a, R.string.wallpaper_set_fail, Toast.LENGTH_SHORT).show();
+                }
+            }
+        };
+
+        DialogUtils.executeCropTaskAfterPrompt(a, cropTask, a.getOnDialogCancelListener());
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/tileinfo/LiveWallpaperInfo.java b/src/com/android/wallpaperpicker/tileinfo/LiveWallpaperInfo.java
new file mode 100644
index 0000000..e776cba
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/LiveWallpaperInfo.java
@@ -0,0 +1,118 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.service.wallpaper.WallpaperService;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wallpaperpicker.R;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class LiveWallpaperInfo extends WallpaperTileInfo {
+
+    private static final String TAG = "LiveWallpaperTile";
+
+    private Drawable mThumbnail;
+    private WallpaperInfo mInfo;
+
+    public LiveWallpaperInfo(Drawable thumbnail, WallpaperInfo info, Intent intent) {
+        mThumbnail = thumbnail;
+        mInfo = info;
+    }
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
+        preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
+                mInfo.getComponent());
+        a.startActivityForResultSafely(preview,
+                WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
+
+        ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image);
+        ImageView icon = (ImageView) mView.findViewById(R.id.wallpaper_icon);
+        if (mThumbnail != null) {
+            image.setImageDrawable(mThumbnail);
+            icon.setVisibility(View.GONE);
+        } else {
+            icon.setImageDrawable(mInfo.loadIcon(context.getPackageManager()));
+            icon.setVisibility(View.VISIBLE);
+        }
+
+        TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label);
+        label.setText(mInfo.loadLabel(context.getPackageManager()));
+        return mView;
+    }
+
+    /**
+     * An async task to load various live wallpaper tiles.
+     */
+    public static class LoaderTask extends AsyncTask<Void, Void, List<LiveWallpaperInfo>> {
+        private final Context mContext;
+
+        public LoaderTask(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        protected List<LiveWallpaperInfo> doInBackground(Void... params) {
+            final PackageManager pm = mContext.getPackageManager();
+
+            List<ResolveInfo> list = pm.queryIntentServices(
+                    new Intent(WallpaperService.SERVICE_INTERFACE),
+                    PackageManager.GET_META_DATA);
+
+            Collections.sort(list, new Comparator<ResolveInfo>() {
+                final Collator mCollator = Collator.getInstance();
+
+                public int compare(ResolveInfo info1, ResolveInfo info2) {
+                    return mCollator.compare(info1.loadLabel(pm), info2.loadLabel(pm));
+                }
+            });
+
+            List<LiveWallpaperInfo> result = new ArrayList<>();
+
+            for (ResolveInfo resolveInfo : list) {
+                WallpaperInfo info = null;
+                try {
+                    info = new WallpaperInfo(mContext, resolveInfo);
+                } catch (XmlPullParserException | IOException e) {
+                    Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+                    continue;
+                }
+
+
+                Drawable thumb = info.loadThumbnail(pm);
+                Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
+                launchIntent.setClassName(info.getPackageName(), info.getServiceName());
+                result.add(new LiveWallpaperInfo(thumb, info, launchIntent));
+            }
+
+            return result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/tileinfo/PickImageInfo.java b/src/com/android/wallpaperpicker/tileinfo/PickImageInfo.java
new file mode 100644
index 0000000..b5ba696
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/PickImageInfo.java
@@ -0,0 +1,74 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.os.Process;
+import android.provider.MediaStore;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.wallpaperpicker.R;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+
+public class PickImageInfo extends WallpaperTileInfo {
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
+        a.startActivityForResultSafely(intent, WallpaperPickerActivity.IMAGE_PICK);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_image_picker_item, parent, false);
+
+        // Make its background the last photo taken on external storage
+        Bitmap lastPhoto = getThumbnailOfLastPhoto(context);
+        if (lastPhoto != null) {
+            ImageView galleryThumbnailBg =
+                    (ImageView) mView.findViewById(R.id.wallpaper_image);
+            galleryThumbnailBg.setImageBitmap(lastPhoto);
+            int colorOverlay = context.getResources().getColor(R.color.wallpaper_picker_translucent_gray);
+            galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
+        }
+
+        mView.setTag(this);
+        return mView;
+    }
+
+    private Bitmap getThumbnailOfLastPhoto(Context context) {
+        boolean canReadExternalStorage = context.checkPermission(
+                Manifest.permission.READ_EXTERNAL_STORAGE, Process.myPid(), Process.myUid()) ==
+                PackageManager.PERMISSION_GRANTED;
+
+        if (!canReadExternalStorage) {
+            // MediaStore.Images.Media.EXTERNAL_CONTENT_URI requires
+            // the READ_EXTERNAL_STORAGE permission
+            return null;
+        }
+
+        Cursor cursor = MediaStore.Images.Media.query(context.getContentResolver(),
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                new String[] { MediaStore.Images.ImageColumns._ID,
+                    MediaStore.Images.ImageColumns.DATE_TAKEN},
+                null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
+
+        Bitmap thumb = null;
+        if (cursor != null) {
+            if (cursor.moveToNext()) {
+                int id = cursor.getInt(0);
+                thumb = MediaStore.Images.Thumbnails.getThumbnail(context.getContentResolver(),
+                        id, MediaStore.Images.Thumbnails.MINI_KIND, null);
+            }
+            cursor.close();
+        }
+        return thumb;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/tileinfo/ResourceWallpaperInfo.java b/src/com/android/wallpaperpicker/tileinfo/ResourceWallpaperInfo.java
new file mode 100644
index 0000000..79dd1b2
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/ResourceWallpaperInfo.java
@@ -0,0 +1,65 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.wallpaperpicker.WallpaperCropActivity.CropViewScaleAndOffsetProvider;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+
+public class ResourceWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private final Resources mResources;
+    private final int mResId;
+
+    public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
+        super(thumb);
+        mResources = res;
+        mResId = resId;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.InputStreamSource bitmapSource =
+                new BitmapRegionTileSource.InputStreamSource(mResources, mResId, a);
+        a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleAndOffsetProvider() {
+
+            @Override
+            public float getScale(Point wallpaperSize, RectF crop) {
+                return wallpaperSize.x /crop.width();
+            }
+
+            @Override
+            public float getParallaxOffset() {
+                return a.getWallpaperParallaxOffset();
+            }
+        }, new Runnable() {
+
+            @Override
+            public void run() {
+                if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                    a.setWallpaperButtonEnabled(true);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSave(WallpaperPickerActivity a) {
+        a.cropImageAndSetWallpaper(mResources, mResId, true /* shouldFadeOutOnFinish */);
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/tileinfo/ThirdPartyWallpaperInfo.java b/src/com/android/wallpaperpicker/tileinfo/ThirdPartyWallpaperInfo.java
new file mode 100644
index 0000000..ac19a89
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/ThirdPartyWallpaperInfo.java
@@ -0,0 +1,79 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.wallpaperpicker.R;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+import com.android.wallpaperpicker.WallpaperUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class ThirdPartyWallpaperInfo extends WallpaperTileInfo {
+
+    private final ResolveInfo mResolveInfo;
+    private final int mIconSize;
+
+    public ThirdPartyWallpaperInfo(ResolveInfo resolveInfo, int iconSize) {
+        mResolveInfo = resolveInfo;
+        mIconSize = iconSize;
+    }
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        final ComponentName itemComponentName = new ComponentName(
+                mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
+        Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER)
+            .setComponent(itemComponentName)
+            .putExtra(WallpaperUtils.EXTRA_WALLPAPER_OFFSET,
+                    a.getWallpaperParallaxOffset());
+        a.startActivityForResultSafely(
+                launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
+
+        TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label);
+        label.setText(mResolveInfo.loadLabel(context.getPackageManager()));
+        Drawable icon = mResolveInfo.loadIcon(context.getPackageManager());
+        icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
+        label.setCompoundDrawables(null, icon, null, null);
+        return mView;
+    }
+
+    public static List<ThirdPartyWallpaperInfo> getAll(Context context) {
+        ArrayList<ThirdPartyWallpaperInfo> result = new ArrayList<>();
+        int iconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
+
+        final PackageManager pm = context.getPackageManager();
+        Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
+        HashSet<String> excludePackages = new HashSet<>();
+        // Exclude packages which contain an image picker
+        for (ResolveInfo info : pm.queryIntentActivities(pickImageIntent, 0)) {
+            excludePackages.add(info.activityInfo.packageName);
+        }
+        excludePackages.add(context.getPackageName());
+        excludePackages.add("com.android.wallpaper.livepicker");
+
+        final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+        for (ResolveInfo info : pm.queryIntentActivities(pickWallpaperIntent, 0)) {
+            if (!excludePackages.contains(info.activityInfo.packageName)) {
+                result.add(new ThirdPartyWallpaperInfo(info, iconSize));
+            }
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/tileinfo/UriWallpaperInfo.java b/src/com/android/wallpaperpicker/tileinfo/UriWallpaperInfo.java
new file mode 100644
index 0000000..7e62154
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/UriWallpaperInfo.java
@@ -0,0 +1,112 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import com.android.wallpaperpicker.common.CropAndSetWallpaperTask;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.wallpaperpicker.R;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+import com.android.wallpaperpicker.common.InputStreamProvider;
+
+public class UriWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private static final String TAG = "UriWallpaperInfo";
+
+    public final Uri mUri;
+
+    public UriWallpaperInfo(Uri uri) {
+        super(null);
+        mUri = uri;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.InputStreamSource bitmapSource =
+                new BitmapRegionTileSource.InputStreamSource(a, mUri);
+        a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() {
+
+            @Override
+            public void run() {
+                if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+                    a.selectTile(mView);
+                    a.setWallpaperButtonEnabled(true);
+                } else {
+                    ViewGroup parent = (ViewGroup) mView.getParent();
+                    if (parent != null) {
+                        parent.removeView(mView);
+                        Toast.makeText(a, R.string.image_load_fail,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onSave(final WallpaperPickerActivity a) {
+        CropAndSetWallpaperTask.OnBitmapCroppedHandler h =
+                new CropAndSetWallpaperTask.OnBitmapCroppedHandler() {
+            public void onBitmapCropped(byte[] imageBytes) {
+                // rotation is set to 0 since imageBytes has already been correctly rotated
+                Bitmap thumb = createThumbnail(
+                        InputStreamProvider.fromBytes(imageBytes), a, 0, true);
+                a.getSavedImages().writeImage(thumb, imageBytes);
+            }
+        };
+        boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f;
+        a.cropImageAndSetWallpaper(mUri, h, shouldFadeOutOnFinish);
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+
+    public void loadThumbnaleAsync(final WallpaperPickerActivity activity) {
+        mView.setVisibility(View.GONE);
+        new AsyncTask<Void, Void, Bitmap>() {
+            protected Bitmap doInBackground(Void...args) {
+                try {
+                    InputStreamProvider isp = InputStreamProvider.fromUri(activity, mUri);
+                    int rotation = isp.getRotationFromExif(activity);
+                    return createThumbnail(isp, activity, rotation, false);
+                } catch (SecurityException securityException) {
+                    if (activity.isActivityDestroyed()) {
+                        // Temporarily granted permissions are revoked when the activity
+                        // finishes, potentially resulting in a SecurityException here.
+                        // Even though {@link #isDestroyed} might also return true in different
+                        // situations where the configuration changes, we are fine with
+                        // catching these cases here as well.
+                        cancel(false);
+                    } else {
+                        // otherwise it had a different cause and we throw it further
+                        throw securityException;
+                    }
+                    return null;
+                }
+            }
+            protected void onPostExecute(Bitmap thumb) {
+                if (!isCancelled() && thumb != null) {
+                    setThumb(new BitmapDrawable(activity.getResources(), thumb));
+                    mView.setVisibility(View.VISIBLE);
+                } else {
+                    Log.e(TAG, "Error loading thumbnail for uri=" + mUri);
+                }
+            }
+        }.execute();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/wallpaperpicker/tileinfo/WallpaperTileInfo.java b/src/com/android/wallpaperpicker/tileinfo/WallpaperTileInfo.java
new file mode 100644
index 0000000..01b89b1
--- /dev/null
+++ b/src/com/android/wallpaperpicker/tileinfo/WallpaperTileInfo.java
@@ -0,0 +1,67 @@
+package com.android.wallpaperpicker.tileinfo;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.common.Utils;
+import com.android.wallpaperpicker.R;
+import com.android.wallpaperpicker.WallpaperPickerActivity;
+import com.android.wallpaperpicker.common.InputStreamProvider;
+
+public abstract class WallpaperTileInfo {
+
+    protected View mView;
+
+    public void onClick(WallpaperPickerActivity a) {}
+
+    public void onSave(WallpaperPickerActivity a) {}
+
+    public void onDelete(WallpaperPickerActivity a) {}
+
+    public boolean isSelectable() { return false; }
+
+    public boolean isNamelessWallpaper() { return false; }
+
+    public void onIndexUpdated(CharSequence label) {
+        if (isNamelessWallpaper()) {
+            mView.setContentDescription(label);
+        }
+    }
+
+    public abstract View createView(Context context, LayoutInflater inflator, ViewGroup parent);
+
+    protected static Point getDefaultThumbSize(Resources res) {
+        return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+                res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+    }
+
+    protected static Bitmap createThumbnail(InputStreamProvider streamProvider, Context context,
+            int rotation, boolean leftAligned) {
+        Point size = getDefaultThumbSize(context.getResources());
+        int width = size.x;
+        int height = size.y;
+        Point bounds = streamProvider.getImageBounds();
+        if (bounds == null) {
+            return null;
+        }
+
+        Matrix rotateMatrix = new Matrix();
+        rotateMatrix.setRotate(rotation);
+        float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+        rotateMatrix.mapPoints(rotatedBounds);
+        rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+        rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+        RectF cropRect = Utils.getMaxCropRect(
+                (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
+        return streamProvider.readCroppedBitmap(cropRect, width, height, rotation);
+    }
+}
\ No newline at end of file